Update: web.xml and generated files
This commit is contained in:
34
web/WEB-INF/classes/com/demo/action/HelloAction.java
Normal file
34
web/WEB-INF/classes/com/demo/action/HelloAction.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String execute() throws Exception {
|
||||||
|
// 业务逻辑
|
||||||
|
if (name == null || name.trim().isEmpty()) {
|
||||||
|
name = "World";
|
||||||
|
}
|
||||||
|
message = "Hello, " + name + "! 欢迎学习 Struts2!";
|
||||||
|
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; }
|
||||||
|
}
|
||||||
47
web/WEB-INF/classes/com/demo/action/LoginAction.java
Normal file
47
web/WEB-INF/classes/com/demo/action/LoginAction.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package com.demo.action;
|
||||||
|
|
||||||
|
import com.opensymphony.xwork2.ActionSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录 Action
|
||||||
|
* 展示:
|
||||||
|
* - 表单参数接收
|
||||||
|
* - 数据验证
|
||||||
|
* - Session 使用
|
||||||
|
* - 返回不同结果
|
||||||
|
*/
|
||||||
|
public class LoginAction extends ActionSupport {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String execute() throws Exception {
|
||||||
|
// 模拟登录验证
|
||||||
|
if ("admin".equals(username) && "123456".equals(password)) {
|
||||||
|
// 登录成功,设置 session
|
||||||
|
return SUCCESS;
|
||||||
|
} else if (username != null && !username.isEmpty()) {
|
||||||
|
// 登录失败
|
||||||
|
addActionError("用户名或密码错误!");
|
||||||
|
return INPUT;
|
||||||
|
}
|
||||||
|
return INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
@Override
|
||||||
|
public void validate() {
|
||||||
|
if (username == null || username.trim().length() < 3) {
|
||||||
|
addFieldError("username", "用户名至少3个字符");
|
||||||
|
}
|
||||||
|
if (password == null || password.length() < 6) {
|
||||||
|
addFieldError("password", "密码至少6个字符");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
30
web/WEB-INF/classes/com/demo/model/User.java
Normal file
30
web/WEB-INF/classes/com/demo/model/User.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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(Long id, String username, String email) {
|
||||||
|
this.id = id;
|
||||||
|
this.username = username;
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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; }
|
||||||
|
}
|
||||||
65
web/WEB-INF/classes/struts.xml
Normal file
65
web/WEB-INF/classes/struts.xml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<struts>
|
||||||
|
<!-- 开发模式 - 修改配置后自动reload -->
|
||||||
|
<constant name="struts.devMode" value="true"/>
|
||||||
|
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
|
||||||
|
|
||||||
|
<!-- 编码设置 -->
|
||||||
|
<constant name="struts.i18n.encoding" value="UTF-8"/>
|
||||||
|
|
||||||
|
<!-- ==================== 示例包定义 ==================== -->
|
||||||
|
<package name="default" namespace="/" extends="struts-default">
|
||||||
|
|
||||||
|
<!-- 首页 -->
|
||||||
|
<action name="index">
|
||||||
|
<result>/index.jsp</result>
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<!-- Hello World 示例 -->
|
||||||
|
<action name="hello" class="com.demo.action.HelloAction" method="execute">
|
||||||
|
<result>/hello.jsp</result>
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<!-- 用户登录示例 -->
|
||||||
|
<action name="login" class="com.demo.action.LoginAction" method="execute">
|
||||||
|
<result name="success">/user/success.jsp</result>
|
||||||
|
<result name="input">/user/login.jsp</result>
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<!-- 用户表单提交 -->
|
||||||
|
<action name="submitUser" class="com.demo.action.UserAction" method="submit">
|
||||||
|
<result name="success">/user/success.jsp</result>
|
||||||
|
<result name="input">/user/form.jsp</result>
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<!-- 文件上传示例 -->
|
||||||
|
<action name="upload" class="com.demo.action.FileUploadAction" method="execute">
|
||||||
|
<result name="success">/upload/success.jsp</result>
|
||||||
|
<result name="input">/upload/index.jsp</result>
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<!-- AJAX 示例 -->
|
||||||
|
<action name="ajax" class="com.demo.action.AjaxAction" method="execute">
|
||||||
|
<result type="json"/>
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<!-- 验证示例 -->
|
||||||
|
<action name="validate" class="com.demo.action.ValidationAction" method="execute">
|
||||||
|
<result name="success">/validation/success.jsp</result>
|
||||||
|
<result name="input">/validation/form.jsp</result>
|
||||||
|
</action>
|
||||||
|
|
||||||
|
</package>
|
||||||
|
|
||||||
|
<!-- REST 风格示例 -->
|
||||||
|
<package name="rest" namespace="/api" extends="struts-default">
|
||||||
|
<action name="users" class="com.demo.action.rest.UserRestAction">
|
||||||
|
<result type="json"/>
|
||||||
|
</action>
|
||||||
|
</package>
|
||||||
|
|
||||||
|
</struts>
|
||||||
@@ -1,11 +1,34 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<web-app>
|
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
|
||||||
|
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
|
||||||
|
version="4.0">
|
||||||
|
|
||||||
|
<display-name>Struts2 学习 Demo</display-name>
|
||||||
|
|
||||||
|
<!-- Struts2 核心过滤器 -->
|
||||||
<filter>
|
<filter>
|
||||||
<filter-name>struts2</filter-name>
|
<filter-name>struts2</filter-name>
|
||||||
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
|
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
||||||
<filter-mapping>
|
<filter-mapping>
|
||||||
<filter-name>struts2</filter-name>
|
<filter-name>struts2</filter-name>
|
||||||
<url-pattern>/*</url-pattern>
|
<url-pattern>/*</url-pattern>
|
||||||
</filter-mapping>
|
</filter-mapping>
|
||||||
</web-app>
|
|
||||||
|
<welcome-file-list>
|
||||||
|
<welcome-file>index.jsp</welcome-file>
|
||||||
|
</welcome-file-list>
|
||||||
|
|
||||||
|
<!-- 错误页面配置 -->
|
||||||
|
<error-page>
|
||||||
|
<error-code>404</error-code>
|
||||||
|
<location>/error/404.jsp</location>
|
||||||
|
</error-page>
|
||||||
|
<error-page>
|
||||||
|
<error-code>500</error-code>
|
||||||
|
<location>/error/500.jsp</location>
|
||||||
|
</error-page>
|
||||||
|
</web-app>
|
||||||
101
web/demo/ajax/index.jsp
Normal file
101
web/demo/ajax/index.jsp
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>AJAX + JSON - Struts2 学习</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea, #764ba2); min-height: 100vh; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
.breadcrumb { background: white; padding: 15px 25px; border-radius: 10px; margin-bottom: 20px; }
|
||||||
|
.content { background: white; border-radius: 20px; padding: 40px; }
|
||||||
|
h1 { color: #667eea; border-bottom: 3px solid #667eea; padding-bottom: 15px; }
|
||||||
|
h2 { color: #764ba2; margin-top: 30px; }
|
||||||
|
.section { margin: 25px 0; padding: 20px; background: #f8f9fa; border-radius: 10px; }
|
||||||
|
pre { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; overflow-x: auto; }
|
||||||
|
.keyword { color: #569cd6; }
|
||||||
|
.string { color: #ce9178; }
|
||||||
|
.comment { color: #6a9955; }
|
||||||
|
.btn { display: inline-block; padding: 12px 30px; background: #667eea; color: white; text-decoration: none; border-radius: 25px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<a href="/">🏠 首页</a> / AJAX + JSON
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h1>⚡ AJAX + JSON</h1>
|
||||||
|
|
||||||
|
<h2>1. JSON 结果类型</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>使用 struts2-json-plugin 实现 AJAX 返回 JSON</p>
|
||||||
|
<pre><span class="comment">// struts.xml 配置</span>
|
||||||
|
<span class="keyword"><action</span> name=<span class="string">"getUser"</span> class=<span class="string">"com.demo.UserAction"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><result</span> type=<span class="string">"json"</span><span class="keyword">></span>
|
||||||
|
<span class="comment"><!-- 可选: 只包含指定属性 --></span>
|
||||||
|
<span class="keyword"><param</span> name=<span class="string">"includeProperties"</span><span class="keyword">></span>user\..*,success<span class="keyword"></param></span>
|
||||||
|
<span class="keyword"></result></span>
|
||||||
|
<span class="keyword"></action></span></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>2. Action 示例</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="keyword">public class</span> UserAction <span class="keyword">extends</span> ActionSupport {
|
||||||
|
|
||||||
|
<span class="keyword">private</span> User user;
|
||||||
|
<span class="keyword">private</span> boolean success;
|
||||||
|
<span class="keyword">private</span> String message;
|
||||||
|
|
||||||
|
<span class="comment">// getter 是必须的,JSON 插件只序列化有 getter 的属性</span>
|
||||||
|
<span class="keyword">public</span> User getUser() { <span class="keyword">return</span> user; }
|
||||||
|
<span class="keyword">public</span> boolean isSuccess() { <span class="keyword">return</span> success; }
|
||||||
|
<span class="keyword">public</span> String getMessage() { <span class="keyword">return</span> message; }
|
||||||
|
|
||||||
|
<span class="keyword">public</span> String getData() {
|
||||||
|
user = <span class="keyword">new</span> User(<span class="string">"张三"</span>, <span class="string">"zhangsan@email.com"</span>);
|
||||||
|
success = <span class="keyword">true</span>;
|
||||||
|
message = <span class="string">"获取成功"</span>;
|
||||||
|
<span class="keyword">return</span> SUCCESS;
|
||||||
|
}
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>3. 前端 AJAX 调用</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment">// 原生 fetch</span>
|
||||||
|
fetch(<span class="string">'/getUser'</span>)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data.user);
|
||||||
|
console.log(data.success);
|
||||||
|
});
|
||||||
|
|
||||||
|
<span class="comment">// jQuery</span>
|
||||||
|
$.getJSON(<span class="string">'/getUser'</span>, <span class="keyword">function</span>(data) {
|
||||||
|
$(<span class="string">'#username'</span>).text(data.user.username);
|
||||||
|
});</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>4. 完整示例:实时搜索</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment">// HTML</span>
|
||||||
|
<span class="keyword"><input</span> type=<span class="string">"text"</span> id=<span class="string">"keyword"</span> onkeyup=<span class="string">"search(this.value)"</span>/>
|
||||||
|
<span class="keyword"><div</span> id=<span class="string">"results"</span><span class="keyword">></div></span>
|
||||||
|
|
||||||
|
<span class="comment">// JS</span>
|
||||||
|
<span class="keyword">function</span> search(keyword) {
|
||||||
|
$.getJSON(<span class="string">'/search'</span>, {keyword: keyword}, <span class="keyword">function</span>(data) {
|
||||||
|
$(<span class="string">'#results'</span>).empty();
|
||||||
|
data.results.forEach(<span class="keyword">function</span>(item) {
|
||||||
|
$(<span class="string">'#results'</span>).append(<span class="string">'<div>'</span> + item.name + <span class="string">'</div>'</span>);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/demo/interceptor" class="btn">下一节:拦截器 →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
168
web/demo/hello/index.jsp
Normal file
168
web/demo/hello/index.jsp
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Hello World - Struts2 学习</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', 'PingFang SC', sans-serif; background: linear-gradient(135deg, #667eea, #764ba2); min-height: 100vh; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
.breadcrumb { background: white; padding: 15px 25px; border-radius: 10px; margin-bottom: 20px; }
|
||||||
|
.breadcrumb a { color: #667eea; text-decoration: none; }
|
||||||
|
.content { background: white; border-radius: 20px; padding: 40px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
|
||||||
|
h1 { color: #667eea; border-bottom: 3px solid #667eea; padding-bottom: 15px; }
|
||||||
|
h2 { color: #764ba2; margin-top: 30px; }
|
||||||
|
.section { margin: 25px 0; padding: 20px; background: #f8f9fa; border-radius: 10px; }
|
||||||
|
code { background: #1e1e1e; color: #d4d4d4; padding: 2px 8px; border-radius: 4px; font-family: Consolas, monospace; }
|
||||||
|
pre { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; overflow-x: auto; font-size: 0.9em; }
|
||||||
|
.keyword { color: #569cd6; }
|
||||||
|
.string { color: #ce9178; }
|
||||||
|
.comment { color: #6a9955; }
|
||||||
|
.btn { display: inline-block; padding: 12px 30px; background: #667eea; color: white; text-decoration: none; border-radius: 25px; margin-right: 10px; }
|
||||||
|
.btn:hover { background: #764ba2; }
|
||||||
|
.note { background: #fff3e0; border-left: 4px solid #ff9800; padding: 15px; margin: 20px 0; border-radius: 0 10px 10px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<a href="/">🏠 首页</a> / Hello World
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>👋 Hello World - 第一个 Struts2 应用</h1>
|
||||||
|
|
||||||
|
<div class="note">
|
||||||
|
<strong>📝 本节要点:</strong> 创建最简单的 Struts2 应用,掌握 Action 编写和 struts.xml 配置
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>1. 项目结构</h2>
|
||||||
|
<pre>
|
||||||
|
webapp/
|
||||||
|
├── WEB-INF/
|
||||||
|
│ ├── web.xml
|
||||||
|
│ └── lib/
|
||||||
|
│ └── struts2-core.jar
|
||||||
|
├── struts.xml ← Action 配置
|
||||||
|
├── index.jsp
|
||||||
|
└── hello.jsp ← 结果页面</pre>
|
||||||
|
|
||||||
|
<h2>2. web.xml 配置</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment"><?xml version="1.0" encoding="UTF-8"?></span>
|
||||||
|
<span class="keyword"><web-app</span> xmlns=<span class="string">"http://xmlns.jcp.org/xml/ns/javaee"</span><span class="keyword">></span>
|
||||||
|
|
||||||
|
<span class="comment"><!-- Struts2 核心过滤器 --></span>
|
||||||
|
<span class="keyword"><filter></span>
|
||||||
|
<span class="keyword"><filter-name></span>struts2<span class="keyword"></filter-name></span>
|
||||||
|
<span class="keyword"><filter-class></span>
|
||||||
|
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
|
||||||
|
<span class="keyword"></filter-class></span>
|
||||||
|
<span class="keyword"></filter></span>
|
||||||
|
|
||||||
|
<span class="keyword"><filter-mapping></span>
|
||||||
|
<span class="keyword"><filter-name></span>struts2<span class="keyword"></filter-name></span>
|
||||||
|
<span class="keyword"><url-pattern></span>/*<span class="keyword"></url-pattern></span>
|
||||||
|
<span class="keyword"></filter-mapping></span>
|
||||||
|
|
||||||
|
<span class="keyword"></web-app></span></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>3. struts.xml 配置</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment"><?xml version="1.0" encoding="UTF-8"?></span>
|
||||||
|
<span class="keyword"><!DOCTYPE struts PUBLIC</span>
|
||||||
|
<span class="string">"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"</span>
|
||||||
|
<span class="string">"http://struts.apache.org/dtds/struts-2.5.dtd"</span><span class="keyword">></span>
|
||||||
|
|
||||||
|
<span class="keyword"><struts></span>
|
||||||
|
<span class="comment"><!-- 开发模式 --></span>
|
||||||
|
<span class="keyword"><constant</span> name=<span class="string">"struts.devMode"</span> value=<span class="string">"true"</span><span class="keyword">/></span>
|
||||||
|
|
||||||
|
<span class="comment"><!-- 定义包 --></span>
|
||||||
|
<span class="keyword"><package</span> name=<span class="string">"default"</span> namespace=<span class="string">"/"</span> extends=<span class="string">"struts-default"</span><span class="keyword">></span>
|
||||||
|
|
||||||
|
<span class="comment"><!-- Action 配置 --></span>
|
||||||
|
<span class="keyword"><action</span> name=<span class="string">"hello"</span> class=<span class="string">"com.demo.HelloAction"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><result></span>/hello.jsp<span class="keyword"></result></span>
|
||||||
|
<span class="keyword"></action></span>
|
||||||
|
|
||||||
|
<span class="keyword"></package></span>
|
||||||
|
<span class="keyword"></struts></span></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>4. HelloAction.java</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="keyword">package</span> com.demo;
|
||||||
|
|
||||||
|
<span class="keyword">import</span> com.opensymphony.xwork2.ActionSupport;
|
||||||
|
|
||||||
|
<span class="comment">/**
|
||||||
|
* 第一个 Struts2 Action
|
||||||
|
* 继承 ActionSupport 可以获得验证、国际化的能力
|
||||||
|
*/</span>
|
||||||
|
<span class="keyword">public class</span> HelloAction <span class="keyword">extends</span> ActionSupport {
|
||||||
|
|
||||||
|
<span class="comment">// 接收参数</span>
|
||||||
|
<span class="keyword">private</span> String name;
|
||||||
|
|
||||||
|
<span class="comment">// 返回给页面的数据</span>
|
||||||
|
<span class="keyword">private</span> String message;
|
||||||
|
|
||||||
|
<span class="comment">/**
|
||||||
|
* 执行方法,默认调用
|
||||||
|
*/</span>
|
||||||
|
<span class="annotation">@Override</span>
|
||||||
|
<span class="keyword">public</span> String execute() <span class="keyword">throws</span> Exception {
|
||||||
|
<span class="keyword">if</span> (name == <span class="keyword">null</span> || name.trim().isEmpty()) {
|
||||||
|
name = <span class="string">"Struts2 学习者"</span>;
|
||||||
|
}
|
||||||
|
message = <span class="string">"你好, "</span> + name + <span class="string">"! 欢迎学习 Struts2!"</span>;
|
||||||
|
<span class="keyword">return</span> SUCCESS; <span class="comment">// 返回 "success"</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="comment">// Getter/Setter 必须提供,Struts2 通过反射注入参数</span>
|
||||||
|
<span class="keyword">public</span> String getName() { <span class="keyword">return</span> name; }
|
||||||
|
<span class="keyword">public void</span> setName(String name) { <span class="keyword">this</span>.name = name; }
|
||||||
|
|
||||||
|
<span class="keyword">public</span> String getMessage() { <span class="keyword">return</span> message; }
|
||||||
|
<span class="keyword">public void</span> setMessage(String message) { <span class="keyword">this</span>.message = message; }
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>5. hello.jsp 结果页面</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment"><%@ page contentType="text/html;charset=UTF-8" language="java" %></span>
|
||||||
|
<span class="comment"><%@ taglib prefix="s" uri="/struts-tags" %></span>
|
||||||
|
<span class="keyword"><!DOCTYPE html></span>
|
||||||
|
<span class="keyword"><html></span>
|
||||||
|
<span class="keyword"><body></span>
|
||||||
|
<span class="comment"><!-- 使用 Struts2 标签获取 Action 返回的值 --></span>
|
||||||
|
<span class="keyword"><h1><s:property</span> value=<span class="string">"message"</span>/></h1>
|
||||||
|
<span class="keyword"></body></span>
|
||||||
|
<span class="keyword"></html></span></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>6. 访问方式</h2>
|
||||||
|
<div class="section">
|
||||||
|
<p>启动应用后,访问:</p>
|
||||||
|
<code>http://localhost:8080/hello</code> - 默认执行<br>
|
||||||
|
<code>http://localhost:8080/hello?name=张三</code> - 传递参数
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>执行流程图</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre style="text-align: center;">
|
||||||
|
浏览器请求 → Struts2 Filter → ActionProxy → HelloAction.execute()
|
||||||
|
→ 返回 "success" → 配置的 result → hello.jsp → 浏览器</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 30px;">
|
||||||
|
<a href="/demo/validation" class="btn">下一节:表单验证 →</a>
|
||||||
|
<a href="/" class="btn" style="background: #764ba2;">← 返回首页</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
173
web/demo/model/index.jsp
Normal file
173
web/demo/model/index.jsp
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>数据封装 - Struts2 学习</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', 'PingFang SC', sans-serif; background: linear-gradient(135deg, #667eea, #764ba2); min-height: 100vh; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
.breadcrumb { background: white; padding: 15px 25px; border-radius: 10px; margin-bottom: 20px; }
|
||||||
|
.breadcrumb a { color: #667eea; text-decoration: none; }
|
||||||
|
.content { background: white; border-radius: 20px; padding: 40px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
|
||||||
|
h1 { color: #667eea; border-bottom: 3px solid #667eea; padding-bottom: 15px; }
|
||||||
|
h2 { color: #764ba2; margin-top: 30px; }
|
||||||
|
.section { margin: 25px 0; padding: 20px; background: #f8f9fa; border-radius: 10px; }
|
||||||
|
pre { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; overflow-x: auto; font-size: 0.9em; }
|
||||||
|
.keyword { color: #569cd6; }
|
||||||
|
.string { color: #ce9178; }
|
||||||
|
.comment { color: #6a9955; }
|
||||||
|
.btn { display: inline-block; padding: 12px 30px; background: #667eea; color: white; text-decoration: none; border-radius: 25px; margin-right: 10px; }
|
||||||
|
.note { background: #fff3e0; border-left: 4px solid #ff9800; padding: 15px; margin: 20px 0; border-radius: 0 10px 10px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<a href="/">🏠 首页</a> / 数据封装
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>📦 数据封装 - 属性驱动 vs 模型驱动</h1>
|
||||||
|
|
||||||
|
<div class="note">
|
||||||
|
<strong>📝 本节要点:</strong> 掌握 Struts2 两种数据封装方式,将请求参数封装到 Java 对象
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>1. 两种封装方式对比</h2>
|
||||||
|
<div class="section">
|
||||||
|
<table style="width:100%; border-collapse: collapse;">
|
||||||
|
<tr style="background:#667eea; color:white;">
|
||||||
|
<th style="padding:10px;">方式</th>
|
||||||
|
<th style="padding:10px;">实现</th>
|
||||||
|
<th style="padding:10px;">适用场景</th>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8f9fa;">
|
||||||
|
<td style="padding:10px;"><strong>属性驱动</strong></td>
|
||||||
|
<td style="padding:10px;">Action 中定义属性 + getter/setter</td>
|
||||||
|
<td style="padding:10px;">简单场景,属性较少</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:10px;"><strong>模型驱动</strong></td>
|
||||||
|
<td style="padding:10px;">实现 ModelDriven 接口</td>
|
||||||
|
<td style="padding:10px;">复杂对象,表单字段多</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>2. 方式一:属性驱动 (Property Driven)</h2>
|
||||||
|
|
||||||
|
<h3>2.1 基本属性封装</h3>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment">/**
|
||||||
|
* 直接在 Action 中定义属性
|
||||||
|
* 表单: <input name="username"> 会自动调用 setUsername()
|
||||||
|
*/</span>
|
||||||
|
<span class="keyword">public class</span> UserAction <span class="keyword">extends</span> ActionSupport {
|
||||||
|
|
||||||
|
<span class="comment">// Struts2 自动调用 setter 注入参数</span>
|
||||||
|
<span class="keyword">private</span> String username;
|
||||||
|
<span class="keyword">private</span> String email;
|
||||||
|
<span class="keyword">private</span> Integer age;
|
||||||
|
|
||||||
|
<span class="annotation">@Override</span>
|
||||||
|
<span class="keyword">public</span> String execute() {
|
||||||
|
System.out.println(username + <span class="string">" - "</span> + email + <span class="string">" - "</span> + age);
|
||||||
|
<span class="keyword">return</span> SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="comment">// 必须提供 setter 和 getter</span>
|
||||||
|
<span class="keyword">public</span> String getUsername() { <span class="keyword">return</span> username; }
|
||||||
|
<span class="keyword">public void</span> setUsername(String username) { <span class="keyword">this</span>.username = username; }
|
||||||
|
|
||||||
|
<span class="keyword">public</span> String getEmail() { <span class="keyword">return</span> email; }
|
||||||
|
<span class="keyword">public void</span> setEmail(String email) { <span class="keyword">this</span>.email = email; }
|
||||||
|
|
||||||
|
<span class="keyword">public</span> Integer getAge() { <span class="keyword">return</span> age; }
|
||||||
|
<span class="keyword">public void</span> setAge(Integer age) { <span class="keyword">this</span>.age = age; }
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>2.2 复杂属性 (嵌套对象)</h3>
|
||||||
|
<div class="section">
|
||||||
|
<p>表单中的 <code>user.username</code> 会自动调用 setUser(用户对象).setUsername()</p>
|
||||||
|
<pre><span class="comment">// Action</span>
|
||||||
|
<span class="keyword">private</span> User user; <span class="comment">// 包含 username, email 属性的对象</span>
|
||||||
|
|
||||||
|
<span class="comment">// 表单</span>
|
||||||
|
<span class="keyword"><input</span> name=<span class="string">"user.username"</span>/>
|
||||||
|
<span class="keyword"><input</span> name=<span class="string">"user.email"</span>/></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>3. 方式二:模型驱动 (ModelDriven)</h2>
|
||||||
|
<div class="note">
|
||||||
|
<strong>💡 推荐:</strong> 对于复杂表单,使用模型驱动更清晰,Action 和 Model 分离
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="keyword">import</span> com.opensymphony.xwork2.ModelDriven;
|
||||||
|
|
||||||
|
<span class="comment">/**
|
||||||
|
* 实现 ModelDriven 接口
|
||||||
|
* 需要:
|
||||||
|
* 1. 实现 getModel() 方法
|
||||||
|
* 2. 泛型指定模型类型
|
||||||
|
*/</span>
|
||||||
|
<span class="keyword">public class</span> UserAction <span class="keyword">extends</span> ActionSupport <span class="keyword">implements</span> ModelDriven<User> {
|
||||||
|
|
||||||
|
<span class="comment">// 模型对象</span>
|
||||||
|
<span class="keyword">private</span> User user = <span class="keyword">new</span> User();
|
||||||
|
|
||||||
|
<span class="comment">/**
|
||||||
|
* 返回模型对象
|
||||||
|
* Struts2 会把参数注入到返回的对象中
|
||||||
|
*/</span>
|
||||||
|
<span class="annotation">@Override</span>
|
||||||
|
<span class="keyword">public</span> User getModel() {
|
||||||
|
<span class="keyword">return</span> user;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="annotation">@Override</span>
|
||||||
|
<span class="keyword">public</span> String execute() {
|
||||||
|
<span class="comment">// 直接使用 user 对象</span>
|
||||||
|
System.out.println(user.getUsername());
|
||||||
|
<span class="keyword">return</span> SUCCESS;
|
||||||
|
}
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>4. List/Map 属性封装</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment">// Action - 接收 List</span>
|
||||||
|
<span class="keyword">private</span> List<User> users;
|
||||||
|
<span class="keyword">public void</span> setUsers(List<User> users) { <span class="keyword">this</span>.users = users; }
|
||||||
|
<span class="keyword">public</span> List<User> getUsers() { <span class="keyword">return</span> users; }
|
||||||
|
|
||||||
|
<span class="comment">// 页面表单 - users[0].username, users[1].username</span>
|
||||||
|
<span class="keyword"><input</span> name=<span class="string">"users[0].username"</span> value=<span class="string">"张三"</span>/>
|
||||||
|
<span class="keyword"><input</span> name=<span class="string">"users[1].username"</span> value=<span class="string">"李四"</span>/>
|
||||||
|
|
||||||
|
<span class="comment">// Action - 接收 Map</span>
|
||||||
|
<span class="keyword">private</span> Map<String, User> userMap;
|
||||||
|
<span class="keyword"><input</span> name=<span class="string">"userMap['one'].username"</span>/></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>5. 封装流程图</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre style="text-align: center; font-size: 1.1em;">
|
||||||
|
请求参数 → Action setter 方法 → 属性赋值
|
||||||
|
↓
|
||||||
|
实现 ModelDriven → getModel() → 模型对象赋值
|
||||||
|
↓
|
||||||
|
值栈 (Value Stack)</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 30px;">
|
||||||
|
<a href="/demo/upload" class="btn">下一节:文件上传 →</a>
|
||||||
|
<a href="/" class="btn" style="background: #764ba2;">← 返回首页</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
102
web/demo/upload/index.jsp
Normal file
102
web/demo/upload/index.jsp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>文件上传 - Struts2 学习</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea, #764ba2); min-height: 100vh; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
.breadcrumb { background: white; padding: 15px 25px; border-radius: 10px; margin-bottom: 20px; }
|
||||||
|
.content { background: white; border-radius: 20px; padding: 40px; }
|
||||||
|
h1 { color: #667eea; border-bottom: 3px solid #667eea; padding-bottom: 15px; }
|
||||||
|
h2 { color: #764ba2; margin-top: 30px; }
|
||||||
|
.section { margin: 25px 0; padding: 20px; background: #f8f9fa; border-radius: 10px; }
|
||||||
|
pre { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; overflow-x: auto; }
|
||||||
|
.keyword { color: #569cd6; }
|
||||||
|
.string { color: #ce9178; }
|
||||||
|
.comment { color: #6a9955; }
|
||||||
|
.btn { display: inline-block; padding: 12px 30px; background: #667eea; color: white; text-decoration: none; border-radius: 25px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<a href="/">🏠 首页</a> / 文件上传
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h1>📁 文件上传</h1>
|
||||||
|
|
||||||
|
<h2>1. 单文件上传</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment">// Action</span>
|
||||||
|
<span class="keyword">public class</span> UploadAction <span class="keyword">extends</span> ActionSupport {
|
||||||
|
|
||||||
|
<span class="comment">// 文件对象</span>
|
||||||
|
<span class="keyword">private</span> File upload;
|
||||||
|
|
||||||
|
<span class="comment">// 文件名 (格式: 属性名FileName)</span>
|
||||||
|
<span class="keyword">private</span> String uploadFileName;
|
||||||
|
|
||||||
|
<span class="comment">// 文件类型 (格式: 属性名ContentType)</span>
|
||||||
|
<span class="keyword">private</span> String uploadContentType;
|
||||||
|
|
||||||
|
<span class="annotation">@Override</span>
|
||||||
|
<span class="keyword">public</span> String execute() <span class="keyword">throws</span> Exception {
|
||||||
|
<span class="comment">// 保存文件</span>
|
||||||
|
String path = ServletActionContext.getServletContext()
|
||||||
|
.getRealPath(<span class="string">"/upload"</span>);
|
||||||
|
<span class="keyword">new</span> File(path, uploadFileName).createNewFile();
|
||||||
|
|
||||||
|
<span class="comment">// 复制文件</span>
|
||||||
|
FileUtils.copyFile(upload, <span class="keyword">new</span> File(path, uploadFileName));
|
||||||
|
|
||||||
|
<span class="keyword">return</span> SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="comment">// getter/setter</span>
|
||||||
|
<span class="keyword">public</span> File getUpload() { <span class="keyword">return</span> upload; }
|
||||||
|
<span class="keyword">public void</span> setUpload(File upload) { <span class="keyword">this</span>.upload = upload; }
|
||||||
|
<span class="keyword">public</span> String getUploadFileName() { <span class="keyword">return</span> uploadFileName; }
|
||||||
|
<span class="keyword">public void</span> setUploadFileName(String uploadFileName) { <span class="keyword">this</span>.uploadFileName = uploadFileName; }
|
||||||
|
<span class="keyword">public</span> String getUploadContentType() { <span class="keyword">return</span> uploadContentType; }
|
||||||
|
<span class="keyword">public void</span> setUploadContentType(String uploadContentType) { <span class="keyword">this</span>.uploadContentType = uploadContentType; }
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>2. JSP 表单</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment"><%@ taglib prefix="s" uri="/struts-tags" %></span>
|
||||||
|
<span class="keyword"><s:form</span> action=<span class="string">"upload"</span> method=<span class="string">"post"</span> enctype=<span class="string">"multipart/form-data"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><s:file</span> name=<span class="string">"upload"</span> label=<span class="string">"选择文件"</span>/>
|
||||||
|
<span class="keyword"><s:submit</span> value=<span class="string">"上传"</span>/>
|
||||||
|
<span class="keyword"></s:form></span></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>3. 多文件上传</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment">// 使用 List 接收多文件</span>
|
||||||
|
<span class="keyword">private</span> List<File> uploads;
|
||||||
|
<span class="keyword">private</span> List<String> uploadsFileName;
|
||||||
|
|
||||||
|
<span class="comment">// 表单</span>
|
||||||
|
<span class="keyword"><s:file</span> name=<span class="string">"uploads"</span> multiple=<span class="string">"multiple"</span>/></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>4. 限制文件大小和类型</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment">// struts.xml 配置</span>
|
||||||
|
<span class="keyword"><action</span> name=<span class="string">"upload"</span> class=<span class="string">"com.demo.UploadAction"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><interceptor-ref</span> name=<span class="string">"fileUpload"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><param</span> name=<span class="string">"maximumSize"</span><span class="keyword">></span>10485760<span class="keyword"></param></span><span class="comment"><!-- 10MB --></span>
|
||||||
|
<span class="keyword"><param</span> name=<span class="string">"allowedTypes"</span><span class="keyword">></span>image/png,image/jpeg<span class="keyword"></param></span>
|
||||||
|
<span class="keyword"></interceptor-ref></span>
|
||||||
|
<span class="keyword"><result></span>/upload/success.jsp<span class="keyword"></result></span>
|
||||||
|
<span class="keyword"></action></span></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/demo/ajax" class="btn">下一节:AJAX →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
227
web/demo/validation/index.jsp
Normal file
227
web/demo/validation/index.jsp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>表单验证 - Struts2 学习</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', 'PingFang SC', sans-serif; background: linear-gradient(135deg, #667eea, #764ba2); min-height: 100vh; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
.breadcrumb { background: white; padding: 15px 25px; border-radius: 10px; margin-bottom: 20px; }
|
||||||
|
.breadcrumb a { color: #667eea; text-decoration: none; }
|
||||||
|
.content { background: white; border-radius: 20px; padding: 40px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
|
||||||
|
h1 { color: #667eea; border-bottom: 3px solid #667eea; padding-bottom: 15px; }
|
||||||
|
h2 { color: #764ba2; margin-top: 30px; }
|
||||||
|
.section { margin: 25px 0; padding: 20px; background: #f8f9fa; border-radius: 10px; }
|
||||||
|
pre { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; overflow-x: auto; font-size: 0.9em; }
|
||||||
|
.keyword { color: #569cd6; }
|
||||||
|
.string { color: #ce9178; }
|
||||||
|
.comment { color: #6a9955; }
|
||||||
|
.btn { display: inline-block; padding: 12px 30px; background: #667eea; color: white; text-decoration: none; border-radius: 25px; margin-right: 10px; }
|
||||||
|
.note { background: #fff3e0; border-left: 4px solid #ff9800; padding: 15px; margin: 20px 0; border-radius: 0 10px 10px 0; }
|
||||||
|
.tip { background: #e3f2fd; border-left: 4px solid #2196f3; padding: 15px; margin: 20px 0; border-radius: 0 10px 10px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<a href="/">🏠 首页</a> / 表单验证
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>✅ 表单验证</h1>
|
||||||
|
|
||||||
|
<div class="note">
|
||||||
|
<strong>📝 本节要点:</strong> 掌握 Struts2 的两种验证方式:编程式验证和声明式验证
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>1. 验证方式概览</h2>
|
||||||
|
<div class="section">
|
||||||
|
<table style="width:100%; border-collapse: collapse;">
|
||||||
|
<tr style="background:#667eea; color:white;">
|
||||||
|
<th style="padding:10px; text-align:left;">方式</th>
|
||||||
|
<th style="padding:10px; text-align:left;">实现方式</th>
|
||||||
|
<th style="padding:10px; text-align:left;">适用场景</th>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8f9fa;">
|
||||||
|
<td style="padding:10px;">编程式</td>
|
||||||
|
<td style="padding:10px;">重写 validate() 方法</td>
|
||||||
|
<td style="padding:10px;">简单验证,逻辑复杂</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:10px;">声明式 - 方法级</td>
|
||||||
|
<td style="padding:10px;">validateXxx() 方法</td>
|
||||||
|
<td style="padding:10px;">特定方法的验证</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8f9fa;">
|
||||||
|
<td style="padding:10px;">声明式 - XML</td>
|
||||||
|
<td style="padding:10px;">XxxAction-validation.xml</td>
|
||||||
|
<td style="padding:10px;">可复用,团队协作</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>2. 方式一:编程式验证 (validate)</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="keyword">public class</span> LoginAction <span class="keyword">extends</span> ActionSupport {
|
||||||
|
|
||||||
|
<span class="keyword">private</span> String username;
|
||||||
|
<span class="keyword">private</span> String password;
|
||||||
|
|
||||||
|
<span class="annotation">@Override</span>
|
||||||
|
<span class="keyword">public</span> String execute() {
|
||||||
|
<span class="comment">// 登录逻辑...</span>
|
||||||
|
<span class="keyword">return</span> SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="comment">/**
|
||||||
|
* 验证方法 - 对所有 execute 方法都生效
|
||||||
|
*/</span>
|
||||||
|
<span class="annotation">@Override</span>
|
||||||
|
<span class="keyword">public void</span> validate() {
|
||||||
|
<span class="comment">// 用户名验证</span>
|
||||||
|
<span class="keyword">if</span> (username == <span class="keyword">null</span> || username.trim().length() < 3) {
|
||||||
|
addFieldError(<span class="string">"username"</span>, <span class="string">"用户名至少3个字符"</span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="comment">// 密码验证</span>
|
||||||
|
<span class="keyword">if</span> (password == <span class="keyword">null</span> || password.length() < 6) {
|
||||||
|
addFieldError(<span class="string">"password"</span>, <span class="string">"密码至少6个字符"</span>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="comment">// getter/setter...</span>
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>3. 方式二:方法级验证 (validateXxx)</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment">/**
|
||||||
|
* 只验证 register 方法,不验证 login 方法
|
||||||
|
*/</span>
|
||||||
|
<span class="keyword">public void</span> validateRegister() {
|
||||||
|
<span class="keyword">if</span> (email == <span class="keyword">null</span> || !email.contains(<span class="string">"@"</span>)) {
|
||||||
|
addFieldError(<span class="string">"email"</span>, <span class="string">"邮箱格式不正确"</span>);
|
||||||
|
}
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>4. 方式三:XML 声明式验证</h2>
|
||||||
|
<div class="tip">
|
||||||
|
<strong>优点:</strong> 验证规则与代码分离,团队协作更清晰,可复用
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>4.1 创建验证文件</h3>
|
||||||
|
<div class="section">
|
||||||
|
<p>文件名格式:<code>ActionName-validation.xml</code></p>
|
||||||
|
<p>位置:<code>WEB-INF/classes/com/demo/action/</code></p>
|
||||||
|
<pre><span class="comment"><?xml version="1.0" encoding="UTF-8"?></span>
|
||||||
|
<span class="keyword"><!DOCTYPE validators PUBLIC</span>
|
||||||
|
<span class="string">"-//OpenSymphony Group//XWork Validator 1.0.3//EN"</span>
|
||||||
|
<span class="string">"http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd"</span><span class="keyword">></span>
|
||||||
|
|
||||||
|
<span class="keyword"><validators></span>
|
||||||
|
<span class="comment"><!-- 字段验证 --></span>
|
||||||
|
<span class="keyword"><field</span> name=<span class="string">"username"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><field-validator</span> type=<span class="string">"requiredstring"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><message></span>用户名不能为空<span class="keyword"></message></span>
|
||||||
|
<span class="keyword"></field-validator></span>
|
||||||
|
<span class="keyword"><field-validator</span> type=<span class="string">"stringlength"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><param</span> name=<span class="string">"minLength"</span><span class="keyword">></span>3<span class="keyword"></param></span>
|
||||||
|
<span class="keyword"><param</span> name=<span class="string">"maxLength"</span><span class="keyword">></span>20<span class="keyword"></param></span>
|
||||||
|
<span class="keyword"><message></span>用户名长度3-20字符<span class="keyword"></message></span>
|
||||||
|
<span class="keyword"></field-validator></span>
|
||||||
|
<span class="keyword"></field></span>
|
||||||
|
|
||||||
|
<span class="comment"><!-- 字段验证:email --></span>
|
||||||
|
<span class="keyword"><field</span> name=<span class="string">"email"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><field-validator</span> type=<span class="string">"email"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><message></span>邮箱格式不正确<span class="keyword"></message></span>
|
||||||
|
<span class="keyword"></field-validator></span>
|
||||||
|
<span class="keyword"></field></span>
|
||||||
|
|
||||||
|
<span class="comment"><!-- 非字段验证 --></span>
|
||||||
|
<span class="keyword"><validator</span> type=<span class="string">"expression"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><param</span> name=<span class="string">"expression"</span><span class="keyword">></span>password==confirmPassword<span class="keyword"></param></span>
|
||||||
|
<span class="keyword"><message></span>两次密码不一致<span class="keyword"></message></span>
|
||||||
|
<span class="keyword"></validator></span>
|
||||||
|
<span class="keyword"></validators></span></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>5. 常用验证器类型</h2>
|
||||||
|
<div class="section">
|
||||||
|
<table style="width:100%; border-collapse: collapse;">
|
||||||
|
<tr style="background:#667eea; color:white;">
|
||||||
|
<th style="padding:10px; text-align:left;">验证器</th>
|
||||||
|
<th style="padding:10px; text-align:left;">说明</th>
|
||||||
|
<th style="padding:10px; text-align:left;">示例</th>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8f9fa;">
|
||||||
|
<td style="padding:10px;">required</td>
|
||||||
|
<td style="padding:10px;">必填</td>
|
||||||
|
<td style="padding:10px;">username 不能为空</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:10px;">requiredstring</td>
|
||||||
|
<td style="padding:10px;">字符串必填</td>
|
||||||
|
<td style="padding:10px;">不能为空字符串</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8f9fa;">
|
||||||
|
<td style="padding:10px;">stringlength</td>
|
||||||
|
<td style="padding:10px;">字符串长度</td>
|
||||||
|
<td style="padding:10px;">minLength, maxLength</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:10px;">email</td>
|
||||||
|
<td style="padding:10px;">邮箱格式</td>
|
||||||
|
<td style="padding:10px;">xxx@xxx.com</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8f9fa;">
|
||||||
|
<td style="padding:10px;">regex</td>
|
||||||
|
<td style="padding:10px;">正则表达式</td>
|
||||||
|
<td style="padding:10px;">手机号、身份证等</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:10px;">int</td>
|
||||||
|
<td style="padding:10px;">整数范围</td>
|
||||||
|
<td style="padding:10px;">min, max</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8f9fa;">
|
||||||
|
<td style="padding:10px;">url</td>
|
||||||
|
<td style="padding:10px;">URL 格式</td>
|
||||||
|
<td style="padding:10px;">http://xxx.com</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:10px;">date</td>
|
||||||
|
<td style="padding:10px;">日期范围</td>
|
||||||
|
<td style="padding:10px;">min, max</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>6. 在 JSP 中显示错误</h2>
|
||||||
|
<div class="section">
|
||||||
|
<pre><span class="comment"><%@ taglib prefix="s" uri="/struts-tags" %></span>
|
||||||
|
|
||||||
|
<span class="comment"><!-- 显示特定字段的错误 --></span>
|
||||||
|
<span class="keyword"><s:fielderror</span> fieldName=<span class="string">"username"</span>/>
|
||||||
|
|
||||||
|
<span class="comment"><!-- 显示所有字段错误 --></span>
|
||||||
|
<span class="keyword"><s:fielderror</span>/>
|
||||||
|
|
||||||
|
<span class="comment"><!-- 显示 Action 级别错误 --></span>
|
||||||
|
<span class="keyword"><s:actionerror</span>/>
|
||||||
|
|
||||||
|
<span class="comment"><!-- 显示 Action 级别消息 --></span>
|
||||||
|
<span class="keyword"><s:actionmessage</span>/></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 30px;">
|
||||||
|
<a href="/demo/model" class="btn">下一节:数据封装 →</a>
|
||||||
|
<a href="/" class="btn" style="background: #764ba2;">← 返回首页</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
21
web/error/404.jsp
Normal file
21
web/error/404.jsp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>404 - 页面未找到</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; background: #f5f5f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
|
||||||
|
.card { background: white; padding: 50px; border-radius: 15px; text-align: center; }
|
||||||
|
h1 { color: #e53935; font-size: 4em; margin-bottom: 20px; }
|
||||||
|
p { color: #666; }
|
||||||
|
a { color: #667eea; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>404</h1>
|
||||||
|
<p>页面未找到</p>
|
||||||
|
<p><a href="../index.jsp">返回首页</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
47
web/hello.jsp
Normal file
47
web/hello.jsp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Hello - Struts2 Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 50px;
|
||||||
|
border-radius: 20px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 3em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
p { color: #666; font-size: 1.2em; }
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 30px;
|
||||||
|
padding: 14px 40px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h1><s:property value="message" default="Hello World!"/></h1>
|
||||||
|
<p>Struts2 Action 执行成功!</p>
|
||||||
|
<a href="index.jsp" class="btn">返回首页</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
552
web/index.jsp
Normal file
552
web/index.jsp
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Struts2 全栈学习平台</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
:root {
|
||||||
|
--primary: #667eea;
|
||||||
|
--secondary: #764ba2;
|
||||||
|
--success: #11998e;
|
||||||
|
--warning: #f093fb;
|
||||||
|
--danger: #f5576c;
|
||||||
|
--dark: #2d3748;
|
||||||
|
--light: #f7fafc;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||||
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: rgba(255,255,255,0.95);
|
||||||
|
padding: 20px 0;
|
||||||
|
box-shadow: 0 2px 20px rgba(0,0,0,0.1);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.header-content {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
font-size: 1.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.nav-links a {
|
||||||
|
margin-left: 25px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--dark);
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
.nav-links a:hover { color: var(--primary); }
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
.hero h1 {
|
||||||
|
font-size: 3em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.hero p {
|
||||||
|
font-size: 1.3em;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.progress-section h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.progress-item {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 150px;
|
||||||
|
padding: 15px;
|
||||||
|
background: var(--light);
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
border-left: 4px solid var(--primary);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
.progress-item:hover { transform: translateY(-5px); }
|
||||||
|
.progress-item.completed { border-color: var(--success); }
|
||||||
|
.progress-item.current { border-color: var(--warning); background: #fff3e0; }
|
||||||
|
.progress-item h3 { font-size: 1.1em; margin-bottom: 5px; }
|
||||||
|
.progress-item span { font-size: 0.85em; color: #666; }
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||||
|
transition: transform 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 2px solid var(--light);
|
||||||
|
}
|
||||||
|
.card-icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.card-icon.green { background: #e8f5e9; }
|
||||||
|
.card-icon.blue { background: #e3f2fd; }
|
||||||
|
.card-icon.pink { background: #fce4ec; }
|
||||||
|
.card-icon.yellow { background: #fff3e0; }
|
||||||
|
.card-icon.purple { background: #f3e5f5; }
|
||||||
|
|
||||||
|
.card h3 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.7;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.tag.basic { background: #e8f5e9; color: #2e7d32; }
|
||||||
|
.tag.advance { background: #e3f2fd; color: #1565c0; }
|
||||||
|
.tag.expert { background: #fce4ec; color: #c2185b; }
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 25px;
|
||||||
|
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 10px;
|
||||||
|
transition: transform 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
.feature-list li {
|
||||||
|
padding: 8px 0;
|
||||||
|
padding-left: 25px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.feature-list li::before {
|
||||||
|
content: "✓";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--success);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-preview {
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 0.85em;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
.code-preview .keyword { color: #569cd6; }
|
||||||
|
.code-preview .string { color: #ce9178; }
|
||||||
|
.code-preview .comment { color: #6a9955; }
|
||||||
|
.code-preview .annotation { color: #c586c0; }
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: white;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero h1 { font-size: 2em; }
|
||||||
|
.grid { grid-template-columns: 1fr; }
|
||||||
|
.progress-bar { flex-direction: column; }
|
||||||
|
.nav-links { display: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="logo">🚀 Struts2 学习平台</div>
|
||||||
|
<div class="nav-links">
|
||||||
|
<a href="#course">课程体系</a>
|
||||||
|
<a href="#examples">示例代码</a>
|
||||||
|
<a href="#projects">实战项目</a>
|
||||||
|
<a href="#resources">资源下载</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="hero">
|
||||||
|
<h1>从零掌握 Struts2 框架</h1>
|
||||||
|
<p>完整的 Struts2 学习路径 | 视频教程 | 代码示例 | 实战项目</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学习进度 -->
|
||||||
|
<div class="progress-section">
|
||||||
|
<h2>📈 学习路径</h2>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-item completed">
|
||||||
|
<h3>第一阶段</h3>
|
||||||
|
<span>环境搭建 + 基础</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-item current">
|
||||||
|
<h3>第二阶段</h3>
|
||||||
|
<span>核心组件</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-item">
|
||||||
|
<h3>第三阶段</h3>
|
||||||
|
<span>数据处理</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-item">
|
||||||
|
<h3>第四阶段</h3>
|
||||||
|
<span>企业应用</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 课程卡片 -->
|
||||||
|
<div class="grid" id="course">
|
||||||
|
|
||||||
|
<!-- 1. Hello World -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon green">👋</div>
|
||||||
|
<h3>1. Hello World</h3>
|
||||||
|
</div>
|
||||||
|
<p>Struts2 入门最简单的示例,掌握基本的 Action 配置和参数传递。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>Action 编写</li>
|
||||||
|
<li>struts.xml 配置</li>
|
||||||
|
<li>结果视图映射</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag basic">入门</span>
|
||||||
|
<a href="demo/hello/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 2. 表单与验证 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon blue">✅</div>
|
||||||
|
<h3>2. 表单与验证</h3>
|
||||||
|
</div>
|
||||||
|
<p>深入理解 Struts2 的两种验证方式:编程式验证和声明式验证。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>validate() 方法</li>
|
||||||
|
<li>XML 验证文件</li>
|
||||||
|
<li>字段级验证</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag basic">入门</span>
|
||||||
|
<a href="demo/validation/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 3. 数据封装 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon pink">📦</div>
|
||||||
|
<h3>3. 数据封装</h3>
|
||||||
|
</div>
|
||||||
|
<p>掌握 Struts2 的数据封装机制,包括属性驱动和模型驱动。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>属性驱动</li>
|
||||||
|
<li>模型驱动 (ModelDriven)</li>
|
||||||
|
<li>复杂对象封装</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag basic">进阶</span>
|
||||||
|
<a href="demo/model/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 4. 文件上传 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon yellow">📁</div>
|
||||||
|
<h3>4. 文件上传</h3>
|
||||||
|
</div>
|
||||||
|
<p>Struts2 文件上传功能,支持单文件和多文件上传。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>File 对象接收</li>
|
||||||
|
<li>文件类型验证</li>
|
||||||
|
<li>多文件上传</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag advance">进阶</span>
|
||||||
|
<a href="demo/upload/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 5. 类型转换 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon purple">🔄</div>
|
||||||
|
<h3>5. 类型转换器</h3>
|
||||||
|
</div>
|
||||||
|
<p>自定义类型转换器,处理特殊数据类型和复杂对象。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>TypeConverter</li>
|
||||||
|
<li>StrutsTypeConverter</li>
|
||||||
|
<li>局部/全局转换器</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag advance">进阶</span>
|
||||||
|
<a href="demo/converter/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 6. AJAX -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon green">⚡</div>
|
||||||
|
<h3>6. AJAX + JSON</h3>
|
||||||
|
</div>
|
||||||
|
<p>使用 Struts2 JSON 插件实现异步数据交互。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>JSON 结果类型</li>
|
||||||
|
<li>AJAX 表单提交</li>
|
||||||
|
<li>局部页面刷新</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag advance">进阶</span>
|
||||||
|
<a href="demo/ajax/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 7. 拦截器 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon blue">🛡️</div>
|
||||||
|
<h3>7. 拦截器</h3>
|
||||||
|
</div>
|
||||||
|
<p>理解拦截器机制,这是 Struts2 框架的核心灵魂。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>自定义拦截器</li>
|
||||||
|
<li>拦截器栈</li>
|
||||||
|
<li>方法过滤拦截器</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag advance">进阶</span>
|
||||||
|
<a href="demo/interceptor/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 8. OGNL -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon pink">📊</div>
|
||||||
|
<h3>8. OGNL 表达式</h3>
|
||||||
|
</div>
|
||||||
|
<p>掌握 Struts2 的表达式语言,操作对象图和集合。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>基础语法</li>
|
||||||
|
<li>集合操作</li>
|
||||||
|
<li>上下文对象</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag advance">进阶</span>
|
||||||
|
<a href="demo/ognl/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 9. Session/Request -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon yellow">🔐</div>
|
||||||
|
<h3>9. 作用域对象</h3>
|
||||||
|
</div>
|
||||||
|
<p>掌握 ActionContext 和值栈的操作。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>Session 管理</li>
|
||||||
|
<li>Request/Response</li>
|
||||||
|
<li>值栈操作</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag advance">进阶</span>
|
||||||
|
<a href="demo/scope/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 10. REST -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon purple">🌐</div>
|
||||||
|
<h3>10. RESTful</h3>
|
||||||
|
</div>
|
||||||
|
<p>使用 Struts2 实现 RESTful 风格的 API 设计。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>REST 插件</li>
|
||||||
|
<li>URL 参数映射</li>
|
||||||
|
<li>内容协商</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag expert">专家</span>
|
||||||
|
<a href="demo/rest/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 11. 国际化 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon green">🌍</div>
|
||||||
|
<h3>11. 国际化 (i18n)</h3>
|
||||||
|
</div>
|
||||||
|
<p>实现多语言支持的应用程序。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>资源文件配置</li>
|
||||||
|
<li>getText() 使用</li>
|
||||||
|
<li>动态语言切换</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag advance">进阶</span>
|
||||||
|
<a href="demo/i18n/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 12. 源码解析 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-icon blue">🔍</div>
|
||||||
|
<h3>12. 源码解析</h3>
|
||||||
|
</div>
|
||||||
|
<p>深入理解 Struts2 框架的内部实现原理。</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li>请求处理流程</li>
|
||||||
|
<li>拦截器链源码</li>
|
||||||
|
<li>OGNL 原理</li>
|
||||||
|
</ul>
|
||||||
|
<span class="tag expert">专家</span>
|
||||||
|
<a href="demo/source/index.jsp" class="btn">开始学习</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 配置参考 -->
|
||||||
|
<div class="progress-section" style="margin-top: 50px;" id="examples">
|
||||||
|
<h2>⚙️ 配置文件示例</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div>
|
||||||
|
<h3 style="margin: 15px 0;">struts.xml 基础配置</h3>
|
||||||
|
<div class="code-preview">
|
||||||
|
<span class="comment"><?xml version="1.0"?></span>
|
||||||
|
<span class="keyword"><struts></span>
|
||||||
|
<span class="comment"><!-- 开发模式 --></span>
|
||||||
|
<span class="keyword"><constant</span> name=<span class="string">"struts.devMode"</span> value=<span class="string">"true"</span><span class="keyword">/></span>
|
||||||
|
|
||||||
|
<span class="comment"><!-- 包定义 --></span>
|
||||||
|
<span class="keyword"><package</span> name=<span class="string">"default"</span> namespace=<span class="string">"/"</span> extends=<span class="string">"struts-default"</span><span class="keyword">></span>
|
||||||
|
|
||||||
|
<span class="keyword"><action</span> name=<span class="string">"hello"</span> class=<span class="string">"com.demo.HelloAction"</span><span class="keyword">></span>
|
||||||
|
<span class="keyword"><result>/hello.jsp</result></span>
|
||||||
|
<span class="keyword"></action></span>
|
||||||
|
|
||||||
|
<span class="keyword"></package></span>
|
||||||
|
<span class="keyword"></struts></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style="margin: 15px 0;">Action 基础写法</h3>
|
||||||
|
<div class="code-preview">
|
||||||
|
<span class="keyword">public class</span> HelloAction <span class="keyword">extends</span> ActionSupport {
|
||||||
|
|
||||||
|
<span class="keyword">private</span> String name;
|
||||||
|
|
||||||
|
<span class="annotation">@Override</span>
|
||||||
|
<span class="keyword">public</span> String execute() {
|
||||||
|
<span class="keyword">return</span> SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="comment">// getter/setter</span>
|
||||||
|
<span class="keyword">public</span> String getName() { <span class="keyword">return</span> name; }
|
||||||
|
<span class="keyword">public void</span> setName(String name) { <span class="keyword">this</span>.name = name; }
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 实战项目 -->
|
||||||
|
<div class="progress-section" style="margin-top: 50px;" id="projects">
|
||||||
|
<h2>💼 实战项目</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<h3>📝 博客系统</h3>
|
||||||
|
<p>完整的博客系统,包含文章管理、评论、分类等功能。</p>
|
||||||
|
<span class="tag basic">入门</span>
|
||||||
|
<a href="#" class="btn">查看详情</a>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>📋 待办事项</h3>
|
||||||
|
<p>Todo 列表应用,练习 CRUD 操作和 AJAX。</p>
|
||||||
|
<span class="tag basic">入门</span>
|
||||||
|
<a href="#" class="btn">查看详情</a>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>🛒 商城购物车</h3>
|
||||||
|
<p>购物车功能,练习 Session 和订单管理。</p>
|
||||||
|
<span class="tag advance">进阶</span>
|
||||||
|
<a href="#" class="btn">查看详情</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Struts2 版本: 2.5.30 | Tomcat 9 | JDK 17</p>
|
||||||
|
<p>本学习平台包含完整的代码示例和配置文件</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
87
web/upload/index.jsp
Normal file
87
web/upload/index.jsp
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>文件上传 - Struts2 Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container { max-width: 600px; margin: 0 auto; }
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
h2 { color: #4facfe; margin-bottom: 20px; }
|
||||||
|
.info {
|
||||||
|
background: #fff3e0;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-left: 4px solid #ff9800;
|
||||||
|
}
|
||||||
|
.form-group { margin-bottom: 20px; }
|
||||||
|
label { display: block; margin-bottom: 8px; color: #555; font-weight: 500; }
|
||||||
|
input[type="file"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px dashed #4facfe;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f0f9ff;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: #4facfe;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover { background: #00c6fb; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="card">
|
||||||
|
<h2>📁 文件上传</h2>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<strong>Struts2 文件上传原理:</strong>
|
||||||
|
<ul style="margin:10px 0 0 20px;">
|
||||||
|
<li>使用 <code>File</code> 类型接收文件</li>
|
||||||
|
<li>使用 <code>String fileName</code> 获取原始文件名</li>
|
||||||
|
<li>使用 <code>String contentType</code> 获取文件类型</li>
|
||||||
|
<li>底层使用 commons-fileupload</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<s:form action="upload" method="post" enctype="multipart/form-data">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>选择文件</label>
|
||||||
|
<s:file name="upload"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>选择文件 (多文件)</label>
|
||||||
|
<s:file name="uploads" multiple="multiple"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">上传文件</button>
|
||||||
|
</s:form>
|
||||||
|
|
||||||
|
<p style="text-align:center;margin-top:20px;">
|
||||||
|
<a href="../index.jsp">← 返回首页</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39
web/upload/success.jsp
Normal file
39
web/upload/success.jsp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>操作成功</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1 { color: #11998e; }
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 12px 30px;
|
||||||
|
background: #11998e;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>✅ 操作成功!</h1>
|
||||||
|
<a href="../../index.jsp" class="btn">返回首页</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
58
web/user/form.jsp
Normal file
58
web/user/form.jsp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>用户表单 - Struts2 Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container { max-width: 600px; margin: 0 auto; }
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
h2 { color: #f5576c; margin-bottom: 20px; }
|
||||||
|
.form-group { margin-bottom: 20px; }
|
||||||
|
label { display: block; margin-bottom: 8px; color: #555; }
|
||||||
|
input { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; }
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: #f5576c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="card">
|
||||||
|
<h2>👤 用户信息提交</h2>
|
||||||
|
<s:form action="submitUser">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>用户名</label>
|
||||||
|
<s:textfield name="username"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>邮箱</label>
|
||||||
|
<s:textfield name="email"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>电话</label>
|
||||||
|
<s:textfield name="phone"/>
|
||||||
|
</div>
|
||||||
|
<button type="submit">提交</button>
|
||||||
|
</s:form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
94
web/user/login.jsp
Normal file
94
web/user/login.jsp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>用户登录 - Struts2 Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.login-box {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
h2 { color: #333; margin-bottom: 30px; text-align: center; }
|
||||||
|
.form-group { margin-bottom: 20px; }
|
||||||
|
label { display: block; margin-bottom: 8px; color: #555; font-weight: 500; }
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
input:focus { border-color: #667eea; outline: none; }
|
||||||
|
.error { color: #e53935; font-size: 0.85em; margin-top: 5px; }
|
||||||
|
.error-field { border-color: #e53935 !important; }
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
button:hover { background: #764ba2; }
|
||||||
|
.tips {
|
||||||
|
background: #e8f5e9;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
a { color: #667eea; text-decoration: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-box">
|
||||||
|
<h2>🔐 用户登录</h2>
|
||||||
|
|
||||||
|
<div class="tips">
|
||||||
|
<strong>测试账号:</strong> admin / 123456
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Struts2 表单标签 -->
|
||||||
|
<s:form action="login" method="post" namespace="/">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>用户名</label>
|
||||||
|
<s:textfield name="username" placeholder="请输入用户名" cssClass="%{hasErrors('username') ? 'error-field' : ''}"/>
|
||||||
|
<s:fielderror fieldName="username"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>密码</label>
|
||||||
|
<s:password name="password" placeholder="请输入密码" showPassword="true"/>
|
||||||
|
<s:fielderror fieldName="password"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 显示 Action 错误消息 -->
|
||||||
|
<s:actionerror/>
|
||||||
|
|
||||||
|
<button type="submit">登录</button>
|
||||||
|
</s:form>
|
||||||
|
|
||||||
|
<p style="text-align:center;margin-top:20px;">
|
||||||
|
<a href="index.jsp">← 返回首页</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
44
web/user/success.jsp
Normal file
44
web/user/success.jsp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>登录成功 - Struts2 Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 15px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
h1 { color: #11998e; font-size: 3em; margin-bottom: 20px; }
|
||||||
|
p { color: #666; font-size: 1.2em; margin-bottom: 30px; }
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 30px;
|
||||||
|
background: #11998e;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
.btn:hover { transform: scale(1.05); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>✅ 登录成功!</h1>
|
||||||
|
<p>欢迎回来,<s:property value="username"/>!</p>
|
||||||
|
<a href="../index.jsp" class="btn">返回首页</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
103
web/validation/form.jsp
Normal file
103
web/validation/form.jsp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>表单验证 - Struts2 Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container { max-width: 600px; margin: 0 auto; }
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 15px 50px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
h2 { color: #f5576c; margin-bottom: 20px; }
|
||||||
|
.form-group { margin-bottom: 20px; }
|
||||||
|
label { display: block; margin-bottom: 8px; color: #555; font-weight: 500; }
|
||||||
|
input, textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
input:focus, textarea:focus { border-color: #f5576c; outline: none; }
|
||||||
|
.error { color: #e53935; font-size: 0.85em; margin-top: 5px; }
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
background: #f5576c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover { background: #e91e63; }
|
||||||
|
.info {
|
||||||
|
background: #e3f2fd;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #e91e63;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="card">
|
||||||
|
<h2>📝 表单验证示例</h2>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<strong>验证方式:</strong>
|
||||||
|
<ul style="margin:10px 0 0 20px;">
|
||||||
|
<li>在 Action 中重写 <code>validate()</code> 方法</li>
|
||||||
|
<li>使用 XML 验证文件 (ActionName-validation.xml)</li>
|
||||||
|
<li>使用注解验证 (@Required, @IntRange 等)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<s:form action="validate" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>用户名 (3-20字符)</label>
|
||||||
|
<s:textfield name="username"/>
|
||||||
|
<s:fielderror fieldName="username"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>邮箱</label>
|
||||||
|
<s:textfield name="email"/>
|
||||||
|
<s:fielderror fieldName="email"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>年龄 (18-60)</label>
|
||||||
|
<s:textfield name="age"/>
|
||||||
|
<s:fielderror fieldName="age"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>个人简介</label>
|
||||||
|
<s:textarea name="bio" rows="4"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<s:actionerror/>
|
||||||
|
<button type="submit">提交验证</button>
|
||||||
|
</s:form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
54
web/validation/success.jsp
Normal file
54
web/validation/success.jsp
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>验证成功 - Struts2 Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 15px;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
h1 { color: #11998e; margin-bottom: 20px; }
|
||||||
|
.data {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: left;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.data p { margin: 10px 0; }
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 30px;
|
||||||
|
background: #11998e;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>✅ 验证通过!</h1>
|
||||||
|
<div class="data">
|
||||||
|
<p><strong>用户名:</strong> <s:property value="username"/></p>
|
||||||
|
<p><strong>邮箱:</strong> <s:property value="email"/></p>
|
||||||
|
<p><strong>年龄:</strong> <s:property value="age"/></p>
|
||||||
|
<p><strong>简介:</strong> <s:property value="bio"/></p>
|
||||||
|
</div>
|
||||||
|
<a href="form.jsp" class="btn">继续测试</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user