feat: finish bilingual session auth learning lab

This commit is contained in:
Codex
2026-03-24 09:18:13 +08:00
parent 4cc4c26f2b
commit 5e318cb7f4
27 changed files with 1911 additions and 1079 deletions

View File

@@ -0,0 +1,44 @@
package com.demo.action;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
public class DashboardAction extends ActionSupport implements SessionAware {
private Map<String, Object> session;
private String displayName;
private String role;
private String loginTime;
@Override
public String execute() {
displayName = value(LoginAction.SESSION_USER, "ops-admin");
role = value(LoginAction.SESSION_ROLE, "admin");
loginTime = value(LoginAction.SESSION_LOGIN_TIME, "--");
return SUCCESS;
}
private String value(String key, String fallback) {
Object value = session == null ? null : session.get(key);
return value == null ? fallback : String.valueOf(value);
}
public String getDisplayName() {
return displayName;
}
public String getRole() {
return role;
}
public String getLoginTime() {
return loginTime;
}
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}

View File

@@ -27,11 +27,11 @@ public class FileUploadAction extends ActionSupport {
}
if (fileCount == 0) {
addActionError("Select at least one file before submitting the demo.");
addActionError("请至少选择一个文件再提交。 / Select at least one file before submitting the demo.");
return INPUT;
}
summary = "This demo captures upload metadata only. It does not persist files to disk, which keeps the sample safe for classroom use.";
summary = "metadata-only";
return SUCCESS;
}

View File

@@ -1,20 +1,25 @@
package com.demo.action;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
public class LoginAction extends ActionSupport {
public class LoginAction extends ActionSupport implements SessionAware {
public static final String SESSION_USER = "demoUser";
public static final String SESSION_ROLE = "demoRole";
public static final String SESSION_LOGIN_TIME = "demoLoginTime";
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private Map<String, Object> session;
private String username;
private String password;
private String displayName;
private String role;
private String loginTime;
private String recommendation;
@Override
public String execute() {
@@ -26,14 +31,15 @@ public class LoginAction extends ActionSupport {
}
if ("admin".equals(username) && "123456".equals(password)) {
displayName = "System Demo Admin";
role = "Administrator";
displayName = "ops-admin";
loginTime = LocalDateTime.now().format(TIME_FORMATTER);
recommendation = "Continue with the user form, validation sample, or upload flow to explore the rest of the demo.";
session.put(SESSION_USER, displayName);
session.put(SESSION_ROLE, "admin");
session.put(SESSION_LOGIN_TIME, loginTime);
return SUCCESS;
}
addActionError("Invalid demo credentials. Use admin / 123456.");
addActionError("演示账号不正确,请使用 admin / 123456。 / Invalid demo credentials. Use admin / 123456.");
return INPUT;
}
@@ -43,10 +49,10 @@ public class LoginAction extends ActionSupport {
return;
}
if (username == null || username.length() < 3) {
addFieldError("username", "Username must be at least 3 characters.");
addFieldError("username", "用户名至少 3 个字符。 / Username must be at least 3 characters.");
}
if (password == null || password.length() < 6) {
addFieldError("password", "Password must be at least 6 characters.");
addFieldError("password", "密码至少 6 个字符。 / Password must be at least 6 characters.");
}
}
@@ -79,15 +85,12 @@ public class LoginAction extends ActionSupport {
return displayName;
}
public String getRole() {
return role;
}
public String getLoginTime() {
return loginTime;
}
public String getRecommendation() {
return recommendation;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}

View File

@@ -0,0 +1,26 @@
package com.demo.action;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
public class LogoutAction extends ActionSupport implements SessionAware {
private Map<String, Object> session;
@Override
public String execute() {
if (session != null) {
session.remove(LoginAction.SESSION_USER);
session.remove(LoginAction.SESSION_ROLE);
session.remove(LoginAction.SESSION_LOGIN_TIME);
}
return SUCCESS;
}
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}

View File

@@ -14,6 +14,7 @@ public class UserAction extends ActionSupport {
private String phone;
private String submittedAt;
private String profileStage;
private boolean profileReady;
public String submit() {
username = normalize(username);
@@ -25,22 +26,23 @@ public class UserAction extends ActionSupport {
}
submittedAt = LocalDateTime.now().format(TIME_FORMATTER);
profileStage = (phone != null && phone.length() >= 7) ? "Profile ready for follow-up demos." : "Profile captured. Add a stronger phone number next time.";
profileReady = phone != null && phone.length() >= 7;
profileStage = profileReady ? "ready" : "review";
return SUCCESS;
}
private boolean isValid() {
boolean valid = true;
if (username == null || username.length() < 3) {
addFieldError("username", "Username must be at least 3 characters.");
addFieldError("username", "用户名至少 3 个字符。 / Username must be at least 3 characters.");
valid = false;
}
if (email == null || !email.contains("@")) {
addFieldError("email", "Enter a valid email address.");
addFieldError("email", "请输入有效邮箱。 / Enter a valid email address.");
valid = false;
}
if (phone == null || phone.replaceAll("[^0-9]", "").length() < 7) {
addFieldError("phone", "Enter at least 7 digits for the phone number.");
addFieldError("phone", "手机号至少 7 位数字。 / Enter at least 7 digits for the phone number.");
valid = false;
}
return valid;
@@ -81,4 +83,8 @@ public class UserAction extends ActionSupport {
public String getProfileStage() {
return profileStage;
}
public boolean isProfileReady() {
return profileReady;
}
}

View File

@@ -15,13 +15,15 @@ public class ValidationAction extends ActionSupport {
private String bio;
private String scoreBand;
private String submittedAt;
private boolean seniorTrack;
@Override
public String execute() {
username = normalize(username);
email = normalize(email);
bio = normalize(bio);
scoreBand = age >= 30 ? "Mid-career operator profile" : "Early-career operator profile";
seniorTrack = age >= 30;
scoreBand = seniorTrack ? "mid" : "early";
submittedAt = LocalDateTime.now().format(TIME_FORMATTER);
return SUCCESS;
}
@@ -29,16 +31,16 @@ public class ValidationAction extends ActionSupport {
@Override
public void validate() {
if (username == null || username.trim().length() < 3 || username.trim().length() > 20) {
addFieldError("username", "Username must be between 3 and 20 characters.");
addFieldError("username", "用户名长度需在 3 到 20 之间。 / Username must be between 3 and 20 characters.");
}
if (email == null || !email.contains("@") || email.indexOf('@') == email.length() - 1) {
addFieldError("email", "Enter a valid email address.");
addFieldError("email", "请输入有效邮箱。 / Enter a valid email address.");
}
if (age == null || age < 18 || age > 60) {
addFieldError("age", "Age must be between 18 and 60.");
addFieldError("age", "年龄需在 18 到 60 之间。 / Age must be between 18 and 60.");
}
if (bio != null && bio.trim().length() > 240) {
addFieldError("bio", "Bio must stay under 240 characters.");
addFieldError("bio", "简介不能超过 240 个字符。 / Bio must stay under 240 characters.");
}
}
@@ -85,4 +87,8 @@ public class ValidationAction extends ActionSupport {
public String getSubmittedAt() {
return submittedAt;
}
public boolean isSeniorTrack() {
return seniorTrack;
}
}

View File

@@ -0,0 +1,19 @@
package com.demo.action.interceptor;
import com.demo.action.LoginAction;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import java.util.Map;
public class AuthInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
Map<String, Object> session = invocation.getInvocationContext().getSession();
if (session != null && session.get(LoginAction.SESSION_USER) != null) {
return invocation.invoke();
}
return "login";
}
}

View File

@@ -4,13 +4,30 @@
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.devMode" value="true"/>
<constant name="struts.devMode" value="false"/>
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<constant name="struts.action.extension" value="action"/>
<package name="default" namespace="/" extends="json-default">
<interceptors>
<interceptor name="auth" class="com.demo.action.interceptor.AuthInterceptor"/>
<interceptor-stack name="secureStack">
<interceptor-ref name="auth"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<global-results>
<result name="login" type="redirectAction">loginPage</result>
</global-results>
<package name="default" namespace="/" extends="struts-default">
<action name="index">
<result>/index.jsp</result>
<result>/WEB-INF/views/index.jsp</result>
</action>
<action name="loginPage">
<result>/WEB-INF/views/user/login.jsp</result>
</action>
<action name="hello" class="com.demo.action.HelloAction" method="execute">
@@ -18,18 +35,44 @@
</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>
<result name="success" type="redirectAction">dashboard</result>
<result name="input">/WEB-INF/views/user/login.jsp</result>
</action>
<action name="dashboard" class="com.demo.action.DashboardAction" method="execute">
<interceptor-ref name="secureStack"/>
<result>/WEB-INF/views/user/dashboard.jsp</result>
</action>
<action name="logout" class="com.demo.action.LogoutAction" method="execute">
<result type="redirectAction">loginPage</result>
</action>
<action name="userFormPage">
<interceptor-ref name="secureStack"/>
<result>/WEB-INF/views/user/form.jsp</result>
</action>
<action name="validationPage">
<interceptor-ref name="secureStack"/>
<result>/WEB-INF/views/validation/form.jsp</result>
</action>
<action name="uploadPage">
<interceptor-ref name="secureStack"/>
<result>/WEB-INF/views/upload/index.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>
<interceptor-ref name="secureStack"/>
<result name="success">/WEB-INF/views/user/success.jsp</result>
<result name="input">/WEB-INF/views/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>
<interceptor-ref name="secureStack"/>
<result name="success">/WEB-INF/views/upload/success.jsp</result>
<result name="input">/WEB-INF/views/upload/index.jsp</result>
</action>
<action name="ajax" class="com.demo.action.AjaxAction" method="execute">
@@ -37,12 +80,13 @@
</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>
<interceptor-ref name="secureStack"/>
<result name="success">/WEB-INF/views/validation/success.jsp</result>
<result name="input">/WEB-INF/views/validation/form.jsp</result>
</action>
</package>
<package name="rest" namespace="/api" extends="struts-default">
<package name="rest" namespace="/api" extends="json-default">
<action name="users" class="com.demo.action.rest.UserRestAction" method="execute">
<result type="json"/>
</action>