feat: expand struts demo lab
This commit is contained in:
@@ -1,101 +1,51 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>AJAX + JSON - Struts2 学习</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AJAX and JSON Guide</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; }
|
||||
body { margin: 0; padding: 24px; font-family: "Aptos", "Segoe UI", sans-serif; background: linear-gradient(135deg, #122c63, #1464c7); }
|
||||
.shell { max-width: 980px; margin: 0 auto; background: rgba(255,255,255,0.96); border-radius: 28px; padding: 28px; box-shadow: 0 24px 60px rgba(0,0,0,0.18); }
|
||||
.eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: 0.12em; color: #1464c7; font-weight: 800; }
|
||||
h1, h2 { margin: 10px 0 12px; }
|
||||
p, li { color: #53667d; line-height: 1.9; }
|
||||
pre { background: #101827; color: #d9e7ff; padding: 18px; border-radius: 18px; overflow-x: auto; }
|
||||
.links { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
|
||||
.btn { display: inline-flex; padding: 10px 14px; border-radius: 999px; text-decoration: none; font-weight: 700; background: #e8f2ff; color: #1464c7; }
|
||||
</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;
|
||||
<div class="shell">
|
||||
<div class="eyebrow">Guide</div>
|
||||
<h1>Returning JSON from Struts2 actions</h1>
|
||||
<p>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.</p>
|
||||
|
||||
<h2>What the action returns</h2>
|
||||
<ul>
|
||||
<li>A <code>success</code> flag.</li>
|
||||
<li>A short message describing the payload.</li>
|
||||
<li>Sample user records that can be consumed by AJAX.</li>
|
||||
</ul>
|
||||
|
||||
<pre>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;
|
||||
}
|
||||
}</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 class="links">
|
||||
<a class="btn" href="../../ajax">Open action JSON</a>
|
||||
<a class="btn" href="../../api/users">Open REST JSON</a>
|
||||
<a class="btn" href="../../index.jsp">Back to portal</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,168 +1,53 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hello World - Struts2 学习</title>
|
||||
<title>Hello Action Guide</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; }
|
||||
body { margin: 0; padding: 24px; font-family: "Aptos", "Segoe UI", sans-serif; background: linear-gradient(135deg, #1464c7, #3a8dff); }
|
||||
.shell { max-width: 980px; margin: 0 auto; background: rgba(255,255,255,0.96); border-radius: 28px; padding: 28px; box-shadow: 0 24px 60px rgba(0,0,0,0.18); }
|
||||
.eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: 0.12em; color: #1464c7; font-weight: 800; }
|
||||
h1, h2 { margin: 10px 0 12px; }
|
||||
p, li { color: #53667d; line-height: 1.9; }
|
||||
pre { background: #101827; color: #d9e7ff; padding: 18px; border-radius: 18px; overflow-x: auto; }
|
||||
.links { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
|
||||
.btn { display: inline-flex; padding: 10px 14px; border-radius: 999px; text-decoration: none; font-weight: 700; background: #e8f2ff; color: #1464c7; }
|
||||
</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>
|
||||
<div class="shell">
|
||||
<div class="eyebrow">Guide</div>
|
||||
<h1>Hello action walkthrough</h1>
|
||||
<p>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.</p>
|
||||
|
||||
<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;
|
||||
<h2>Flow</h2>
|
||||
<ul>
|
||||
<li>The browser requests <code>/hello</code> or <code>/hello?name=Team</code>.</li>
|
||||
<li>Struts populates the <code>name</code> property on <code>HelloAction</code>.</li>
|
||||
<li><code>execute()</code> builds a view message and returns <code>SUCCESS</code>.</li>
|
||||
<li>The framework resolves the success result to <code>/hello.jsp</code>.</li>
|
||||
</ul>
|
||||
|
||||
<span class="keyword">import</span> com.opensymphony.xwork2.ActionSupport;
|
||||
<h2>Minimal action shape</h2>
|
||||
<pre>public class HelloAction extends ActionSupport {
|
||||
private String name;
|
||||
private String message;
|
||||
|
||||
<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>;
|
||||
public String execute() {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
name = "World";
|
||||
}
|
||||
message = <span class="string">"你好, "</span> + name + <span class="string">"! 欢迎学习 Struts2!"</span>;
|
||||
<span class="keyword">return</span> SUCCESS; <span class="comment">// 返回 "success"</span>
|
||||
message = "Hello, " + name + "!";
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
<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 class="links">
|
||||
<a class="btn" href="../../hello?name=Platform%20Team">Run hello action</a>
|
||||
<a class="btn" href="../../index.jsp">Back to portal</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,173 +1,54 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>数据封装 - Struts2 学习</title>
|
||||
<title>Model Binding Guide</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; }
|
||||
body { margin: 0; padding: 24px; font-family: "Aptos", "Segoe UI", sans-serif; background: linear-gradient(135deg, #7c3aed, #a855f7); }
|
||||
.shell { max-width: 980px; margin: 0 auto; background: rgba(255,255,255,0.96); border-radius: 28px; padding: 28px; box-shadow: 0 24px 60px rgba(0,0,0,0.18); }
|
||||
.eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: 0.12em; color: #7c3aed; font-weight: 800; }
|
||||
h1, h2 { margin: 10px 0 12px; }
|
||||
p, li { color: #5e5570; line-height: 1.9; }
|
||||
pre { background: #101827; color: #d9e7ff; padding: 18px; border-radius: 18px; overflow-x: auto; }
|
||||
.links { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
|
||||
.btn { display: inline-flex; padding: 10px 14px; border-radius: 999px; text-decoration: none; font-weight: 700; background: #f0e9ff; color: #7c3aed; }
|
||||
</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>
|
||||
<div class="shell">
|
||||
<div class="eyebrow">Guide</div>
|
||||
<h1>Property binding vs model-driven binding</h1>
|
||||
<p>This note helps explain how Struts2 turns request parameters into action state. It works well alongside the user form and validation examples.</p>
|
||||
|
||||
<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;
|
||||
<h2>Property-driven</h2>
|
||||
<ul>
|
||||
<li>Define simple fields directly on the action.</li>
|
||||
<li>Expose setters and getters for each form field.</li>
|
||||
<li>Great for short demos and small forms.</li>
|
||||
</ul>
|
||||
|
||||
<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;
|
||||
<h2>Model-driven</h2>
|
||||
<ul>
|
||||
<li>Return a dedicated model object from <code>getModel()</code>.</li>
|
||||
<li>Keep request data separate from action orchestration logic.</li>
|
||||
<li>Better for larger forms and nested objects.</li>
|
||||
</ul>
|
||||
|
||||
<pre>public class UserAction extends ActionSupport implements ModelDriven<User> {
|
||||
private final User user = new User();
|
||||
|
||||
@Override
|
||||
public User getModel() {
|
||||
return user;
|
||||
}
|
||||
}</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 class="links">
|
||||
<a class="btn" href="../../user/form.jsp">Open user form</a>
|
||||
<a class="btn" href="../../index.jsp">Back to portal</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,102 +1,50 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>文件上传 - Struts2 学习</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Upload Guide</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; }
|
||||
body { margin: 0; padding: 24px; font-family: "Aptos", "Segoe UI", sans-serif; background: linear-gradient(135deg, #0ea5e9, #38bdf8); }
|
||||
.shell { max-width: 980px; margin: 0 auto; background: rgba(255,255,255,0.96); border-radius: 28px; padding: 28px; box-shadow: 0 24px 60px rgba(0,0,0,0.18); }
|
||||
.eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: 0.12em; color: #0284c7; font-weight: 800; }
|
||||
h1, h2 { margin: 10px 0 12px; }
|
||||
p, li { color: #53667d; line-height: 1.9; }
|
||||
pre { background: #101827; color: #d9e7ff; padding: 18px; border-radius: 18px; overflow-x: auto; }
|
||||
.links { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
|
||||
.btn { display: inline-flex; padding: 10px 14px; border-radius: 999px; text-decoration: none; font-weight: 700; background: #edf7ff; color: #0284c7; }
|
||||
</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;
|
||||
<div class="shell">
|
||||
<div class="eyebrow">Guide</div>
|
||||
<h1>Upload demo guide</h1>
|
||||
<p>The refreshed upload flow is deliberately safer for demo use. It shows multipart binding and metadata capture without persisting files.</p>
|
||||
|
||||
<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>
|
||||
<h2>Core binding fields</h2>
|
||||
<ul>
|
||||
<li><code>File upload</code> for the primary file object.</li>
|
||||
<li><code>String uploadFileName</code> for the original filename.</li>
|
||||
<li><code>String uploadContentType</code> for the MIME type.</li>
|
||||
<li>Optional lists for multiple files.</li>
|
||||
</ul>
|
||||
|
||||
<pre>public class FileUploadAction extends ActionSupport {
|
||||
private File upload;
|
||||
private String uploadFileName;
|
||||
private String uploadContentType;
|
||||
|
||||
public String execute() {
|
||||
// demo keeps metadata only
|
||||
return SUCCESS;
|
||||
}
|
||||
}</pre>
|
||||
|
||||
<div class="links">
|
||||
<a class="btn" href="../../upload/index.jsp">Open upload demo</a>
|
||||
<a class="btn" href="../../index.jsp">Back to portal</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user