diff --git a/web/WEB-INF/classes/com/demo/action/AjaxAction.java b/web/WEB-INF/classes/com/demo/action/AjaxAction.java new file mode 100644 index 0000000..fb82bd9 --- /dev/null +++ b/web/WEB-INF/classes/com/demo/action/AjaxAction.java @@ -0,0 +1,48 @@ +package com.demo.action; + +import com.demo.model.User; +import com.opensymphony.xwork2.ActionSupport; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; + +public class AjaxAction extends ActionSupport { + + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private boolean success; + private String message; + private String serverTime; + private List users; + + @Override + public String execute() { + success = true; + message = "Mock AJAX response generated from the Struts2 action."; + serverTime = LocalDateTime.now().format(TIME_FORMATTER); + users = Arrays.asList( + new User(1L, "ops-admin", "ops-admin@example.com", "13800000001"), + new User(2L, "platform-owner", "platform-owner@example.com", "13800000002"), + new User(3L, "release-manager", "release-manager@example.com", "13800000003") + ); + return SUCCESS; + } + + public boolean isSuccess() { + return success; + } + + public String getMessage() { + return message; + } + + public String getServerTime() { + return serverTime; + } + + public List getUsers() { + return users; + } +} diff --git a/web/WEB-INF/classes/com/demo/action/FileUploadAction.java b/web/WEB-INF/classes/com/demo/action/FileUploadAction.java new file mode 100644 index 0000000..130fd46 --- /dev/null +++ b/web/WEB-INF/classes/com/demo/action/FileUploadAction.java @@ -0,0 +1,93 @@ +package com.demo.action; + +import com.opensymphony.xwork2.ActionSupport; + +import java.io.File; +import java.util.List; + +public class FileUploadAction extends ActionSupport { + + private File upload; + private String uploadFileName; + private String uploadContentType; + private List uploads; + private List uploadsFileName; + private List uploadsContentType; + private int fileCount; + private String summary; + + @Override + public String execute() { + fileCount = 0; + if (upload != null) { + fileCount++; + } + if (uploads != null) { + fileCount += uploads.size(); + } + + if (fileCount == 0) { + addActionError("Select at least one file before submitting the demo."); + return INPUT; + } + + summary = "This demo captures upload metadata only. It does not persist files to disk, which keeps the sample safe for classroom use."; + return SUCCESS; + } + + public File getUpload() { + return upload; + } + + public void setUpload(File upload) { + this.upload = upload; + } + + public String getUploadFileName() { + return uploadFileName; + } + + public void setUploadFileName(String uploadFileName) { + this.uploadFileName = uploadFileName; + } + + public String getUploadContentType() { + return uploadContentType; + } + + public void setUploadContentType(String uploadContentType) { + this.uploadContentType = uploadContentType; + } + + public List getUploads() { + return uploads; + } + + public void setUploads(List uploads) { + this.uploads = uploads; + } + + public List getUploadsFileName() { + return uploadsFileName; + } + + public void setUploadsFileName(List uploadsFileName) { + this.uploadsFileName = uploadsFileName; + } + + public List getUploadsContentType() { + return uploadsContentType; + } + + public void setUploadsContentType(List uploadsContentType) { + this.uploadsContentType = uploadsContentType; + } + + public int getFileCount() { + return fileCount; + } + + public String getSummary() { + return summary; + } +} diff --git a/web/WEB-INF/classes/com/demo/action/HelloAction.java b/web/WEB-INF/classes/com/demo/action/HelloAction.java index 2e805f8..4e56025 100644 --- a/web/WEB-INF/classes/com/demo/action/HelloAction.java +++ b/web/WEB-INF/classes/com/demo/action/HelloAction.java @@ -2,33 +2,50 @@ package com.demo.action; import com.opensymphony.xwork2.ActionSupport; -/** - * Hello World 示例 Action - * 展示 Struts2 最基础的 Action 写法 - * - * ActionSupport 提供了: - * - validate() 方法用于表单验证 - * - getText() 方法用于国际化 - * - addActionError()/addFieldError() 用于错误消息 - */ public class HelloAction extends ActionSupport { - + private String name; private String message; - + private String tip; + private String nextStep; + private String requestSample; + @Override - public String execute() throws Exception { - // 业务逻辑 + public String execute() { if (name == null || name.trim().isEmpty()) { name = "World"; + } else { + name = name.trim(); } - message = "Hello, " + name + "! 欢迎学习 Struts2!"; + + message = "Hello, " + name + "! Welcome to the Struts2 demo lab."; + tip = "Struts2 injected the request parameter into the action property before execute() ran."; + nextStep = "Try the login flow or open the demo catalog to compare different action patterns."; + requestSample = "/hello?name=Platform%20Team"; return SUCCESS; } - - // Getter/Setter (Struts2 需要这些来获取/设置参数) - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public String getMessage() { return message; } - public void setMessage(String message) { this.message = message; } -} \ No newline at end of file + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMessage() { + return message; + } + + public String getTip() { + return tip; + } + + public String getNextStep() { + return nextStep; + } + + public String getRequestSample() { + return requestSample; + } +} diff --git a/web/WEB-INF/classes/com/demo/action/LoginAction.java b/web/WEB-INF/classes/com/demo/action/LoginAction.java index 4822b92..701dc9f 100644 --- a/web/WEB-INF/classes/com/demo/action/LoginAction.java +++ b/web/WEB-INF/classes/com/demo/action/LoginAction.java @@ -2,46 +2,92 @@ package com.demo.action; import com.opensymphony.xwork2.ActionSupport; -/** - * 用户登录 Action - * 展示: - * - 表单参数接收 - * - 数据验证 - * - Session 使用 - * - 返回不同结果 - */ +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + public class LoginAction extends ActionSupport { - + + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private String username; private String password; - + private String displayName; + private String role; + private String loginTime; + private String recommendation; + @Override - public String execute() throws Exception { - // 模拟登录验证 - if ("admin".equals(username) && "123456".equals(password)) { - // 登录成功,设置 session - return SUCCESS; - } else if (username != null && !username.isEmpty()) { - // 登录失败 - addActionError("用户名或密码错误!"); + public String execute() { + username = normalize(username); + password = normalize(password); + + if (!hasSubmitted()) { return INPUT; } + + if ("admin".equals(username) && "123456".equals(password)) { + displayName = "System Demo Admin"; + role = "Administrator"; + loginTime = LocalDateTime.now().format(TIME_FORMATTER); + recommendation = "Continue with the user form, validation sample, or upload flow to explore the rest of the demo."; + return SUCCESS; + } + + addActionError("Invalid demo credentials. Use admin / 123456."); return INPUT; } - - // 表单验证 + @Override public void validate() { - if (username == null || username.trim().length() < 3) { - addFieldError("username", "用户名至少3个字符"); + if (!hasSubmitted()) { + return; + } + if (username == null || username.length() < 3) { + addFieldError("username", "Username must be at least 3 characters."); } if (password == null || password.length() < 6) { - addFieldError("password", "密码至少6个字符"); + addFieldError("password", "Password must be at least 6 characters."); } } - - public String getUsername() { return username; } - public void setUsername(String username) { this.username = username; } - public String getPassword() { return password; } - public void setPassword(String password) { this.password = password; } -} \ No newline at end of file + + private boolean hasSubmitted() { + return (username != null && !username.trim().isEmpty()) + || (password != null && !password.trim().isEmpty()); + } + + private String normalize(String value) { + return value == null ? null : value.trim(); + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getDisplayName() { + return displayName; + } + + public String getRole() { + return role; + } + + public String getLoginTime() { + return loginTime; + } + + public String getRecommendation() { + return recommendation; + } +} diff --git a/web/WEB-INF/classes/com/demo/action/UserAction.java b/web/WEB-INF/classes/com/demo/action/UserAction.java new file mode 100644 index 0000000..0a11bee --- /dev/null +++ b/web/WEB-INF/classes/com/demo/action/UserAction.java @@ -0,0 +1,84 @@ +package com.demo.action; + +import com.opensymphony.xwork2.ActionSupport; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class UserAction extends ActionSupport { + + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private String username; + private String email; + private String phone; + private String submittedAt; + private String profileStage; + + public String submit() { + username = normalize(username); + email = normalize(email); + phone = normalize(phone); + + if (!isValid()) { + return INPUT; + } + + submittedAt = LocalDateTime.now().format(TIME_FORMATTER); + profileStage = (phone != null && phone.length() >= 7) ? "Profile ready for follow-up demos." : "Profile captured. Add a stronger phone number next time."; + return SUCCESS; + } + + private boolean isValid() { + boolean valid = true; + if (username == null || username.length() < 3) { + addFieldError("username", "Username must be at least 3 characters."); + valid = false; + } + if (email == null || !email.contains("@")) { + addFieldError("email", "Enter a valid email address."); + valid = false; + } + if (phone == null || phone.replaceAll("[^0-9]", "").length() < 7) { + addFieldError("phone", "Enter at least 7 digits for the phone number."); + valid = false; + } + return valid; + } + + private String normalize(String value) { + return value == null ? null : value.trim(); + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getSubmittedAt() { + return submittedAt; + } + + public String getProfileStage() { + return profileStage; + } +} diff --git a/web/WEB-INF/classes/com/demo/action/ValidationAction.java b/web/WEB-INF/classes/com/demo/action/ValidationAction.java new file mode 100644 index 0000000..5823814 --- /dev/null +++ b/web/WEB-INF/classes/com/demo/action/ValidationAction.java @@ -0,0 +1,88 @@ +package com.demo.action; + +import com.opensymphony.xwork2.ActionSupport; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class ValidationAction extends ActionSupport { + + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private String username; + private String email; + private Integer age; + private String bio; + private String scoreBand; + private String submittedAt; + + @Override + public String execute() { + username = normalize(username); + email = normalize(email); + bio = normalize(bio); + scoreBand = age >= 30 ? "Mid-career operator profile" : "Early-career operator profile"; + submittedAt = LocalDateTime.now().format(TIME_FORMATTER); + return SUCCESS; + } + + @Override + public void validate() { + if (username == null || username.trim().length() < 3 || username.trim().length() > 20) { + addFieldError("username", "Username must be between 3 and 20 characters."); + } + if (email == null || !email.contains("@") || email.indexOf('@') == email.length() - 1) { + addFieldError("email", "Enter a valid email address."); + } + if (age == null || age < 18 || age > 60) { + addFieldError("age", "Age must be between 18 and 60."); + } + if (bio != null && bio.trim().length() > 240) { + addFieldError("bio", "Bio must stay under 240 characters."); + } + } + + private String normalize(String value) { + return value == null ? null : value.trim(); + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + 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; + } + + public String getBio() { + return bio; + } + + public void setBio(String bio) { + this.bio = bio; + } + + public String getScoreBand() { + return scoreBand; + } + + public String getSubmittedAt() { + return submittedAt; + } +} diff --git a/web/WEB-INF/classes/com/demo/action/rest/UserRestAction.java b/web/WEB-INF/classes/com/demo/action/rest/UserRestAction.java new file mode 100644 index 0000000..a614679 --- /dev/null +++ b/web/WEB-INF/classes/com/demo/action/rest/UserRestAction.java @@ -0,0 +1,38 @@ +package com.demo.action.rest; + +import com.demo.model.User; +import com.opensymphony.xwork2.ActionSupport; + +import java.util.Arrays; +import java.util.List; + +public class UserRestAction extends ActionSupport { + + private boolean success; + private String message; + private List users; + + @Override + public String execute() { + success = true; + message = "REST-style demo payload"; + users = Arrays.asList( + new User(101L, "alpha", "alpha@example.com", "13800001001"), + new User(102L, "beta", "beta@example.com", "13800001002"), + new User(103L, "gamma", "gamma@example.com", "13800001003") + ); + return SUCCESS; + } + + public boolean isSuccess() { + return success; + } + + public String getMessage() { + return message; + } + + public List getUsers() { + return users; + } +} diff --git a/web/WEB-INF/classes/com/demo/model/User.java b/web/WEB-INF/classes/com/demo/model/User.java index 1d30de2..6f5d945 100644 --- a/web/WEB-INF/classes/com/demo/model/User.java +++ b/web/WEB-INF/classes/com/demo/model/User.java @@ -1,30 +1,54 @@ package com.demo.model; -/** - * 用户模型 - * 展示 Struts2 的 ModelDriven 和 Bean 封装 - */ public class User { private Long id; private String username; private String email; private String phone; - - public User() {} - + + public User() { + } + public User(Long id, String username, String email) { + this(id, username, email, null); + } + + public User(Long id, String username, String email, String phone) { this.id = id; this.username = username; this.email = email; + this.phone = phone; } - - // Getters and Setters - public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - public String getUsername() { return username; } - public void setUsername(String username) { this.username = username; } - public String getEmail() { return email; } - public void setEmail(String email) { this.email = email; } - public String getPhone() { return phone; } - public void setPhone(String phone) { this.phone = phone; } -} \ No newline at end of file + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } +} diff --git a/web/WEB-INF/classes/struts.xml b/web/WEB-INF/classes/struts.xml index d64c786..538cb35 100644 --- a/web/WEB-INF/classes/struts.xml +++ b/web/WEB-INF/classes/struts.xml @@ -4,62 +4,47 @@ "http://struts.apache.org/dtds/struts-2.5.dtd"> - - - - - - /index.jsp - - + /hello.jsp - - + /user/success.jsp /user/login.jsp - - + /user/success.jsp /user/form.jsp - - + /upload/success.jsp /upload/index.jsp - - + - - + /validation/success.jsp /validation/form.jsp - - - + - + - - \ No newline at end of file + diff --git a/web/WEB-INF/web.xml b/web/WEB-INF/web.xml index 958916e..469f923 100644 --- a/web/WEB-INF/web.xml +++ b/web/WEB-INF/web.xml @@ -5,14 +5,13 @@ http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> - Struts2 学习 Demo + Struts2 Demo Lab - struts2 org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter - + struts2 /* @@ -21,8 +20,7 @@ index.jsp - - + 404 /error/404.jsp @@ -31,4 +29,4 @@ 500 /error/500.jsp - \ No newline at end of file + diff --git a/web/demo/ajax/index.jsp b/web/demo/ajax/index.jsp index ef9a9d8..841ce1a 100644 --- a/web/demo/ajax/index.jsp +++ b/web/demo/ajax/index.jsp @@ -1,101 +1,51 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> - + - AJAX + JSON - Struts2 学习 + + AJAX and JSON Guide -
- -
-

⚡ AJAX + JSON

- -

1. JSON 结果类型

-
-

使用 struts2-json-plugin 实现 AJAX 返回 JSON

-
// struts.xml 配置
-<action name="getUser" class="com.demo.UserAction">
-    <result type="json">
-        <!-- 可选: 只包含指定属性 -->
-        <param name="includeProperties">user\..*,success</param>
-    </result>
-</action>
-
- -

2. Action 示例

-
-
public class UserAction extends ActionSupport {
-    
-    private User user;
-    private boolean success;
-    private String message;
-    
-    // getter 是必须的,JSON 插件只序列化有 getter 的属性
-    public User getUser() { return user; }
-    public boolean isSuccess() { return success; }
-    public String getMessage() { return message; }
-    
-    public String getData() {
-        user = new User("张三", "zhangsan@email.com");
-        success = true;
-        message = "获取成功";
-        return SUCCESS;
+    
+
Guide
+

Returning JSON from Struts2 actions

+

The demo now includes both an action-level JSON response and a REST-style JSON route. That makes it easier to compare MVC pages with lightweight API payloads.

+ +

What the action returns

+
    +
  • A success flag.
  • +
  • A short message describing the payload.
  • +
  • Sample user records that can be consumed by AJAX.
  • +
+ +
public class AjaxAction extends ActionSupport {
+    private boolean success;
+    private String message;
+    private List<User> users;
+
+    public String execute() {
+        success = true;
+        message = "Mock AJAX response";
+        return SUCCESS;
     }
 }
-
- -

3. 前端 AJAX 调用

-
-
// 原生 fetch
-fetch('/getUser')
-    .then(r => r.json())
-    .then(data => {
-        console.log(data.user);
-        console.log(data.success);
-    });
 
-// jQuery
-$.getJSON('/getUser', function(data) {
-    $('#username').text(data.user.username);
-});
-
- -

4. 完整示例:实时搜索

-
-
// HTML
-<input type="text" id="keyword" onkeyup="search(this.value)"/>
-<div id="results"></div>
-
-// JS
-function search(keyword) {
-    $.getJSON('/search', {keyword: keyword}, function(data) {
-        $('#results').empty();
-        data.results.forEach(function(item) {
-            $('#results').append('<div>' + item.name + '</div>');
-        });
-    });
-}
-
- - 下一节:拦截器 → +
- \ No newline at end of file + diff --git a/web/demo/hello/index.jsp b/web/demo/hello/index.jsp index 966aa76..4c06856 100644 --- a/web/demo/hello/index.jsp +++ b/web/demo/hello/index.jsp @@ -1,168 +1,53 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> - + - Hello World - Struts2 学习 + Hello Action Guide -
- - -
-

👋 Hello World - 第一个 Struts2 应用

- -
- 📝 本节要点: 创建最简单的 Struts2 应用,掌握 Action 编写和 struts.xml 配置 -
- -

1. 项目结构

-
-webapp/
-├── WEB-INF/
-│   ├── web.xml
-│   └── lib/
-│       └── struts2-core.jar
-├── struts.xml          ← Action 配置
-├── index.jsp
-└── hello.jsp           ← 结果页面
- -

2. web.xml 配置

-
-
<?xml version="1.0" encoding="UTF-8"?>
-<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee">
-    
-    <!-- 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>
-    
-</web-app>
-
- -

3. struts.xml 配置

-
-
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE struts PUBLIC
-    "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
-    "http://struts.apache.org/dtds/struts-2.5.dtd">
+    
+
Guide
+

Hello action walkthrough

+

The hello demo is still the best first stop for explaining how Struts2 maps a request to an action and then routes to a JSP result.

-<struts> - <!-- 开发模式 --> - <constant name="struts.devMode" value="true"/> - - <!-- 定义包 --> - <package name="default" namespace="/" extends="struts-default"> - - <!-- Action 配置 --> - <action name="hello" class="com.demo.HelloAction"> - <result>/hello.jsp</result> - </action> - - </package> -</struts>
-
- -

4. HelloAction.java

-
-
package com.demo;
+        

Flow

+
    +
  • The browser requests /hello or /hello?name=Team.
  • +
  • Struts populates the name property on HelloAction.
  • +
  • execute() builds a view message and returns SUCCESS.
  • +
  • The framework resolves the success result to /hello.jsp.
  • +
-import com.opensymphony.xwork2.ActionSupport; +

Minimal action shape

+
public class HelloAction extends ActionSupport {
+    private String name;
+    private String message;
 
-/**
- * 第一个 Struts2 Action
- * 继承 ActionSupport 可以获得验证、国际化的能力
- */
-public class HelloAction extends ActionSupport {
-    
-    // 接收参数
-    private String name;
-    
-    // 返回给页面的数据
-    private String message;
-    
-    /**
-     * 执行方法,默认调用
-     */
-    @Override
-    public String execute() throws Exception {
-        if (name == null || name.trim().isEmpty()) {
-            name = "Struts2 学习者";
+    public String execute() {
+        if (name == null || name.trim().isEmpty()) {
+            name = "World";
         }
-        message = "你好, " + name + "! 欢迎学习 Struts2!";
-        return SUCCESS;  // 返回 "success"
+        message = "Hello, " + name + "!";
+        return SUCCESS;
     }
-    
-    // Getter/Setter 必须提供,Struts2 通过反射注入参数
-    public String getName() { return name; }
-    public void setName(String name) { this.name = name; }
-    
-    public String getMessage() { return message; }
-    public void setMessage(String message) { this.message = message; }
 }
-
- -

5. hello.jsp 结果页面

-
-
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-<%@ taglib prefix="s" uri="/struts-tags" %>
-<!DOCTYPE html>
-<html>
-<body>
-    <!-- 使用 Struts2 标签获取 Action 返回的值 -->
-    <h1><s:property value="message"/></h1>
-</body>
-</html>
-
- -

6. 访问方式

-
-

启动应用后,访问:

- http://localhost:8080/hello - 默认执行
- http://localhost:8080/hello?name=张三 - 传递参数 -
- -

执行流程图

-
-
-浏览器请求 → Struts2 Filter → ActionProxy → HelloAction.execute()
-    → 返回 "success" → 配置的 result → hello.jsp → 浏览器
-
- - + +
- \ No newline at end of file + diff --git a/web/demo/model/index.jsp b/web/demo/model/index.jsp index ebb30eb..71f0e77 100644 --- a/web/demo/model/index.jsp +++ b/web/demo/model/index.jsp @@ -1,173 +1,54 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> - + - 数据封装 - Struts2 学习 + Model Binding Guide -
- - -
-

📦 数据封装 - 属性驱动 vs 模型驱动

- -
- 📝 本节要点: 掌握 Struts2 两种数据封装方式,将请求参数封装到 Java 对象 -
- -

1. 两种封装方式对比

-
- - - - - - - - - - - - - - - - -
方式实现适用场景
属性驱动Action 中定义属性 + getter/setter简单场景,属性较少
模型驱动实现 ModelDriven 接口复杂对象,表单字段多
-
- -

2. 方式一:属性驱动 (Property Driven)

- -

2.1 基本属性封装

-
-
/**
- * 直接在 Action 中定义属性
- * 表单: <input name="username"> 会自动调用 setUsername()
- */
-public class UserAction extends ActionSupport {
-    
-    // Struts2 自动调用 setter 注入参数
-    private String username;
-    private String email;
-    private Integer age;
-    
-    @Override
-    public String execute() {
-        System.out.println(username + " - " + email + " - " + age);
-        return SUCCESS;
-    }
-    
-    // 必须提供 setter 和 getter
-    public String getUsername() { return username; }
-    public void setUsername(String username) { this.username = username; }
-    
-    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; }
-}
-
- -

2.2 复杂属性 (嵌套对象)

-
-

表单中的 user.username 会自动调用 setUser(用户对象).setUsername()

-
// Action
-private User user;  // 包含 username, email 属性的对象
+    
+
Guide
+

Property binding vs model-driven binding

+

This note helps explain how Struts2 turns request parameters into action state. It works well alongside the user form and validation examples.

-// 表单 -<input name="user.username"/> -<input name="user.email"/>
-
- -

3. 方式二:模型驱动 (ModelDriven)

-
- 💡 推荐: 对于复杂表单,使用模型驱动更清晰,Action 和 Model 分离 -
- -
-
import com.opensymphony.xwork2.ModelDriven;
+        

Property-driven

+
    +
  • Define simple fields directly on the action.
  • +
  • Expose setters and getters for each form field.
  • +
  • Great for short demos and small forms.
  • +
-/** - * 实现 ModelDriven 接口 - * 需要: - * 1. 实现 getModel() 方法 - * 2. 泛型指定模型类型 - */ -public class UserAction extends ActionSupport implements ModelDriven<User> { - - // 模型对象 - private User user = new User(); - - /** - * 返回模型对象 - * Struts2 会把参数注入到返回的对象中 - */ - @Override - public User getModel() { - return user; - } - - @Override - public String execute() { - // 直接使用 user 对象 - System.out.println(user.getUsername()); - return SUCCESS; +

Model-driven

+
    +
  • Return a dedicated model object from getModel().
  • +
  • Keep request data separate from action orchestration logic.
  • +
  • Better for larger forms and nested objects.
  • +
+ +
public class UserAction extends ActionSupport implements ModelDriven<User> {
+    private final User user = new User();
+
+    @Override
+    public User getModel() {
+        return user;
     }
 }
-
- -

4. List/Map 属性封装

-
-
// Action - 接收 List
-private List<User> users;
-public void setUsers(List<User> users) { this.users = users; }
-public List<User> getUsers() { return users; }
 
-// 页面表单 - users[0].username, users[1].username
-<input name="users[0].username" value="张三"/>
-<input name="users[1].username" value="李四"/>
-
-// Action - 接收 Map
-private Map<String, User> userMap;
-<input name="userMap['one'].username"/>
-
- -

5. 封装流程图

-
-
-请求参数 → Action  setter 方法 → 属性赋值
-                ↓
-      实现 ModelDriven → getModel() → 模型对象赋值
-                ↓
-             值栈 (Value Stack)
-
- - +
- \ No newline at end of file + diff --git a/web/demo/upload/index.jsp b/web/demo/upload/index.jsp index a32e483..148b0b6 100644 --- a/web/demo/upload/index.jsp +++ b/web/demo/upload/index.jsp @@ -1,102 +1,50 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> - + - 文件上传 - Struts2 学习 + + Upload Guide -
- -
-

📁 文件上传

- -

1. 单文件上传

-
-
// Action
-public class UploadAction extends ActionSupport {
-    
-    // 文件对象
-    private File upload;
-    
-    // 文件名 (格式: 属性名FileName)
-    private String uploadFileName;
-    
-    // 文件类型 (格式: 属性名ContentType)
-    private String uploadContentType;
-    
-    @Override
-    public String execute() throws Exception {
-        // 保存文件
-        String path = ServletActionContext.getServletContext()
-            .getRealPath("/upload");
-        new File(path, uploadFileName).createNewFile();
-        
-        // 复制文件
-        FileUtils.copyFile(upload, new File(path, uploadFileName));
-        
-        return SUCCESS;
-    }
-    
-    // getter/setter
-    public File getUpload() { return upload; }
-    public void setUpload(File upload) { this.upload = upload; }
-    public String getUploadFileName() { return uploadFileName; }
-    public void setUploadFileName(String uploadFileName) { this.uploadFileName = uploadFileName; }
-    public String getUploadContentType() { return uploadContentType; }
-    public void setUploadContentType(String uploadContentType) { this.uploadContentType = uploadContentType; }
-}
-
- -

2. JSP 表单

-
-
<%@ taglib prefix="s" uri="/struts-tags" %>
-<s:form action="upload" method="post" enctype="multipart/form-data">
-    <s:file name="upload" label="选择文件"/>
-    <s:submit value="上传"/>
-</s:form>
-
- -

3. 多文件上传

-
-
// 使用 List 接收多文件
-private List<File> uploads;
-private List<String> uploadsFileName;
+    
+
Guide
+

Upload demo guide

+

The refreshed upload flow is deliberately safer for demo use. It shows multipart binding and metadata capture without persisting files.

-// 表单 -<s:file name="uploads" multiple="multiple"/>
-
- -

4. 限制文件大小和类型

-
-
// struts.xml 配置
-<action name="upload" class="com.demo.UploadAction">
-    <interceptor-ref name="fileUpload">
-        <param name="maximumSize">10485760</param><!-- 10MB -->
-        <param name="allowedTypes">image/png,image/jpeg</param>
-    </interceptor-ref>
-    <result>/upload/success.jsp</result>
-</action>
-
- - 下一节:AJAX → +

Core binding fields

+
    +
  • File upload for the primary file object.
  • +
  • String uploadFileName for the original filename.
  • +
  • String uploadContentType for the MIME type.
  • +
  • Optional lists for multiple files.
  • +
+ +
public class FileUploadAction extends ActionSupport {
+    private File upload;
+    private String uploadFileName;
+    private String uploadContentType;
+
+    public String execute() {
+        // demo keeps metadata only
+        return SUCCESS;
+    }
+}
+ +
- \ No newline at end of file + diff --git a/web/error/404.jsp b/web/error/404.jsp index bd3cf2d..77b28e8 100644 --- a/web/error/404.jsp +++ b/web/error/404.jsp @@ -1,21 +1,47 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> - + - 404 - 页面未找到 + + + 404 - Struts2 Demo Lab

404

-

页面未找到

-

返回首页

+

The requested demo page was not found. Use the portal to jump back into the working Struts2 examples.

+ Back to portal
- \ No newline at end of file + diff --git a/web/error/500.jsp b/web/error/500.jsp new file mode 100644 index 0000000..f5b7ae3 --- /dev/null +++ b/web/error/500.jsp @@ -0,0 +1,47 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + + 500 - Struts2 Demo Lab + + + +
+

500

+

The demo hit an internal error. Return to the portal and try one of the working examples again.

+ Back to portal +
+ + diff --git a/web/hello.jsp b/web/hello.jsp index 50f4681..3f1782d 100644 --- a/web/hello.jsp +++ b/web/hello.jsp @@ -1,47 +1,124 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> - + - Hello - Struts2 Demo + + + Hello Action - Struts2 Demo Lab
-

-

Struts2 Action 执行成功!

- 返回首页 +
Hello Action
+

+

This page proves the classic request -> action -> result flow. It accepts a name parameter, prepares a response in the action, and renders it through the JSP view.

+ +
+
+

What just happened

+

+
+
+

Good next step

+

+
+
+

Try another request

+

Use a custom query parameter to change the greeting.

+ +
+
+

Current input

+

Name value:

+
+
+ +
- \ No newline at end of file + diff --git a/web/index.jsp b/web/index.jsp index a0ac5a6..a761227 100644 --- a/web/index.jsp +++ b/web/index.jsp @@ -1,552 +1,403 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> - + - Struts2 全栈学习平台 + Struts2 Demo Lab -
-
- - -
-
- -
-
-

从零掌握 Struts2 框架

-

完整的 Struts2 学习路径 | 视频教程 | 代码示例 | 实战项目

-
- - -
-

📈 学习路径

-
-
-

第一阶段

- 环境搭建 + 基础 -
-
-

第二阶段

- 核心组件 -
-
-

第三阶段

- 数据处理 -
-
-

第四阶段

- 企业应用 -
-
-
- - -
- - -
-
-
👋
-

1. Hello World

-
-

Struts2 入门最简单的示例,掌握基本的 Action 配置和参数传递。

-
    -
  • Action 编写
  • -
  • struts.xml 配置
  • -
  • 结果视图映射
  • -
- 入门 - 开始学习 -
- - -
-
-
-

2. 表单与验证

-
-

深入理解 Struts2 的两种验证方式:编程式验证和声明式验证。

-
    -
  • validate() 方法
  • -
  • XML 验证文件
  • -
  • 字段级验证
  • -
- 入门 - 开始学习 -
- - -
-
-
📦
-

3. 数据封装

-
-

掌握 Struts2 的数据封装机制,包括属性驱动和模型驱动。

-
    -
  • 属性驱动
  • -
  • 模型驱动 (ModelDriven)
  • -
  • 复杂对象封装
  • -
- 进阶 - 开始学习 -
- - -
-
-
📁
-

4. 文件上传

-
-

Struts2 文件上传功能,支持单文件和多文件上传。

-
    -
  • File 对象接收
  • -
  • 文件类型验证
  • -
  • 多文件上传
  • -
- 进阶 - 开始学习 -
- - -
-
-
🔄
-

5. 类型转换器

-
-

自定义类型转换器,处理特殊数据类型和复杂对象。

-
    -
  • TypeConverter
  • -
  • StrutsTypeConverter
  • -
  • 局部/全局转换器
  • -
- 进阶 - 开始学习 -
- - -
-
-
-

6. AJAX + JSON

-
-

使用 Struts2 JSON 插件实现异步数据交互。

-
    -
  • JSON 结果类型
  • -
  • AJAX 表单提交
  • -
  • 局部页面刷新
  • -
- 进阶 - 开始学习 -
- - -
-
-
🛡️
-

7. 拦截器

-
-

理解拦截器机制,这是 Struts2 框架的核心灵魂。

-
    -
  • 自定义拦截器
  • -
  • 拦截器栈
  • -
  • 方法过滤拦截器
  • -
- 进阶 - 开始学习 -
- - -
-
-
📊
-

8. OGNL 表达式

-
-

掌握 Struts2 的表达式语言,操作对象图和集合。

-
    -
  • 基础语法
  • -
  • 集合操作
  • -
  • 上下文对象
  • -
- 进阶 - 开始学习 -
- - -
-
-
🔐
-

9. 作用域对象

-
-

掌握 ActionContext 和值栈的操作。

-
    -
  • Session 管理
  • -
  • Request/Response
  • -
  • 值栈操作
  • -
- 进阶 - 开始学习 -
- - -
-
-
🌐
-

10. RESTful

-
-

使用 Struts2 实现 RESTful 风格的 API 设计。

-
    -
  • REST 插件
  • -
  • URL 参数映射
  • -
  • 内容协商
  • -
- 专家 - 开始学习 -
- - -
-
-
🌍
-

11. 国际化 (i18n)

-
-

实现多语言支持的应用程序。

-
    -
  • 资源文件配置
  • -
  • getText() 使用
  • -
  • 动态语言切换
  • -
- 进阶 - 开始学习 -
- - -
-
-
🔍
-

12. 源码解析

-
-

深入理解 Struts2 框架的内部实现原理。

-
    -
  • 请求处理流程
  • -
  • 拦截器链源码
  • -
  • OGNL 原理
  • -
- 专家 - 开始学习 -
- -
- - -
-

⚙️ 配置文件示例

-
+
+
+
-

struts.xml 基础配置

-
-<?xml version="1.0"?> -<struts> - <!-- 开发模式 --> - <constant name="struts.devMode" value="true"/> - - <!-- 包定义 --> - <package name="default" namespace="/" extends="struts-default"> - - <action name="hello" class="com.demo.HelloAction"> - <result>/hello.jsp</result> - </action> - - </package> -</struts> -
+
Struts2 Demo Lab
+

Turn scattered examples into a guided demo portal

+

This workspace now acts like a mini learning application instead of a pile of sample pages. You can jump into action basics, login flow, validation, uploads, and JSON demos from one place.

-
-

Action 基础写法

-
-public class HelloAction extends ActionSupport { - - private String name; - - @Override - public String execute() { - return SUCCESS; - } - - // getter/setter - public String getName() { return name; } - public void setName(String name) { this.name = name; } -} -
+
-
- - -
-

💼 实战项目

-
-
-

📝 博客系统

-

完整的博客系统,包含文章管理、评论、分类等功能。

- 入门 - 查看详情 -
-
-

📋 待办事项

-

Todo 列表应用,练习 CRUD 操作和 AJAX。

- 入门 - 查看详情 -
-
-

🛒 商城购物车

-

购物车功能,练习 Session 和订单管理。

- 进阶 - 查看详情 -
+
+
Live action routes5
+
Guide pages4
+
Demo forms3
+
JSON endpoints2
+
+ +
+ + +
+
+
Catalog
+

Interactive demos and guides

+
+
+
+ Action + Beginner +
+

Hello action

+

Run a real Struts action, inject a request parameter, and inspect the rendered result page.

+ +
+ +
+
+ Form + Core flow +
+

Login flow

+

Use demo credentials, see field validation, and land on a richer post-login dashboard.

+ +
+ +
+
+ Action + Form submit +
+

User intake form

+

Submit a small profile payload and see action-backed validation with a success summary.

+ +
+ +
+
+ Validation + Teaching +
+

Validation demo

+

Test length, email, age, and text limits while keeping the page readable for explanation.

+ +
+ +
+
+ Upload + Safe demo +
+

Upload metadata demo

+

Capture file metadata through Struts form binding without writing anything to disk.

+ +
+ +
+
+ JSON + API style +
+

AJAX and REST payloads

+

Show how Struts2 can return JSON from actions for AJAX examples and lightweight REST-style demos.

+ +
+ +
+
+ Guide + Patterns +
+

Model binding guide

+

Compare property-driven and model-driven input binding to explain how Struts builds action state.

+ +
+
+ +
+ +
+
Learning pipeline
+

Suggested order for the full demo

+
+
+ 1. Start with hello +

See the smallest action and result mapping first.

+
+
+ 2. Move to forms +

Login and user intake show parameter binding in action.

+
+
+ 3. Validate inputs +

Explain field errors, business rules, and success pages.

+
+
+ 4. Expand to JSON +

Close the session with AJAX and REST-style payload demos.

+
+
+
+
- -
- - + + - \ No newline at end of file + diff --git a/web/upload/index.jsp b/web/upload/index.jsp index 1133b85..351c101 100644 --- a/web/upload/index.jsp +++ b/web/upload/index.jsp @@ -1,87 +1,117 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> - + - 文件上传 - Struts2 Demo + + Upload Demo - Struts2 Demo Lab -
-
-

📁 文件上传

- -
- Struts2 文件上传原理: -
    -
  • 使用 File 类型接收文件
  • -
  • 使用 String fileName 获取原始文件名
  • -
  • 使用 String contentType 获取文件类型
  • -
  • 底层使用 commons-fileupload
  • -
+
+
Upload demo
+

Capture file metadata without writing anything to disk

+

This safer upload sample focuses on how Struts binds multipart form fields. The action records metadata only, which makes the demo easier to run in constrained environments.

+ +
+ What the action collects +

Primary filename, content type, and total selected file count.

+
+ + +
+
+ + +
+ +
- - -
- - -
- -
- - -
- - -
- -

- ← 返回首页 -

+ +
+ + +
+ + +
+ +
- \ No newline at end of file + diff --git a/web/upload/success.jsp b/web/upload/success.jsp index 90eb272..4ed97ca 100644 --- a/web/upload/success.jsp +++ b/web/upload/success.jsp @@ -1,39 +1,92 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib prefix="s" uri="/struts-tags" %> - + - 操作成功 + + + Upload Summary - Struts2 Demo Lab
-

✅ 操作成功!

- 返回首页 +
Upload summary
+

Upload metadata captured successfully

+

+ +
+
+ Selected files + +
+
+ Primary filename + +
+
+ Content type + +
+
+ +
- \ No newline at end of file + diff --git a/web/user/form.jsp b/web/user/form.jsp index bb5bfc8..e140ae0 100644 --- a/web/user/form.jsp +++ b/web/user/form.jsp @@ -1,58 +1,102 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> - + - 用户表单 - Struts2 Demo + + + User Form - Struts2 Demo Lab -
-
-

👤 用户信息提交

- -
- - -
-
- - -
-
- - -
- -
+
+
User action
+

Capture a user profile through Struts binding

+

This form now demonstrates a more realistic user intake flow with field errors and a structured success screen.

+ + +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ + +
+ +
- \ No newline at end of file + diff --git a/web/user/login.jsp b/web/user/login.jsp index 1e223aa..678831b 100644 --- a/web/user/login.jsp +++ b/web/user/login.jsp @@ -1,94 +1,176 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> - + - 用户登录 - Struts2 Demo + + Login Demo - Struts2 Demo Lab -