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=");
}
}