feat: upgrade user management demo

This commit is contained in:
Codex
2026-03-18 16:43:04 +08:00
parent e8afe9a5f4
commit 00306082fb
9 changed files with 753 additions and 464 deletions

View File

@@ -1,225 +1,161 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spring Boot 学习中心</title>
<title>Spring Boot Learning Hub</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
h1 { color: #6DB33F; text-align: center; margin: 30px 0; font-size: 2.5em; }
h2 { color: #333; border-bottom: 3px solid #6DB33F; padding-bottom: 10px; margin: 20px 0 15px; }
.card { background: white; padding: 25px; margin: 15px 0; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.card h3 { color: #6DB33F; margin-bottom: 15px; font-size: 1.3em; }
.btn-group { display: flex; flex-wrap: wrap; gap: 10px; margin: 15px 0; }
.btn { display: inline-block; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 500; transition: all 0.3s; }
.btn-primary { background: #6DB33F; color: white; }
.btn-primary:hover { background: #5da32f; transform: translateY(-2px); }
.btn-secondary { background: #333; color: white; }
.btn-secondary:hover { background: #444; }
.btn-info { background: #17a2b8; color: white; }
.btn-info:hover { background: #138496; }
.btn-warning { background: #ffc107; color: #333; }
.btn-warning:hover { background: #e0a800; }
code { background: #f0f0f0; padding: 2px 8px; border-radius: 4px; font-family: 'Fira Code', monospace; font-size: 14px; }
pre { background: #2d2d2d; color: #f8f8f2; padding: 20px; border-radius: 8px; overflow-x: auto; margin: 15px 0; }
pre code { background: none; color: inherit; }
ul { line-height: 2; padding-left: 20px; }
.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; }
.feature-item { background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #6DB33F; transition: all 0.3s; }
.feature-item:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.feature-item h4 { color: #333; margin-bottom: 8px; font-size: 1.1em; }
.feature-item p { color: #666; font-size: 14px; margin: 0; }
.feature-item a { color: inherit; text-decoration: none; }
.api-test { background: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 8px; }
.api-test input, .api-test select { padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin: 5px; }
.api-test button { padding: 10px 20px; background: #6DB33F; color: white; border: none; border-radius: 4px; cursor: pointer; }
.api-test button:hover { background: #5da32f; }
#result { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 8px; margin-top: 10px; white-space: pre-wrap; font-family: monospace; font-size: 14px; }
.footer { text-align: center; margin-top: 40px; padding: 20px; color: #666; border-top: 1px solid #ddd; }
.nav-links { display: flex; justify-content: center; gap: 15px; margin-bottom: 30px; }
.nav-links a { padding: 10px 20px; background: white; border-radius: 8px; text-decoration: none; color: #6DB33F; font-weight: 500; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.nav-links a:hover { background: #6DB33F; color: white; }
.nav-links a.active { background: #6DB33F; color: white; }
:root {
--bg: radial-gradient(circle at top, #eef9f0 0%, #eff4ff 45%, #ffffff 100%);
--card: rgba(255,255,255,0.92);
--text: #132238;
--muted: #62788f;
--green: #3f8f2c;
--blue: #0f6db5;
--line: #dfebf6;
--shadow: 0 20px 48px rgba(16, 39, 74, 0.12);
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Segoe UI", "PingFang SC", sans-serif;
background: var(--bg);
color: var(--text);
}
.page {
max-width: 1120px;
margin: 0 auto;
padding: 32px 18px 48px;
}
.hero {
background: var(--card);
border: 1px solid rgba(255,255,255,0.8);
border-radius: 28px;
box-shadow: var(--shadow);
padding: 32px;
margin-bottom: 24px;
}
.eyebrow {
display: inline-flex;
padding: 8px 14px;
border-radius: 999px;
background: rgba(63, 143, 44, 0.12);
color: #2d6e20;
font-size: 12px;
font-weight: 700;
letter-spacing: .06em;
text-transform: uppercase;
}
h1 {
margin: 18px 0 12px;
font-size: clamp(36px, 6vw, 58px);
line-height: 1.04;
}
p {
color: var(--muted);
line-height: 1.7;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 20px;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 18px;
border-radius: 14px;
font-weight: 700;
text-decoration: none;
}
.btn-primary { background: linear-gradient(135deg, var(--green), #57b83f); color: #fff; }
.btn-secondary { background: rgba(15, 109, 181, 0.08); color: var(--blue); }
.grid {
display: grid;
gap: 18px;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.card {
background: var(--card);
border: 1px solid rgba(255,255,255,0.8);
border-radius: 24px;
box-shadow: var(--shadow);
padding: 24px;
}
.card h2 {
margin: 0 0 10px;
font-size: 24px;
}
.card code {
display: inline-block;
margin-top: 14px;
padding: 6px 10px;
border-radius: 10px;
background: rgba(15, 109, 181, 0.08);
color: var(--blue);
font-size: 13px;
}
.quick-links {
display: grid;
gap: 10px;
margin-top: 12px;
}
.quick-links a {
text-decoration: none;
color: var(--text);
background: rgba(255,255,255,0.8);
border: 1px solid var(--line);
border-radius: 16px;
padding: 14px 16px;
font-weight: 600;
}
</style>
</head>
<body>
<h1>🍃 Spring Boot 学习中心</h1>
<div class="nav-links">
<a href="/" class="active">首页</a>
<a href="/users.html">用户管理</a>
<a href="/aop.html">AOP 切面</a>
<a href="/events.html">事件机制</a>
</div>
<div class="card">
<h3>📚 学习模块</h3>
<div class="feature-grid">
<a href="/users.html" class="feature-item">
<h4>👥 用户管理</h4>
<p>RESTful API 设计、CRUD 操作、参数绑定</p>
</a>
<a href="/aop.html" class="feature-item">
<h4>🔪 AOP 切面编程</h4>
<p>日志记录、性能监控、限流控制</p>
</a>
<a href="/events.html" class="feature-item">
<h4>📡 事件机制</h4>
<p>发布/订阅模式、解耦业务逻辑</p>
</a>
<a href="/learn" class="feature-item">
<h4>🔐 鉴权演示(学习用)</h4>
<p>最小 JWT 流程:登录、携带 Token、访问受保护接口</p>
</a>
<div class="page">
<section class="hero">
<span class="eyebrow">Spring Boot practice</span>
<h1>Explore web, validation, security, and AOP from one clean hub.</h1>
<p>
This project now highlights the most useful demos directly from the homepage so you can jump into the
user management flow, auth example, AOP pages, and health endpoints without decoding broken text first.
</p>
<div class="hero-actions">
<a class="btn btn-primary" href="/users.html">Open user management</a>
<a class="btn btn-secondary" href="/learn">View auth demo</a>
<a class="btn btn-secondary" href="/actuator/health">Check health</a>
</div>
</div>
<h2>🔗 快速链接</h2>
<div class="card">
<div class="btn-group">
<a class="btn btn-primary" href="/learn">API 接口列表</a>
<a class="btn btn-info" href="/api/users">用户 JSON</a>
<a class="btn btn-secondary" href="/actuator/health">健康检查</a>
</div>
</div>
<h2>🧪 接口测试</h2>
<div class="card">
<h3>GET 参数示例</h3>
<div class="api-test">
<input type="text" id="param-name" placeholder="姓名" value="张三">
<input type="number" id="param-age" placeholder="年龄" value="25">
<button onclick="testParams()">测试</button>
<div id="result-params"></div>
</div>
<p><code>GET /learn/params?name=xxx&age=18</code></p>
</div>
<div class="card">
<h3>路径变量示例</h3>
<div class="api-test">
<input type="text" id="path-id" placeholder="ID" value="123">
<button onclick="testPath()">测试</button>
<div id="result-path"></div>
</div>
<p><code>GET /learn/path/{id}</code></p>
</div>
<div class="card">
<h3>POST JSON 示例</h3>
<div class="api-test">
<input type="text" id="post-data" placeholder='JSON 数据' value='{"name":"test","value":123}' style="width: 300px;">
<button onclick="testPost()">测试</button>
<div id="result-post"></div>
</div>
<p><code>POST /learn/body</code></p>
</div>
<h2>📖 学习路径</h2>
<div class="card">
<h3>1. IOC 容器</h3>
<ul>
<li><code>@Component</code>, <code>@Service</code>, <code>@Repository</code>, <code>@Controller</code></li>
<li><code>@Autowired</code> 依赖注入</li>
<li><code>@Configuration</code> + <code>@Bean</code> 配置类</li>
</ul>
</div>
<div class="card">
<h3>2. Web 开发</h3>
<ul>
<li><code>@RestController</code> = <code>@Controller</code> + <code>@ResponseBody</code></li>
<li><code>@RequestMapping</code>, <code>@GetMapping</code>, <code>@PostMapping</code></li>
<li><code>@PathVariable</code>, <code>@RequestParam</code>, <code>@RequestBody</code></li>
</ul>
</div>
<div class="card">
<h3>3. AOP 切面编程</h3>
<pre><code>@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.*.*(..))")
public void logBefore(JoinPoint jp) {
System.out.println("方法调用: " + jp.getSignature());
}
}</code></pre>
</div>
<div class="card">
<h3>4. 事件机制</h3>
<pre><code>// 发布事件
@Autowired
ApplicationEventPublisher publisher;
publisher.publishEvent(new UserEvent(...));
</section>
// 监听事件
@EventListener
public void onEvent(UserEvent event) {
// 处理事件
}</code></pre>
</div>
<h2>📁 项目结构</h2>
<div class="card">
<pre><code>├── src/main/java/com/example/demo/
├── DemoApplication.java # 启动类
├── controller/ # 控制器层
├── LearnController.java # 学习示例
├── UserController.java # 用户 API
└── AopEventController.java
│ ├── service/ # 业务逻辑层
├── model/ # 实体类
├── aop/ # AOP 切面
│ ├── LoggingAspect.java
├── PerformanceAspect.java
└── RateLimitAspect.java
└── event/ # 事件机制
├── UserEventPublisher.java
└── UserEventListener.java
├── src/main/resources/
├── static/ # 静态资源
│ │ ├── index.html
│ │ ├── users.html
│ │ ├── aop.html
│ │ └── events.html
│ └── application.properties # 配置文件
└── pom.xml # Maven 配置</code></pre>
</div>
<div class="footer">
<p>🍃 Spring Boot 学习脚手架 | <a href="https://spring.io" style="color: #6DB33F;">Spring 官网</a></p>
</div>
<script>
async function testParams() {
const name = document.getElementById('param-name').value;
const age = document.getElementById('param-age').value;
const res = await fetch(`/learn/params?name=${encodeURIComponent(name)}&age=${age}`);
const data = await res.json();
document.getElementById('result-params').innerHTML = '<div id="result">' + JSON.stringify(data, null, 2) + '</div>';
}
async function testPath() {
const id = document.getElementById('path-id').value;
const res = await fetch(`/learn/path/${id}`);
const data = await res.json();
document.getElementById('result-path').innerHTML = '<div id="result">' + JSON.stringify(data, null, 2) + '</div>';
}
async function testPost() {
const data = document.getElementById('post-data').value;
const res = await fetch('/learn/body', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: data
});
const result = await res.json();
document.getElementById('result-post').innerHTML = '<div id="result">' + JSON.stringify(result, null, 2) + '</div>';
}
</script>
<section class="grid">
<article class="card">
<h2>User Management</h2>
<p>CRUD, validation, duplicate email protection, search, and stats cards in one interactive page.</p>
<code>/users.html</code>
</article>
<article class="card">
<h2>AOP Showcase</h2>
<p>Review logging, performance tracing, and rate limiting demonstrations built with Spring AOP.</p>
<code>/aop.html</code>
</article>
<article class="card">
<h2>Event Flow</h2>
<p>See how controller actions can publish events and decouple downstream reactions.</p>
<code>/events.html</code>
</article>
<article class="card">
<h2>Quick Links</h2>
<div class="quick-links">
<a href="/api/users">Open `/api/users`</a>
<a href="/api/users/stats">Open `/api/users/stats`</a>
<a href="/learn">Open `/learn`</a>
<a href="/actuator/health">Open `/actuator/health`</a>
</div>
</article>
</section>
</div>
</body>
</html>
</html>