feat(interactive): enhance struts learning pages with task cards and quick experiments

This commit is contained in:
likingcode
2026-03-09 23:57:50 +08:00
commit 0bbfdd1d7a
14 changed files with 941 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
package com.example.struts2;
import com.opensymphony.xwork2.ActionSupport;
/**
* 计算器 Action - 表单处理示例
*
* 学习点:
* - Action 接收表单参数(通过 setter
* - 数据验证validate 方法)
* - 结果类型
*/
public class CalculatorAction extends ActionSupport {
private Double num1;
private Double num2;
private String operator;
private Double result;
// 默认执行方法
public String execute() {
return SUCCESS;
}
// 计算方法
public String calculate() {
if (num1 == null || num2 == null || operator == null) {
return INPUT;
}
switch (operator) {
case "+":
result = num1 + num2;
break;
case "-":
result = num1 - num2;
break;
case "*":
result = num1 * num2;
break;
case "/":
if (num2 == 0) {
addFieldError("num2", "除数不能为0");
return INPUT;
}
result = num1 / num2;
break;
default:
return INPUT;
}
return SUCCESS;
}
// 验证方法
@Override
public void validate() {
if (num1 == null) {
addFieldError("num1", "请输入第一个数字");
}
if (num2 == null) {
addFieldError("num2", "请输入第二个数字");
}
}
// Getter and SetterStruts2 通过这些接收参数)
public Double getNum1() { return num1; }
public void setNum1(Double num1) { this.num1 = num1; }
public Double getNum2() { return num2; }
public void setNum2(Double num2) { this.num2 = num2; }
public String getOperator() { return operator; }
public void setOperator(String operator) { this.operator = operator; }
public Double getResult() { return result; }
public void setResult(Double result) { this.result = result; }
}

View File

@@ -0,0 +1,15 @@
package com.example.struts2;
import com.opensymphony.xwork2.ActionSupport;
public class HelloAction extends ActionSupport {
private String message = "Hello from Struts2 Scaffold!";
public String execute() {
return SUCCESS;
}
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,36 @@
package com.example.struts2;
import com.opensymphony.xwork2.ActionSupport;
import com.example.struts2.interceptor.MonitorInterceptor;
import org.apache.struts2.interceptor.RequestAware;
import java.util.Map;
/**
* 拦截器演示 Action
*
* 学习点:
* - 拦截器与 Action 的协作
* - RequestAware 接口获取 request
*/
public class InterceptorDemoAction extends ActionSupport implements RequestAware {
private Map<String, Object> request;
private Map<String, Long> interceptorStats;
private Long executionTime;
public String execute() {
interceptorStats = MonitorInterceptor.getStats();
if (request != null) {
executionTime = (Long) request.get("executionTimeNs");
}
return SUCCESS;
}
@Override
public void setRequest(Map<String, Object> request) {
this.request = request;
}
public Map<String, Long> getInterceptorStats() { return interceptorStats; }
public Long getExecutionTime() { return executionTime; }
}

View File

@@ -0,0 +1,60 @@
package com.example.struts2;
import com.opensymphony.xwork2.ActionSupport;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.interceptor.ServletRequestAware;
import java.util.HashMap;
import java.util.Map;
/**
* 学习示例 Action
*
* 学习点:
* - 获取原始 HttpServletRequest
* - 多种结果类型
* - Action 配置
*/
public class LearnAction extends ActionSupport implements ServletRequestAware {
private HttpServletRequest request;
private Map<String, Object> data;
// 默认方法 - 显示学习菜单
public String execute() {
data = new HashMap<>();
data.put("message", "欢迎学习 Struts2");
data.put("features", new String[]{
"MVC 架构模式",
"拦截器机制",
"OGNL 表达式语言",
"标签库",
"类型转换",
"输入验证"
});
data.put("actions", new String[]{
"hello - 基础示例",
"calculator - 表单处理",
"user - CRUD 操作",
"learn/info - 本页面"
});
return SUCCESS;
}
// 显示请求信息
public String info() {
data = new HashMap<>();
data.put("method", request.getMethod());
data.put("contextPath", request.getContextPath());
data.put("remoteAddr", request.getRemoteAddr());
data.put("header_UserAgent", request.getHeader("User-Agent"));
return SUCCESS;
}
@Override
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
public Map<String, Object> getData() { return data; }
public void setData(Map<String, Object> data) { this.data = data; }
}

View File

@@ -0,0 +1,106 @@
package com.example.struts2;
import com.opensymphony.xwork2.ActionSupport;
import java.util.ArrayList;
import java.util.List;
/**
* 用户管理 Action - CRUD 示例
*
* 学习点:
* - Session 范围数据
* - 多个处理方法
* - 动态方法调用
*/
public class UserFormAction extends ActionSupport {
private static List<User> users = new ArrayList<>();
private static Long idCounter = 1L;
private User user = new User();
private Long id;
private List<User> userList;
static {
users.add(new User(idCounter++, "张三", "zhangsan@example.com", 25));
users.add(new User(idCounter++, "李四", "lisi@example.com", 30));
users.add(new User(idCounter++, "王五", "wangwu@example.com", 28));
}
// 列表
public String list() {
userList = new ArrayList<>(users);
return SUCCESS;
}
// 添加
public String add() {
user.setId(idCounter++);
users.add(user);
return SUCCESS;
}
// 编辑页面
public String edit() {
for (User u : users) {
if (u.getId().equals(id)) {
user = u;
break;
}
}
return SUCCESS;
}
// 更新
public String update() {
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getId().equals(user.getId())) {
users.set(i, user);
break;
}
}
return SUCCESS;
}
// 删除
public String delete() {
users.removeIf(u -> u.getId().equals(id));
return SUCCESS;
}
// Getters and Setters
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public List<User> getUserList() { return userList; }
public void setUserList(List<User> userList) { this.userList = userList; }
// 内部类 - 用户实体
public static class User {
private Long id;
private String name;
private String email;
private Integer age;
public User() {}
public User(Long id, String name, String email, Integer age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
}

View File

@@ -0,0 +1,38 @@
package com.example.struts2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import java.util.Map;
/**
* 日志拦截器 - 演示自定义拦截器
*
* 学习点:
* - AbstractInterceptor 基类
* - intercept() 方法
* - ActionInvocation 对象
* - 拦截器链执行顺序
*/
public class LoggingInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String actionName = invocation.getProxy().getActionName();
String method = invocation.getProxy().getMethod();
System.out.println("[LoggingInterceptor] 进入 Action: " + actionName +
", Method: " + method);
long startTime = System.currentTimeMillis();
// 执行下一个拦截器或 Action
String result = invocation.invoke();
long duration = System.currentTimeMillis() - startTime;
System.out.println("[LoggingInterceptor] 退出 Action: " + actionName +
", Result: " + result + ", 耗时: " + duration + "ms");
return result;
}
}

View File

@@ -0,0 +1,48 @@
package com.example.struts2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.ActionContext;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 拦截器状态监控 - 演示拦截器生命周期
*/
public class MonitorInterceptor implements Interceptor {
private static final Map<String, Long> actionCallCounts = new ConcurrentHashMap<>();
private static long interceptorInitTime;
@Override
public void init() {
interceptorInitTime = System.currentTimeMillis();
System.out.println("[MonitorInterceptor] 初始化 - " + new java.util.Date());
}
@Override
public void destroy() {
System.out.println("[MonitorInterceptor] 销毁 - " + new java.util.Date());
System.out.println("[MonitorInterceptor] 最终统计: " + actionCallCounts);
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String actionName = invocation.getProxy().getActionName();
actionCallCounts.merge(actionName, 1L, Long::sum);
// 通过 invocation 获取 context
ActionContext context = invocation.getInvocationContext();
Map<String, Object> request = (Map<String, Object>) context.get("request");
if (request != null) {
request.put("interceptorInitTime", interceptorInitTime);
request.put("actionCallCounts", new ConcurrentHashMap<>(actionCallCounts));
}
return invocation.invoke();
}
public static Map<String, Long> getStats() {
return new ConcurrentHashMap<>(actionCallCounts);
}
}

View File

@@ -0,0 +1,86 @@
package com.example.struts2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.ActionContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.StrutsStatics;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 限流拦截器 - 演示拦截器实现横切关注点
*
* 学习点:
* - 拦截器实现非功能性需求
* - IP 限流算法
* - 返回自定义结果
*/
public class RateLimitInterceptor extends AbstractInterceptor {
// IP 请求计数器
private final Map<String, AtomicLong> ipRequestCounts = new ConcurrentHashMap<>();
private final Map<String, Long> ipLastAccess = new ConcurrentHashMap<>();
// 每分钟最大请求数
private long maxRequestsPerMinute = 60;
@Override
public String intercept(ActionInvocation invocation) throws Exception {
// 获取客户端 IP
String clientIp = getClientIp(invocation);
long currentTime = System.currentTimeMillis();
Long lastAccess = ipLastAccess.get(clientIp);
// 每分钟重置计数器
if (lastAccess == null || currentTime - lastAccess > 60000) {
ipRequestCounts.put(clientIp, new AtomicLong(0));
ipLastAccess.put(clientIp, currentTime);
}
AtomicLong count = ipRequestCounts.get(clientIp);
long currentCount = count.incrementAndGet();
if (currentCount > maxRequestsPerMinute) {
System.out.println("[RateLimitInterceptor] IP: " + clientIp +
" 超过限流阈值: " + currentCount);
return "rateLimitExceeded";
}
System.out.println("[RateLimitInterceptor] IP: " + clientIp +
" 请求计数: " + currentCount + "/" + maxRequestsPerMinute);
return invocation.invoke();
}
/**
* 获取客户端真实 IP
*/
private String getClientIp(ActionInvocation invocation) {
ActionContext context = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest)
context.get(StrutsStatics.HTTP_REQUEST);
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多个代理时取第一个
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
// Getter/Setter for configuration
public void setMaxRequestsPerMinute(long max) {
this.maxRequestsPerMinute = max;
}
}

View File

@@ -0,0 +1,51 @@
package com.example.struts2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.ActionContext;
import java.util.Map;
/**
* 计时拦截器 - 演示拦截器统计功能
*
* 学习点:
* - 拦截器状态保持
* - ActionContext 获取上下文
* - 多个拦截器协作
*/
public class TimingInterceptor extends AbstractInterceptor {
// 统计数据
private long totalRequests = 0;
private long totalTime = 0;
@Override
public String intercept(ActionInvocation invocation) throws Exception {
totalRequests++;
String actionName = invocation.getProxy().getActionName();
long startTime = System.nanoTime();
// 执行 Action
String result = invocation.invoke();
long duration = System.nanoTime() - startTime;
totalTime += duration;
// 存储到 request 作用域
Map<String, Object> request = (Map<String, Object>)
ActionContext.getContext().get("request");
if (request != null) {
request.put("executionTimeNs", duration);
request.put("totalRequests", totalRequests);
}
System.out.println("[TimingInterceptor] " + actionName +
" 执行时间: " + (duration / 1_000_000.0) + " ms");
return result;
}
public long getTotalRequests() { return totalRequests; }
public long getTotalTime() { return totalTime; }
}

View File

@@ -0,0 +1,44 @@
package com.example.struts2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.ActionContext;
import org.apache.struts2.dispatcher.HttpParameters;
import java.util.Map;
/**
* 参数验证拦截器 - 演示拦截器预处理
*/
public class ValidationInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
System.out.println("[ValidationInterceptor] 开始参数验证");
// 获取参数 (Struts2 6.x 使用 HttpParameters)
HttpParameters params = invocation.getInvocationContext().getParameters();
for (String paramName : params.keySet()) {
String[] values = params.get(paramName).getMultipleValues();
if (values != null) {
for (String value : values) {
if (containsXss(value)) {
System.out.println("[ValidationInterceptor] 检测到可疑参数: " + paramName);
return "invalidInput";
}
}
}
}
System.out.println("[ValidationInterceptor] 参数验证通过");
return invocation.invoke();
}
private boolean containsXss(String value) {
if (value == null) return false;
String lower = value.toLowerCase();
return lower.contains("<script") ||
lower.contains("javascript:") ||
lower.contains("onerror=");
}
}

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,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,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>

112
src/main/webapp/learn.jsp Normal file
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>