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=");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user