feat(content): enrich structs pages with learning map and deeper CRUD guidance

This commit is contained in:
likingcode
2026-03-10 00:20:58 +08:00
parent 0bbfdd1d7a
commit f16401b913
5 changed files with 319 additions and 47 deletions

40
src/main/webapp/hello.jsp Normal file
View File

@@ -0,0 +1,40 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<title>Hello 示例 - Struts2</title>
<style>
body { font-family: Arial, sans-serif; max-width: 900px; margin: 50px auto; padding: 20px; background:#f7f8fa; }
.card { background:#fff; border-radius:14px; padding:24px; box-shadow:0 4px 14px rgba(0,0,0,.06); margin-bottom:20px; }
h1 { color:#e74c3c; }
.msg { font-size:1.3em; color:#2c3e50; margin-top:10px; }
code { background:#f0f0f0; padding:2px 6px; border-radius:4px; }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; }
a { color:#3498db; }
</style>
</head>
<body>
<div class="card">
<h1>👋 Hello 示例</h1>
<p class="msg">${message}</p>
<p>这是 Struts2 最基础的一条链路:浏览器请求 <code>/hello</code> → Struts2 匹配 Action → Action 返回结果名 → JSP 被渲染。</p>
</div>
<div class="lab">
<strong>学习观察点</strong>
<ul>
<li>访问 URL<code>/hello</code></li>
<li>对应配置:<code>struts.xml</code> 里的 <code>&lt;action name="hello" ...&gt;</code></li>
<li>对应控制器:<code>HelloAction.execute()</code></li>
<li>视图页面:当前这个 <code>hello.jsp</code></li>
</ul>
</div>
<div class="card">
<h3>🔍 为什么这个例子重要</h3>
<p>很多人一开始学 Struts2会直接被拦截器、标签库、OGNL、配置文件吓到。Hello 示例的价值在于:先把“请求如何进来、结果如何出去”这条最小主线跑通,再去理解更复杂的机制。</p>
</div>
<p><a href="learn">← 返回学习中心</a></p>
</body>
</html>

76
src/main/webapp/index.jsp Normal file
View File

@@ -0,0 +1,76 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<title>Struts2 Learning Scaffold</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1100px; margin: 40px auto; padding: 20px; background:#f7f8fa; }
.hero { background: linear-gradient(135deg,#ff7e5f,#feb47b); color:#fff; padding:30px; border-radius:16px; }
.hero h1 { margin:0 0 10px; font-size:2.4em; }
.hero p { font-size:1.1em; opacity: .95; }
.grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap:18px; margin-top:24px; }
.card { background:#fff; border-radius:14px; padding:20px; box-shadow:0 4px 14px rgba(0,0,0,.06); }
.card h3 { margin-top:0; color:#e74c3c; }
.btn { display:inline-block; background:#3498db; color:#fff; padding:10px 18px; margin-top:10px; text-decoration:none; border-radius:8px; }
.btn:hover { background:#2980b9; }
.btn.green { background:#27ae60; }
.btn.purple { background:#9b59b6; }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:16px; border-radius:10px; margin-top:24px; }
.lab ul { line-height:1.8; }
.timeline { background:#fff; border-radius:14px; padding:20px; margin-top:24px; box-shadow:0 4px 14px rgba(0,0,0,.06); }
.timeline li { margin:10px 0; line-height:1.7; }
code { background:#f0f0f0; padding:2px 6px; border-radius:4px; }
</style>
</head>
<body>
<div class="hero">
<h1>🚀 Struts2 Learning Scaffold</h1>
<p>不是单纯的 Struts2 演示页,而是一套带任务卡、表单实验、拦截器观察和 CRUD 流程的学习脚手架。</p>
<a href="learn" class="btn green">进入学习中心</a>
</div>
<div class="lab">
<strong>推荐学习顺序</strong>
<ul>
<li><strong>第一步:</strong> 进入 <a href="hello">Hello 示例</a>,理解 Action → JSP 的最小闭环</li>
<li><strong>第二步:</strong> 进入 <a href="calc">计算器</a>,体验表单绑定、校验、错误回显</li>
<li><strong>第三步:</strong> 进入 <a href="interceptor">拦截器演示</a>,观察请求被增强的过程</li>
<li><strong>第四步:</strong> 进入 <a href="user">用户管理</a>,学习列表/表单/增删改的完整流程</li>
</ul>
</div>
<div class="grid">
<div class="card">
<h3>👋 Hello 示例</h3>
<p>最小 Action 示例,适合理解 Struts2 如何把请求映射到 Action再把数据渲染到 JSP。</p>
<a href="hello" class="btn">打开示例</a>
</div>
<div class="card">
<h3>🔢 计算器实验</h3>
<p>学习参数绑定、<code>validate()</code> 校验、字段错误回显,以及成功/失败两条分支。</p>
<a href="calc" class="btn">开始实验</a>
</div>
<div class="card">
<h3>🛡️ 拦截器观察</h3>
<p>重点模块。能看到日志、执行时间、限流、监控等拦截器如何织入请求链路。</p>
<a href="interceptor" class="btn purple">观察拦截器</a>
</div>
<div class="card">
<h3>👥 用户 CRUD</h3>
<p>从列表页到表单页,再到新增/编辑/删除,形成完整的 Struts2 业务流学习闭环。</p>
<a href="user" class="btn green">进入 CRUD</a>
</div>
</div>
<div class="timeline">
<h3>📚 你会学到什么</h3>
<ul>
<li><strong>请求入口:</strong><code>StrutsPrepareAndExecuteFilter</code> 如何接管请求</li>
<li><strong>控制层:</strong>Action 如何接收参数、执行业务、返回 result</li>
<li><strong>视图层:</strong>JSP + Struts 标签库如何绑定 Action 数据</li>
<li><strong>增强机制:</strong>Interceptor Stack 如何像 AOP 一样织入日志、校验、限流</li>
<li><strong>状态流转:</strong>输入错误、成功提交、重定向返回列表等典型页面流</li>
</ul>
</div>
</body>
</html>

View File

@@ -5,22 +5,22 @@
<head> <head>
<title>Struts2 学习中心</title> <title>Struts2 学习中心</title>
<style> <style>
body { font-family: Arial, sans-serif; max-width: 900px; margin: 50px auto; padding: 20px; } body { font-family: Arial, sans-serif; max-width: 1000px; margin: 40px auto; padding: 20px; background:#f7f8fa; }
h1 { color: #e74c3c; } h1 { color: #e74c3c; }
h2 { color: #3498db; border-bottom: 2px solid #3498db; padding-bottom: 10px; } h2 { color: #3498db; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
.card { background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0; } .card { background: #fff; padding: 20px; border-radius: 12px; margin: 20px 0; box-shadow:0 4px 14px rgba(0,0,0,.06); }
.card h3 { margin-top: 0; } .card h3 { margin-top: 0; }
ul { line-height: 2; } ul { line-height: 1.9; }
a { color: #3498db; text-decoration: none; } a { color: #3498db; text-decoration: none; }
a:hover { text-decoration: underline; } a:hover { text-decoration: underline; }
.btn { display: inline-block; background: #3498db; color: white; padding: 10px 20px; .btn { display: inline-block; background: #3498db; color: white; padding: 10px 20px; border-radius: 6px; margin: 5px; }
border-radius: 5px; margin: 5px; }
.btn:hover { text-decoration: none; background: #2980b9; } .btn:hover { text-decoration: none; background: #2980b9; }
.btn-green { background: #27ae60; } .btn-green { background: #27ae60; }
.btn-purple { background: #9b59b6; } .btn-purple { background: #9b59b6; }
code { background: #eee; padding: 2px 6px; border-radius: 3px; } code { background: #eee; padding: 2px 6px; border-radius: 3px; }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; margin:20px 0; } .lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; margin:20px 0; }
.quick-tools { display:flex; gap:10px; flex-wrap:wrap; margin-top:10px; } .quick-tools { display:flex; gap:10px; flex-wrap:wrap; margin-top:10px; }
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:16px; }
</style> </style>
</head> </head>
<body> <body>
@@ -30,7 +30,7 @@
<strong>🧪 学习任务卡</strong> <strong>🧪 学习任务卡</strong>
<ul> <ul>
<li>先点“拦截器演示”,观察执行统计和链路顺序</li> <li>先点“拦截器演示”,观察执行统计和链路顺序</li>
<li>再点“计算器”,故意输入非法值体验 Struts2 验</li> <li>再点“计算器”,故意输入非法值体验 Struts2 验</li>
<li>最后点“用户管理”,理解 Action + JSP + 表单提交流程</li> <li>最后点“用户管理”,理解 Action + JSP + 表单提交流程</li>
</ul> </ul>
<div class="quick-tools"> <div class="quick-tools">
@@ -39,33 +39,31 @@
<a class="btn btn-green" href="user">直接去用户管理实验</a> <a class="btn btn-green" href="user">直接去用户管理实验</a>
</div> </div>
</div> </div>
<div class="card"> <div class="grid">
<h3>🛡️ 拦截器 (核心特性)</h3> <div class="card">
<p>拦截器是 Struts2 最强大的特性之一,基于责任链模式实现 AOP。</p> <h3>👋 最小闭环</h3>
<div> <p>从 <code>/hello</code> 开始,看 Action 如何返回 JSP 页面。</p>
<a class="btn btn-purple" href="interceptor">拦截器演示</a> <a class="btn" href="hello">打开 Hello</a>
</div> </div>
<ul> <div class="card">
<li><a href="interceptor">LoggingInterceptor</a> - 日志记录</li> <h3>🔢 表单与校验</h3>
<li><a href="interceptor">TimingInterceptor</a> - 性能计时</li> <p>用计算器理解参数绑定、校验失败回显、成功结果显示。</p>
<li><a href="interceptor">RateLimitInterceptor</a> - IP 限流</li> <a class="btn" href="calc">打开计算器</a>
<li><a href="interceptor">ValidationInterceptor</a> - 参数验证</li> </div>
<li><a href="interceptor">MonitorInterceptor</a> - 状态监控</li> <div class="card">
</ul> <h3>🛡️ 拦截器机制</h3>
</div> <p>学习 Struts2 最核心的增强机制:日志、计时、限流、监控。</p>
<a class="btn btn-purple" href="interceptor">打开拦截器实验</a>
<div class="card"> </div>
<h3>📝 基础示例</h3> <div class="card">
<div> <h3>👥 CRUD 业务流</h3>
<a class="btn" href="hello">Hello World</a> <p>通过用户管理体验列表页、表单页、重定向和状态更新。</p>
<a class="btn" href="calc">计算器</a> <a class="btn btn-green" href="user">打开用户管理</a>
<a class="btn btn-green" href="user">用户管理</a>
</div> </div>
</div> </div>
<h2>📚 学习路径</h2> <h2>📚 学习路径</h2>
<div class="card"> <div class="card">
<h3>1. MVC 架构</h3> <h3>1. MVC 架构</h3>
<ul> <ul>
@@ -74,7 +72,7 @@
<li><strong>Controller:</strong> StrutsPrepareAndExecuteFilter</li> <li><strong>Controller:</strong> StrutsPrepareAndExecuteFilter</li>
</ul> </ul>
</div> </div>
<div class="card"> <div class="card">
<h3>2. 拦截器机制</h3> <h3>2. 拦截器机制</h3>
<ul> <ul>
@@ -83,30 +81,26 @@
<li>执行顺序: 责任链模式(先入后出)</li> <li>执行顺序: 责任链模式(先入后出)</li>
</ul> </ul>
</div> </div>
<div class="card"> <div class="card">
<h3>3. OGNL 表达式</h3> <h3>3. OGNL 与标签库</h3>
<ul> <ul>
<li><code>&lt;s:property value="name"/&gt;</code> - 输出属性</li> <li><code>&lt;s:property value="name"/&gt;</code> - 输出属性</li>
<li><code>&lt;s:iterator value="list"&gt;</code> - 遍历集合</li> <li><code>&lt;s:iterator value="list"&gt;</code> - 遍历集合</li>
<li><code>#request.key</code> - 访问 request</li> <li><code>&lt;s:form&gt;</code> + <code>&lt;s:textfield&gt;</code> - 表单绑定</li>
</ul> </ul>
</div> </div>
<div class="card"> <div class="card">
<h3>4. 项目结构</h3> <h3>4. 你当前项目里有哪些实验</h3>
<pre> <ul>
├── src/main/java/com/example/struts2/ <li><strong>Hello</strong>最小 Action/JSP 映射</li>
├── action/ # Action 类 <li><strong>Calculator</strong>参数绑定 + 校验 + 错误回显</li>
│ └── interceptor/ # 自定义拦截器 <li><strong>Interceptor</strong>理解自定义拦截器与请求链</li>
├── src/main/resources/ <li><strong>User CRUD</strong>模拟真实业务增删改查</li>
└── struts.xml # 核心配置 </ul>
└── src/main/webapp/
├── WEB-INF/web.xml # Servlet 配置
└── *.jsp # 视图页面
</pre>
</div> </div>
<p><a href="/">← 返回首页</a></p> <p><a href="/">← 返回首页</a></p>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,82 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html>
<html>
<head>
<title>用户表单 - Struts2</title>
<style>
body { font-family: Arial, sans-serif; max-width: 700px; margin: 40px auto; padding: 20px; background:#f7f8fa; }
h1 { color: #e74c3c; }
.form-group { margin: 15px 0; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input { width: 100%; padding: 10px; font-size: 14px; box-sizing: border-box; }
button { background: #3498db; color: white; padding: 12px 24px; border: none; cursor: pointer; margin-top: 10px; border-radius:6px; }
button:hover { background: #2980b9; }
.lab, .tip { background:#fff; padding:15px; border-radius:10px; margin:18px 0; box-shadow:0 4px 14px rgba(0,0,0,.06); }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; }
.quick { display:flex; gap:10px; flex-wrap:wrap; }
.quick button { margin-top:0; }
</style>
</head>
<body>
<h1><s:if test="user.id == null">添加用户</s:if><s:else>编辑用户</s:else></h1>
<div class="lab">
<strong>实验任务卡</strong>
<ul>
<li>试着新增一个完整用户,提交后观察为什么回到列表页</li>
<li>再故意填一个不合理年龄,看当前脚手架是否已经做服务端校验</li>
<li>思考:如果要做更严谨校验,你会把规则放在 Action、拦截器还是 XML/注解里?</li>
</ul>
</div>
<div class="tip">
<strong>快速填充示例</strong>
<div class="quick">
<button type="button" onclick="fillExample('赵六','zhaoliu@example.com',26)">填入正常示例</button>
<button type="button" onclick="fillExample('测试用户','bad-email',-1)">填入异常示例</button>
</div>
</div>
<s:form action="%{user.id == null ? 'user_add' : 'user_update'}" method="post">
<s:hidden name="user.id"/>
<div class="form-group">
<label>姓名:</label>
<s:textfield name="user.name" placeholder="请输入姓名"/>
</div>
<div class="form-group">
<label>邮箱:</label>
<s:textfield name="user.email" placeholder="请输入邮箱"/>
</div>
<div class="form-group">
<label>年龄:</label>
<s:textfield name="user.age" placeholder="请输入年龄"/>
</div>
<button type="submit">保存</button>
</s:form>
<div class="tip">
<strong>学习点</strong>
<ul>
<li>表单字段名 <code>user.name</code> / <code>user.email</code> / <code>user.age</code> 会映射到嵌套对象</li>
<li>当进入编辑页时Action 会先根据 <code>id</code> 找到已有对象并回填</li>
<li>提交成功后通过 <code>redirectAction</code> 返回列表页,避免刷新重复提交</li>
</ul>
</div>
<p><a href="user">← 返回用户列表</a></p>
<script>
function fillExample(name, email, age) {
const inputs = document.querySelectorAll('input[type="text"]');
if (inputs[0]) inputs[0].value = name;
if (inputs[1]) inputs[1].value = email;
if (inputs[2]) inputs[2].value = age;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,80 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html>
<html>
<head>
<title>用户管理 - Struts2 CRUD 示例</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1000px; margin: 40px auto; padding: 20px; background:#f7f8fa; }
h1 { color: #e74c3c; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; background:#fff; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #3498db; color: white; }
tr:nth-child(even) { background: #f9f9f9; }
.btn { padding: 6px 12px; text-decoration: none; border-radius: 4px; margin-right: 5px; display:inline-block; }
.btn-edit { background: #f39c12; color: white; }
.btn-delete { background: #e74c3c; color: white; }
.btn-add { background: #2ecc71; color: white; padding: 10px 20px; }
.tip, .lab { background: #fff; padding: 15px; border-radius: 10px; margin: 20px 0; box-shadow:0 4px 14px rgba(0,0,0,.06); }
.lab { border-left: 4px solid #fa8c16; background:#fff7e6; }
.stats { display:flex; gap:12px; flex-wrap:wrap; margin:15px 0; }
.stat { background:#fff; padding:14px 18px; border-radius:10px; box-shadow:0 4px 14px rgba(0,0,0,.06); min-width:160px; }
.stat strong { display:block; font-size:1.4em; color:#3498db; }
code { background:#f0f0f0; padding:2px 6px; border-radius:4px; }
</style>
</head>
<body>
<h1>👥 用户管理 - Struts2 CRUD 示例</h1>
<div class="lab">
<strong>实验任务卡</strong>
<ul>
<li>先新增一个用户,观察表单提交后为什么会回到列表页</li>
<li>再编辑一个用户,体会 Struts2 如何把已有数据回填到表单</li>
<li>最后删除一个用户,理解 <code>redirectAction</code> 的使用场景</li>
</ul>
</div>
<div class="stats">
<div class="stat"><span>当前用户数</span><strong><s:property value="userList.size()"/></strong></div>
<div class="stat"><span>演示模式</span><strong>内存列表</strong></div>
<div class="stat"><span>学习重点</span><strong>CRUD 流程</strong></div>
</div>
<p><a class="btn btn-add" href="user_edit">+ 添加用户</a></p>
<table>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>年龄</th>
<th>操作</th>
</tr>
<s:iterator value="userList" var="u">
<tr>
<td><s:property value="#u.id"/></td>
<td><s:property value="#u.name"/></td>
<td><s:property value="#u.email"/></td>
<td><s:property value="#u.age"/></td>
<td>
<a class="btn btn-edit" href="user_edit?id=<s:property value='#u.id'/>">编辑</a>
<a class="btn btn-delete" href="user_delete?id=<s:property value='#u.id'/>" onclick="return confirm('确定删除?')">删除</a>
</td>
</tr>
</s:iterator>
</table>
<div class="tip">
<strong>学习点</strong>
<ul>
<li><code>&lt;s:iterator&gt;</code> 遍历集合</li>
<li><code>redirectAction</code> 结果类型实现 PRG 模式</li>
<li>同一个 Action 通过不同 method 处理 list/add/edit/update/delete</li>
<li>这里的数据存在内存里,重启后会恢复初始样本</li>
</ul>
</div>
<p><a href="learn">← 返回学习中心</a></p>
</body>
</html>