feat(structs): add validation and error-flow lab

This commit is contained in:
likingcode
2026-03-10 06:59:08 +08:00
parent da75afbc93
commit e0afbdc002
5 changed files with 152 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
package com.example.struts2;
import com.opensymphony.xwork2.ActionSupport;
public class ValidationLabAction extends ActionSupport {
private String username;
private String email;
private Integer age;
private String resultMessage;
@Override
public String execute() {
return SUCCESS;
}
public String submit() {
resultMessage = String.format("提交成功username=%s, email=%s, age=%s", username, email, age);
return SUCCESS;
}
@Override
public void validate() {
if ("submit".equals(getActionName())) {
if (username == null || username.trim().length() < 2) {
addFieldError("username", "用户名至少 2 个字符");
}
if (email == null || !email.contains("@")) {
addFieldError("email", "邮箱格式不正确");
}
if (age == null || age < 1 || age > 120) {
addFieldError("age", "年龄必须在 1 到 120 之间");
}
}
}
private String actionName;
public String getActionName() { return actionName; }
public void setActionName(String actionName) { this.actionName = actionName; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getResultMessage() { return resultMessage; }
}

View File

@@ -73,6 +73,16 @@
<result>/ognl-lab.jsp</result>
</action>
<action name="validation" class="com.example.struts2.ValidationLabAction">
<result>/validation-lab.jsp</result>
</action>
<action name="validation_submit" class="com.example.struts2.ValidationLabAction" method="submit">
<param name="actionName">submit</param>
<result>/validation-lab.jsp</result>
<result name="input">/validation-lab.jsp</result>
</action>
<action name="interceptor_api" class="com.example.struts2.InterceptorDemoAction">
<interceptor-ref name="apiStack"/>
<result>/interceptor-demo.jsp</result>

View File

@@ -66,6 +66,11 @@
<p>可视化体验普通字段、嵌套对象、多选列表是如何被 Struts2 自动绑定到 Action 的。</p>
<a href="ognl" class="btn purple">进入实验室</a>
</div>
<div class="card">
<h3>⚠️ 验证与错误流实验</h3>
<p>通过错误示例/正确示例切换直观看到字段错误、input 返回和成功路径的区别。</p>
<a href="validation" class="btn">进入实验室</a>
</div>
</div>
<div class="timeline">

View File

@@ -67,6 +67,11 @@
<p>通过真正可交互的表单观察 Struts2 的字段绑定、嵌套对象绑定和集合绑定。</p>
<a class="btn btn-purple" href="ognl">打开 OGNL 实验室</a>
</div>
<div class="card">
<h3>⚠️ 验证与错误流实验</h3>
<p>学习为什么校验失败不会直接 500而是回到 input 页面并显示字段级错误。</p>
<a class="btn" href="validation">打开验证实验室</a>
</div>
</div>
<h2>📚 学习路径</h2>

View File

@@ -0,0 +1,86 @@
<%@ 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</title>
<style>
body { font-family: Arial, sans-serif; max-width: 900px; margin: 40px auto; padding: 20px; background:#f7f8fa; }
h1 { color:#c0392b; }
.card { background:#fff; border-radius:12px; padding:20px; margin:18px 0; box-shadow:0 4px 14px rgba(0,0,0,.06); }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; margin:20px 0; }
.quick { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:12px; }
button { background:#c0392b; color:white; border:none; padding:10px 16px; border-radius:6px; cursor:pointer; }
input { padding:10px; width:100%; box-sizing:border-box; }
.field { margin:12px 0; }
.ok { background:#eafaf1; color:#1e8449; padding:12px; border-radius:8px; }
</style>
</head>
<body>
<h1>⚠️ 验证与错误流实验室</h1>
<div class="lab">
<strong>实验任务卡</strong>
<ul>
<li>先点“填入错误示例”,提交后观察字段级错误是如何显示的</li>
<li>再点“填入正确示例”,观察 Action 成功后的返回</li>
<li>思考:为什么 Struts2 的校验失败会回到 input 页,而不是直接报 500</li>
</ul>
</div>
<div class="card">
<div class="quick">
<button type="button" onclick="fillBad()">填入错误示例</button>
<button type="button" onclick="fillGood()">填入正确示例</button>
</div>
<s:if test="resultMessage != null">
<div class="ok"><s:property value="resultMessage"/></div>
</s:if>
<s:form action="validation_submit" method="post">
<div class="field">
<label>用户名</label>
<s:textfield name="username"/>
<s:fielderror fieldName="username" cssStyle="color:#e74c3c;font-size:12px;"/>
</div>
<div class="field">
<label>邮箱</label>
<s:textfield name="email"/>
<s:fielderror fieldName="email" cssStyle="color:#e74c3c;font-size:12px;"/>
</div>
<div class="field">
<label>年龄</label>
<s:textfield name="age"/>
<s:fielderror fieldName="age" cssStyle="color:#e74c3c;font-size:12px;"/>
</div>
<button type="submit">提交实验</button>
</s:form>
</div>
<div class="card">
<h3>学习点</h3>
<ul>
<li>字段错误通过 <code>addFieldError()</code> 收集</li>
<li>JSP 使用 <code>&lt;s:fielderror&gt;</code> 精准展示错误</li>
<li>成功路径与失败路径在同一个页面闭环,对学习最直观</li>
</ul>
</div>
<p><a href="learn">← 返回学习中心</a></p>
<script>
function fillBad() {
document.querySelector('input[name="username"]').value = 'a';
document.querySelector('input[name="email"]').value = 'bad-email';
document.querySelector('input[name="age"]').value = '-1';
}
function fillGood() {
document.querySelector('input[name="username"]').value = 'zhangsan';
document.querySelector('input[name="email"]').value = 'zhangsan@example.com';
document.querySelector('input[name="age"]').value = '22';
}
</script>
</body>
</html>