feat(v2): add profile banner and advanced module task card

This commit is contained in:
likingcode
2026-03-09 01:55:19 +08:00
parent 2b8b4213e2
commit e766fa427f
2 changed files with 260 additions and 0 deletions

View File

@@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高级功能学习 - Spring Boot</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 30px 20px; text-align: center; margin-bottom: 20px; border-radius: 10px; }
.header h1 { font-size: 2em; }
.nav { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; justify-content: center; }
.nav a { padding: 10px 20px; background: white; border-radius: 20px; text-decoration: none; color: #333; font-size: 0.9em; }
.nav a:hover, .nav a.active { background: #f5576c; color: white; }
.card { background: white; border-radius: 10px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
.card h3 { color: #f5576c; margin-bottom: 15px; border-bottom: 2px solid #eee; padding-bottom: 10px; }
.config-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; }
.config-item { background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #f5576c; }
.config-item h4 { margin-bottom: 10px; }
.config-item .current { color: #28a745; font-weight: bold; }
.btn { padding: 10px 20px; background: #f5576c; color: white; border: none; border-radius: 5px; cursor: pointer; margin: 5px; }
.btn:hover { background: #e0465b; }
.btn-secondary { background: #6c757d; }
.result { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; margin-top: 10px; font-family: monospace; font-size: 0.9em; overflow-x: auto; max-height: 400px; overflow-y: auto; }
.compare-table { width: 100%; border-collapse: collapse; margin: 15px 0; }
.compare-table th, .compare-table td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
.compare-table th { background: #f8f9fa; }
.compare-table tr:hover { background: #f8f9fa; }
.problem-box { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 10px 0; border-radius: 5px; }
.problem-box h4 { color: #856404; margin-bottom: 8px; }
.redis-types { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; }
.redis-type { background: #e3f2fd; padding: 15px; border-radius: 8px; }
.redis-type h4 { color: #1976d2; margin-bottom: 5px; }
.tipbox { background:#fff7e6;border-left:4px solid #fa8c16;padding:15px;border-radius:8px;margin-bottom:20px; }
.tipbox h4 { color:#ad6800;margin-bottom:8px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 高级功能学习</h1>
<p>Redis 缓存 | 分布式锁 | 多数据库 | 认证方案</p>
</div>
<div class="nav">
<a href="index.html">🏠 首页</a>
<a href="ioc.html">📦 IoC</a>
<a href="aop.html">🔪 AOP</a>
<a href="mybatis.html">💾 MyBatis</a>
<a href="transaction.html">🔄 事务</a>
<a href="users.html">👥 用户</a>
<a href="advanced.html" class="active">🚀 高级</a>
<a href="api.html">🔌 API</a>
</div>
<div class="tipbox">
<h4>🧪 实验任务卡(高级模块)</h4>
<ul style="padding-left:20px;line-height:1.8;">
<li>目标:比较 learn/advanced profile 下可用能力差异</li>
<li>步骤1先查看“系统配置”和“认证方案对比”</li>
<li>步骤2执行 Redis SET/GET + 分布式锁接口</li>
<li>预期advanced 模式下功能更完整,返回字段更丰富</li>
<li>常见坑:本机无 Redis 导致接口失败(属于环境问题)</li>
</ul>
</div>
<div class="card">
<h3>⚙️ 系统配置</h3>
<button class="btn" onclick="loadConfig()">查看当前配置</button>
<div id="configResult" class="result"></div>
</div>
<div class="card">
<h3>🔐 认证方案对比</h3>
<button class="btn" onclick="loadAuthCompare()">JWT vs Sa-Token</button>
<div id="authResult" class="result"></div>
</div>
<div class="card">
<h3>💾 Redis 数据类型</h3>
<div class="redis-types">
<div class="redis-type"><h4>String</h4><p>字符串,最基本类型</p></div>
<div class="redis-type"><h4>Hash</h4><p>哈希,存储对象</p></div>
<div class="redis-type"><h4>List</h4><p>列表,队列/栈</p></div>
<div class="redis-type"><h4>Set</h4><p>集合,去重运算</p></div>
<div class="redis-type"><h4>SortedSet</h4><p>有序集合,排名</p></div>
</div>
<button class="btn" onclick="loadRedisTypes()">查看详细说明</button>
</div>
<div class="card">
<h3>🧪 Redis 操作测试</h3>
<div style="display:flex;gap:10px;margin-bottom:15px;">
<input type="text" id="redisKey" placeholder="Key" style="flex:1;padding:10px;border:1px solid #ddd;border-radius:5px;">
<input type="text" id="redisValue" placeholder="Value" style="flex:1;padding:10px;border:1px solid #ddd;border-radius:5px;">
<input type="number" id="redisTtl" placeholder="TTL(秒)" value="60" style="width:100px;padding:10px;border:1px solid #ddd;border-radius:5px;">
</div>
<button class="btn" onclick="setRedisString()">SET</button>
<button class="btn btn-secondary" onclick="getRedisString()">GET</button>
<div id="redisResult" class="result"></div>
</div>
<div class="card">
<h3>🔒 分布式锁演示</h3>
<p>模拟多个请求竞争同一资源</p>
<input type="text" id="lockResource" placeholder="资源名称" value="order:1001" style="padding:10px;border:1px solid #ddd;border-radius:5px;margin-right:10px;">
<button class="btn" onclick="testDistributedLock()">获取分布式锁</button>
<div id="lockResult" class="result"></div>
</div>
<div class="card">
<h3>⚠️ 缓存三大问题</h3>
<div class="problem-box">
<h4>缓存穿透</h4>
<p><strong>问题:</strong>查询不存在的数据,每次都打到数据库</p>
<p><strong>解决:</strong>布隆过滤器 | 缓存空值</p>
</div>
<div class="problem-box">
<h4>缓存击穿</h4>
<p><strong>问题:</strong>热点key过期大量请求打到数据库</p>
<p><strong>解决:</strong>互斥锁 | 逻辑过期</p>
</div>
<div class="problem-box">
<h4>缓存雪崩</h4>
<p><strong>问题:</strong>大量key同时过期数据库压力激增</p>
<p><strong>解决:</strong>随机过期时间 | 多级缓存</p>
</div>
<button class="btn" onclick="loadCacheProblems()">查看详细方案</button>
<div id="cacheResult" class="result"></div>
</div>
</div>
<script>
async function loadConfig() {
const result = document.getElementById('configResult');
result.textContent = '加载中...';
try {
const res = await fetch('/api/learning/advanced/config');
const data = await res.json();
result.innerHTML = `<strong>系统配置</strong>\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
result.textContent = '加载失败: ' + e.message;
}
}
async function loadAuthCompare() {
const result = document.getElementById('authResult');
result.textContent = '加载中...';
try {
const res = await fetch('/api/learning/advanced/auth/compare');
const data = await res.json();
result.innerHTML = `<strong>认证方案对比</strong>\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
result.textContent = '加载失败: ' + e.message;
}
}
async function loadRedisTypes() {
const result = document.getElementById('redisResult');
try {
const res = await fetch('/api/learning/advanced/redis/types');
const data = await res.json();
result.innerHTML = `<strong>Redis 数据类型</strong>\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
result.textContent = '加载失败: ' + e.message;
}
}
async function setRedisString() {
const key = document.getElementById('redisKey').value;
const value = document.getElementById('redisValue').value;
const ttl = document.getElementById('redisTtl').value;
if (!key || !value) {
alert('请输入 Key 和 Value');
return;
}
const result = document.getElementById('redisResult');
try {
const res = await fetch(`/api/learning/advanced/redis/string?key=${encodeURIComponent(key)}&value=${encodeURIComponent(value)}&ttl=${ttl}`, { method: 'POST' });
const data = await res.json();
result.innerHTML = `<strong>SET 结果</strong>\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
result.textContent = '操作失败: ' + e.message;
}
}
async function getRedisString() {
const key = document.getElementById('redisKey').value;
if (!key) {
alert('请输入 Key');
return;
}
const result = document.getElementById('redisResult');
try {
const res = await fetch(`/api/learning/advanced/redis/string?key=${encodeURIComponent(key)}`);
const data = await res.json();
result.innerHTML = `<strong>GET 结果</strong>\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
result.textContent = '操作失败: ' + e.message;
}
}
async function testDistributedLock() {
const resource = document.getElementById('lockResource').value;
const result = document.getElementById('lockResult');
result.textContent = '获取锁中...';
try {
const res = await fetch(`/api/learning/advanced/redis/lock?resource=${encodeURIComponent(resource)}`, { method: 'POST' });
const data = await res.json();
result.innerHTML = `<strong>分布式锁结果</strong>\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
result.textContent = '操作失败: ' + e.message;
}
}
async function loadCacheProblems() {
const result = document.getElementById('cacheResult');
try {
const res = await fetch('/api/learning/advanced/cache/problems');
const data = await res.json();
result.innerHTML = `<strong>缓存问题解决方案</strong>\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
result.textContent = '加载失败: ' + e.message;
}
}
loadConfig();
</script>
</body>
</html>

View File

@@ -49,6 +49,7 @@
.tab-content.active { display: block; } .tab-content.active { display: block; }
.json-input { width: 100%; min-height: 100px; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-family: monospace; font-size: 0.9em; } .json-input { width: 100%; min-height: 100px; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-family: monospace; font-size: 0.9em; }
.profile-banner { background:#fff7e6;border-left:4px solid #fa8c16;padding:12px 14px;border-radius:8px;margin-bottom:18px; color:#874d00; }
</style> </style>
</head> </head>
<body> <body>
@@ -68,6 +69,8 @@
<a href="api.html" class="active">🔌 API</a> <a href="api.html" class="active">🔌 API</a>
</div> </div>
<div id="profileBanner" class="profile-banner">正在读取 profile...</div>
<div class="tabs"> <div class="tabs">
<div class="tab active" onclick="switchTab('user')">👥 用户 API</div> <div class="tab active" onclick="switchTab('user')">👥 用户 API</div>
<div class="tab" onclick="switchTab('product')">📦 产品 API</div> <div class="tab" onclick="switchTab('product')">📦 产品 API</div>
@@ -248,6 +251,17 @@
document.getElementById(tab + 'Tab').classList.add('active'); document.getElementById(tab + 'Tab').classList.add('active');
} }
async function loadProfileBanner() {
try {
const res = await fetch('/api/profile');
const data = await res.json();
const enabled = (data.enabledModules || []).join(', ');
document.getElementById('profileBanner').textContent = `当前 profile: ${data.profile} | 已启用模块: ${enabled}`;
} catch (e) {
document.getElementById('profileBanner').textContent = '当前 profile 读取失败,请检查 /api/profile';
}
}
async function testApi(method, url, body, resultId) { async function testApi(method, url, body, resultId) {
const resultDiv = document.getElementById(resultId); const resultDiv = document.getElementById(resultId);
resultDiv.classList.add('show'); resultDiv.classList.add('show');
@@ -275,6 +289,8 @@
resultDiv.innerHTML = `<strong style="color:#ff6b6b;">Error</strong>\n\n${e.message}`; resultDiv.innerHTML = `<strong style="color:#ff6b6b;">Error</strong>\n\n${e.message}`;
} }
} }
loadProfileBanner();
</script> </script>
</body> </body>
</html> </html>