feat(interactive): enhance struts learning pages with task cards and quick experiments
This commit is contained in:
77
src/main/java/com/example/struts2/CalculatorAction.java
Normal file
77
src/main/java/com/example/struts2/CalculatorAction.java
Normal 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 Setter(Struts2 通过这些接收参数)
|
||||
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; }
|
||||
}
|
||||
15
src/main/java/com/example/struts2/HelloAction.java
Normal file
15
src/main/java/com/example/struts2/HelloAction.java
Normal 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;
|
||||
}
|
||||
}
|
||||
36
src/main/java/com/example/struts2/InterceptorDemoAction.java
Normal file
36
src/main/java/com/example/struts2/InterceptorDemoAction.java
Normal 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; }
|
||||
}
|
||||
60
src/main/java/com/example/struts2/LearnAction.java
Normal file
60
src/main/java/com/example/struts2/LearnAction.java
Normal 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; }
|
||||
}
|
||||
106
src/main/java/com/example/struts2/UserFormAction.java
Normal file
106
src/main/java/com/example/struts2/UserFormAction.java
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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=");
|
||||
}
|
||||
}
|
||||
103
src/main/resources/struts.xml
Normal file
103
src/main/resources/struts.xml
Normal 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>
|
||||
85
src/main/webapp/calculator.jsp
Normal file
85
src/main/webapp/calculator.jsp
Normal 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><s:fielderror></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>
|
||||
80
src/main/webapp/interceptor-demo.jsp
Normal file
80
src/main/webapp/interceptor-demo.jsp
Normal 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
112
src/main/webapp/learn.jsp
Normal 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><s:property value="name"/></code> - 输出属性</li>
|
||||
<li><code><s:iterator value="list"></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>
|
||||
Reference in New Issue
Block a user