Update: struts config, new actions and views

This commit is contained in:
likingcode
2026-03-18 15:18:32 +08:00
parent e0afbdc002
commit 077f054e2c
87 changed files with 7883 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"http://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<!-- 开发模式 -->
<constant name="struts.devMode" value="true" />
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<constant name="struts.action.extension" value="action,," />
<package name="default" namespace="/" extends="struts-default">
<!-- ========== 自定义拦截器配置 ========== -->
<interceptors>
<interceptor name="logging" class="com.example.struts2.interceptor.LoggingInterceptor"/>
<interceptor name="timing" class="com.example.struts2.interceptor.TimingInterceptor"/>
<interceptor name="rateLimit" class="com.example.struts2.interceptor.RateLimitInterceptor"/>
<interceptor name="validation" class="com.example.struts2.interceptor.ValidationInterceptor"/>
<interceptor name="monitor" class="com.example.struts2.interceptor.MonitorInterceptor"/>
<interceptor-stack name="customStack">
<interceptor-ref name="logging"/>
<interceptor-ref name="timing"/>
<interceptor-ref name="monitor"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
<interceptor-stack name="apiStack">
<interceptor-ref name="logging"/>
<interceptor-ref name="rateLimit">
<param name="maxRequestsPerMinute">100</param>
</interceptor-ref>
<interceptor-ref name="validation"/>
<interceptor-ref name="timing"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<!-- 默认拦截器栈 -->
<default-interceptor-ref name="customStack"/>
<!-- 默认 Action -->
<default-action-ref name="index" />
<!-- 全局结果 -->
<global-results>
<result name="rateLimitExceeded">/error-rate-limit.jsp</result>
<result name="invalidInput">/error-invalid-input.jsp</result>
</global-results>
<!-- ========== Actions ========== -->
<action name="index">
<result>/index.jsp</result>
</action>
<action name="hello" class="com.example.struts2.HelloAction">
<result>/hello.jsp</result>
</action>
<action name="learn" class="com.example.struts2.LearnAction">
<result>/learn.jsp</result>
</action>
<action name="interceptor" class="com.example.struts2.InterceptorDemoAction">
<result>/interceptor-demo.jsp</result>
</action>
<action name="interceptor_api" class="com.example.struts2.InterceptorDemoAction">
<interceptor-ref name="apiStack"/>
<result>/interceptor-demo.jsp</result>
</action>
<action name="calc" class="com.example.struts2.CalculatorAction">
<result>/calculator.jsp</result>
</action>
<action name="calc_execute" class="com.example.struts2.CalculatorAction" method="calculate">
<result>/calculator.jsp</result>
<result name="input">/calculator.jsp</result>
</action>
<action name="user" class="com.example.struts2.UserFormAction" method="list">
<result>/user-list.jsp</result>
</action>
<action name="user_add" class="com.example.struts2.UserFormAction" method="add">
<result type="redirectAction">user</result>
</action>
<action name="user_edit" class="com.example.struts2.UserFormAction" method="edit">
<result>/user-form.jsp</result>
</action>
<action name="user_update" class="com.example.struts2.UserFormAction" method="update">
<result type="redirectAction">user</result>
</action>
<action name="user_delete" class="com.example.struts2.UserFormAction" method="delete">
<result type="redirectAction">user</result>
</action>
</package>
</struts>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Struts2 Scaffold</display-name>
<!-- Struts2 核心过滤器 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 欢迎页 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

View File

@@ -0,0 +1,85 @@
<%@ 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: 600px; margin: 50px auto; padding: 20px; }
h1 { color: #e74c3c; }
.form-group { margin: 15px 0; }
label { display: inline-block; width: 100px; }
input, select { padding: 8px; font-size: 14px; }
button { background: #3498db; color: white; padding: 10px 20px; border: none; cursor: pointer; }
button:hover { background: #2980b9; }
.result { background: #2ecc71; color: white; padding: 15px; margin: 20px 0; border-radius: 5px; }
.error { color: #e74c3c; font-size: 12px; }
.tip { background: #f9f9f9; padding: 15px; border-left: 4px solid #3498db; margin: 20px 0; }
.quickbar { background:#fff7e6; padding:12px; border-left:4px solid #fa8c16; margin:20px 0; border-radius:6px; }
.quickbar button { margin-right:8px; }
</style>
</head>
<body>
<h1>🔢 计算器 - Struts2 表单示例</h1>
<s:if test="result != null">
<div class="result">
计算结果: <s:property value="num1"/>
<s:property value="operator"/>
<s:property value="num2"/>
= <s:property value="result"/>
</div>
</s:if>
<div class="quickbar">
<strong>快速实验:</strong>
<button type="button" onclick="fillExample(12, '+', 8)">填入正确示例</button>
<button type="button" onclick="fillExample(12, '/', 0)">填入除零错误</button>
<button type="button" onclick="fillExample('', '*', '')">填入校验错误</button>
</div>
<s:form action="calc_execute" method="post">
<div class="form-group">
<label>数字 1:</label>
<s:textfield name="num1" placeholder="输入第一个数字"/>
<s:fielderror fieldName="num1" cssClass="error"/>
</div>
<div class="form-group">
<label>运算符:</label>
<s:select name="operator" list="{'+', '-', '*', '/'}" headerKey="" headerValue="选择运算符"/>
</div>
<div class="form-group">
<label>数字 2:</label>
<s:textfield name="num2" placeholder="输入第二个数字"/>
<s:fielderror fieldName="num2" cssClass="error"/>
</div>
<div class="form-group">
<button type="submit">计算</button>
</div>
</s:form>
<div class="tip">
<strong>学习点:</strong>
<ul>
<li><code>name</code> 属性对应 Action 的 setter 方法</li>
<li><code>&lt;s:fielderror&gt;</code> 显示验证错误</li>
<li><code>validate()</code> 方法进行数据验证</li>
</ul>
</div>
<p><a href="learn">← 返回学习中心</a></p>
<script>
function fillExample(n1, op, n2) {
const fields = document.querySelectorAll('input[type="text"]');
const select = document.querySelector('select');
if (fields[0]) fields[0].value = n1;
if (select) select.value = op;
if (fields[1]) fields[1].value = n2;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<title>参数无效</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 100px auto; padding: 20px; text-align: center; }
h1 { color: #e74c3c; }
.error-box { background: #fdeaea; border: 1px solid #e74c3c; padding: 30px; border-radius: 10px; }
.info { color: #7f8c8d; margin-top: 20px; }
</style>
</head>
<body>
<div class="error-box">
<h1>🚫 检测到无效参数</h1>
<p>您的请求包含可疑内容,已被拦截。</p>
<p class="info">此页面由 ValidationInterceptor 拦截器生成</p>
</div>
<p><a href="learn">← 返回学习中心</a></p>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<title>请求过于频繁</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 100px auto; padding: 20px; text-align: center; }
h1 { color: #e74c3c; }
.error-box { background: #fdeaea; border: 1px solid #e74c3c; padding: 30px; border-radius: 10px; }
.info { color: #7f8c8d; margin-top: 20px; }
</style>
</head>
<body>
<div class="error-box">
<h1>⚠️ 请求过于频繁</h1>
<p>您的请求次数超过限制,请稍后再试。</p>
<p class="info">此页面由 RateLimitInterceptor 拦截器生成</p>
</div>
<p><a href="learn">← 返回学习中心</a></p>
</body>
</html>

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head><title>Struts2 Scaffold</title></head>
<body>
<h1>${message}</h1>
<p>Struts2 脚手架部署中...</p>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<title>Struts2 Scaffold</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; text-align: center; }
h1 { color: #e74c3c; font-size: 2.5em; }
.links { margin: 30px 0; }
.links a { display: inline-block; background: #3498db; color: white; padding: 15px 30px; margin: 10px; text-decoration: none; border-radius: 5px; }
.links a:hover { background: #2980b9; }
.links a.learn { background: #2ecc71; }
.links a.learn:hover { background: #27ae60; }
.info { color: #7f8c8d; margin-top: 40px; }
</style>
</head>
<body>
<h1>🚀 Struts2 Scaffold</h1>
<p>Struts2 学习脚手架已启动!</p>
<div class="links">
<a href="learn" class="learn">📚 学习中心</a>
</div>
<div class="links">
<a href="hello">Hello 示例</a>
<a href="calc">计算器</a>
<a href="user">用户管理</a>
</div>
<p class="info">Powered by Struts2 + Jetty</p>
</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</title>
<style>
body { font-family: Arial, sans-serif; max-width: 900px; margin: 50px auto; padding: 20px; }
h1 { color: #e74c3c; }
h2 { color: #3498db; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
.card { background: #f9f9f9; padding: 20px; margin: 15px 0; border-radius: 8px; }
.card h3 { margin-top: 0; color: #2c3e50; }
code { background: #eee; padding: 2px 6px; border-radius: 3px; }
pre { background: #2c3e50; color: #ecf0f1; padding: 15px; border-radius: 5px; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #3498db; color: white; }
.time { color: #27ae60; font-weight: bold; }
.api-link { display: inline-block; background: #9b59b6; color: white; padding: 10px 20px;
text-decoration: none; border-radius: 5px; margin: 5px; }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; margin:20px 0; }
</style>
</head>
<body>
<h1>🛡️ Struts2 拦截器演示</h1>
<div class="lab">
<strong>实验观察点:</strong>
<ul>
<li>连续刷新页面,观察 Action 调用次数如何累积</li>
<li>切换到“API 限流栈”,理解为什么拦截器顺序会影响结果</li>
<li>注意执行时间显示,这就是拦截器注入到 request 的数据</li>
</ul>
</div>
<div class="card">
<h3>📊 执行统计</h3>
<p>本次请求执行时间: <span class="time"><s:property value="executionTime / 1000000.0"/> ms</span></p>
<h4>Action 调用统计:</h4>
<table>
<tr><th>Action</th><th>调用次数</th></tr>
<s:iterator value="interceptorStats" var="entry">
<tr>
<td><s:property value="#entry.key"/></td>
<td><s:property value="#entry.value"/></td>
</tr>
</s:iterator>
</table>
</div>
<div class="card">
<h3>🔗 拦截器链演示</h3>
<p>当前使用: <code>customStack</code> (日志 + 计时 + 监控 + 默认栈)</p>
<a class="api-link" href="interceptor_api">使用 API 限流栈</a>
<p>API 栈包含: 日志 + 限流 + 验证 + 计时 + 默认栈</p>
</div>
<div class="card">
<h3>📚 拦截器执行顺序</h3>
<pre>
请求 → LoggingInterceptor → TimingInterceptor → MonitorInterceptor → Action
响应 ← LoggingInterceptor ← TimingInterceptor ← MonitorInterceptor ← Result
</pre>
<p><strong>注意:</strong> 拦截器像洋葱一样,先进入的后退出</p>
</div>
<div class="card">
<h3>💡 拦截器核心概念</h3>
<ul>
<li><strong>Interceptor 接口:</strong> init() → intercept() → destroy()</li>
<li><strong>intercept() 方法:</strong> 调用 invocation.invoke() 继续链</li>
<li><strong>拦截器栈:</strong> 多个拦截器按顺序组成链</li>
<li><strong>责任链模式:</strong> 每个拦截器决定是否继续</li>
</ul>
</div>
<p><a href="learn">← 返回学习中心</a></p>
</body>
</html>

View File

@@ -0,0 +1,112 @@
<%@ 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: 900px; margin: 50px auto; padding: 20px; }
h1 { color: #e74c3c; }
h2 { color: #3498db; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
.card { background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0; }
.card h3 { margin-top: 0; }
ul { line-height: 2; }
a { color: #3498db; text-decoration: none; }
a:hover { text-decoration: underline; }
.btn { display: inline-block; background: #3498db; color: white; padding: 10px 20px;
border-radius: 5px; margin: 5px; }
.btn:hover { text-decoration: none; background: #2980b9; }
.btn-green { background: #27ae60; }
.btn-purple { background: #9b59b6; }
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; }
.quick-tools { display:flex; gap:10px; flex-wrap:wrap; margin-top:10px; }
</style>
</head>
<body>
<h1>🎓 Struts2 学习中心</h1>
<div class="lab">
<strong>🧪 学习任务卡</strong>
<ul>
<li>先点“拦截器演示”,观察执行统计和链路顺序</li>
<li>再点“计算器”,故意输入非法值体验 Struts2 验证</li>
<li>最后点“用户管理”,理解 Action + JSP + 表单提交流程</li>
</ul>
<div class="quick-tools">
<a class="btn btn-purple" href="interceptor">直接去拦截器实验</a>
<a class="btn" href="calc">直接去计算器实验</a>
<a class="btn btn-green" href="user">直接去用户管理实验</a>
</div>
</div>
<div class="card">
<h3>🛡️ 拦截器 (核心特性)</h3>
<p>拦截器是 Struts2 最强大的特性之一,基于责任链模式实现 AOP。</p>
<div>
<a class="btn btn-purple" href="interceptor">拦截器演示</a>
</div>
<ul>
<li><a href="interceptor">LoggingInterceptor</a> - 日志记录</li>
<li><a href="interceptor">TimingInterceptor</a> - 性能计时</li>
<li><a href="interceptor">RateLimitInterceptor</a> - IP 限流</li>
<li><a href="interceptor">ValidationInterceptor</a> - 参数验证</li>
<li><a href="interceptor">MonitorInterceptor</a> - 状态监控</li>
</ul>
</div>
<div class="card">
<h3>📝 基础示例</h3>
<div>
<a class="btn" href="hello">Hello World</a>
<a class="btn" href="calc">计算器</a>
<a class="btn btn-green" href="user">用户管理</a>
</div>
</div>
<h2>📚 学习路径</h2>
<div class="card">
<h3>1. MVC 架构</h3>
<ul>
<li><strong>Model:</strong> Action 类 + JavaBean</li>
<li><strong>View:</strong> JSP + Struts 标签库</li>
<li><strong>Controller:</strong> StrutsPrepareAndExecuteFilter</li>
</ul>
</div>
<div class="card">
<h3>2. 拦截器机制</h3>
<ul>
<li><code>Interceptor</code> 接口: init() → intercept() → destroy()</li>
<li><code>interceptor-stack</code>: 组合多个拦截器</li>
<li>执行顺序: 责任链模式(先入后出)</li>
</ul>
</div>
<div class="card">
<h3>3. OGNL 表达式</h3>
<ul>
<li><code>&lt;s:property value="name"/&gt;</code> - 输出属性</li>
<li><code>&lt;s:iterator value="list"&gt;</code> - 遍历集合</li>
<li><code>#request.key</code> - 访问 request</li>
</ul>
</div>
<div class="card">
<h3>4. 项目结构</h3>
<pre>
├── src/main/java/com/example/struts2/
│ ├── action/ # Action 类
│ └── interceptor/ # 自定义拦截器
├── src/main/resources/
│ └── struts.xml # 核心配置
└── src/main/webapp/
├── WEB-INF/web.xml # Servlet 配置
└── *.jsp # 视图页面
</pre>
</div>
<p><a href="/">← 返回首页</a></p>
</body>
</html>

View File

@@ -0,0 +1,43 @@
<%@ 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: 500px; margin: 50px auto; padding: 20px; }
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; }
button:hover { background: #2980b9; }
</style>
</head>
<body>
<h1><s:if test="user.id == null">添加用户</s:if><s:else>编辑用户</s:else></h1>
<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>
<p><a href="user">← 返回用户列表</a></p>
</body>
</html>

View File

@@ -0,0 +1,59 @@
<%@ 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: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #e74c3c; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #3498db; color: white; }
tr:nth-child(even) { background: #f9f9f9; }
.btn { padding: 5px 10px; text-decoration: none; border-radius: 3px; margin-right: 5px; }
.btn-edit { background: #f39c12; color: white; }
.btn-delete { background: #e74c3c; color: white; }
.btn-add { background: #2ecc71; color: white; padding: 10px 20px; }
.tip { background: #f9f9f9; padding: 15px; border-left: 4px solid #3498db; margin: 20px 0; }
</style>
</head>
<body>
<h1>👥 用户管理 - Struts2 CRUD 示例</h1>
<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>动态方法调用处理不同操作</li>
</ul>
</div>
<p><a href="learn">← 返回学习中心</a></p>
</body>
</html>