
简介这是一份基于Struts2框架的登录注册与用户信息管理系统项目源码面向Java Web初学者及需要掌握MVC分层开发的开发者适合用于课程设计、毕业设计或框架入门练习。项目涵盖用户登录、注册、信息增删改查等基础且完整的功能闭环并配合SQL Server数据库脚本与图解文档便于从代码到运行环境全面理解实现细节。资源共74个文件压缩包约4.27MB主要包含Java源码与class字节码、Struts2配置文件、JSP页面、jar依赖库、数据库MDF/LDF文件以及项目说明文档和图片素材结构与典型Eclipse Web工程一致可按目录逐层学习。目前已有843人浏览/学习。借助核心Action、拦截器、struts.xml配置等代码实例读者可直观掌握Struts2请求流转、表单验证、会话跟踪和数据库交互等关键知识点对后续独立开发Java Web项目具有实际参考价值。1. Struts2 做的登录注册与用户管理为什么这个老框架还值得动手做一遍「Struts2 做的登陆注册及用户信息管理系统」这个标题在搜索引擎里常年是 Java Web 课程设计和毕业设计的经典项目名。说白了它就是用 Struts2 把控制层接起来登录、注册、用户列表、增删改查再配上 JSP 页面和一张 MySQL 用户表跑成一个能演示、能答辩、能作为老项目维护起点的小系统。很多人第一反应是「都什么年代了还学 Struts2」但真去接手过遗留系统的人都明白银行内网、政企 OA、十来年没动的老工程里Struts2 还在跑。这套系统的意义不在于框架本身多先进而在于它的拦截器、值栈和 Action 生命周期能帮你把 Java Web 的控制层演进脉络摸清回头再看 Spring MVC 会有一种「原来如此」的通透感。适合两类人要交课程设计的学生和刚接手老项目的维护者。2. 搭出 Struts2 工程骨架依赖、Filter 与 struts.xml 的最小配置2.1 目录结构与 Maven 依赖先定好三条路线做这个系统之前先得想清楚数据层用什么。常见做法有三种纯 JDBC、MyBatis、Hibernate。对课程设计和快速跑通来说JDBC 依赖最少、出错最好排查我一般先用 JDBC 把业务跑通再换成 ORM 也不迟Hibernate 虽然省 SQL但和 Struts2 一起用时依赖冲突多后面避坑章会专门讲。这篇按 JDBC 版本为主线。不管哪条路线工程骨架都是 Maven 标准结构src/main/java ├─ action # Struts2 Action 类对应控制层 ├─ service # 业务逻辑 ├─ dao # 数据访问 ├─ entity # User 实体 └─ util # DBUtil、MD5 工具 src/main/resources └─ struts.xml # Struts2 核心配置 src/main/webapp ├─ WEB-INF/web.xml └─ WEB-INF/pages # 登录、注册、用户列表 JSPpom.xml 里最关键的几个依赖先列出来。Struts2 用 2.5.x注意 2.5 起不再兼容 JDK 1.6环境最好 JDK 1.8 Tomcat 8.5/9。groupIdartifactId用途说明org.apache.strutsstruts2-coreStruts2 核心库版本选 2.5.xmysqlmysql-connector-javaJDBC 驱动版本和 MySQL 对应javax.servletjavax.servlet-apiprovided 作用域Tomcat 自带javax.servletjstlJSP 标准标签库配合 Struts2 标签使用org.apache.commonscommons-dbutils可选简化 JDBC 结果集到 Bean 的封装commons-dbutils 不是 Struts2 的一部分但它能把 ResultSet 转成 User 对象比手写 getString 再 set 一遍省事做管理端列表时很实用。如果不想要这个依赖手写 Bean 映射也就几十行看个人取舍。2.2 web.xml 里的 Struts2 入口别再写 FilterDispatcherStruts2 的核心入口是过滤器不是 Servlet。2.3 之前的老教程会写 FilterDispatcher从 2.3 开始推荐 StrutsPrepareAndExecuteFilter2.5 里 FilterDispatcher 已经移除照老教程抄必然启动报 ClassNotFoundException。web.xml 这样写filter filter-namestruts2/filter-name filter-classorg.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter/filter-class /filter filter-mapping filter-namestruts2/filter-name url-pattern/*/url-pattern /filter-mappingurl-pattern 必须写成 /*。有人习惯写成 *.action 或.do结果访问 /login.jsp 时静态页面被容器直接处理、Action 请求又进不了过滤器就会得到 404 或者一片空白。Struts2 的过滤器会先接管所有请求再根据 struts.xml 里的 action 映射和后缀决定怎么分发所以 /是唯一不会漏的写法。如果这里配错了后面所有 action 都白写。2.3 struts.xml 的包、命名空间与 action 映射struts.xml 放在 classpath 根目录即可Maven 工程就放 src/main/resources。最小配置如下?xml version1.0 encodingUTF-8? !DOCTYPE struts PUBLIC -//Apache Software Foundation//DTD Struts Configuration 2.5//EN http://struts.apache.org/dtds/struts-2.5.dtd struts constant namestruts.devMode valuefalse / constant namestruts.i18n.encoding valueUTF-8 / constant namestruts.action.extension valueaction / package namedefault namespace/ extendsstruts-default action namelogin classaction.UserAction methodlogin result namesuccess typeredirectActionuser/list/result result nameinput/WEB-INF/pages/login.jsp/result /action /package /struts三个常量分别对应开发模式开关上线必须 falsetrue 时修改配置文件会热加载但也会暴露敏感错误、请求编码、Action 访问后缀。devMode 在排错时可以先开 true看到的具体堆栈比黑匣子强得多。package 的 name 随意namespace 代表 URL 前缀extends 必须继承 struts-default否则拦截器、默认 result 全都没有。action 的 name 就是访问路径class 指向 Action 类全限定名。login 成功后用 redirectAction 重定向到 user/list避免用户按 F5 重复提交表单失败返回 input回登录页并回显错误。写到这里工程骨架就齐了下一步把登录注册这层业务填进去。3. 登录注册这层业务Action、校验、Session 与密码处理3.1 用 ActionSupport 写登录与注册setter 注入是黑匣子Struts2 的 Action 是一个普通 POJO请求参数通过 setter 注入。新手最容易在这里翻车JSP 表单里写了 usernameAction 里却没有 setUsername 方法结果页面上永远显示「用户名不能为空」。常见做法是继承 ActionSupport既拿到 validate() 校验框架又获得 getText() 等工具方法。核心代码public class UserAction extends ActionSupport { private UserService userService new UserService(); private String username; private String password; private User user; public String login() { User dbUser userService.findByUsername(username); if (dbUser ! null dbUser.getPassword().equals( MD5Util.md5WithSalt(password, dbUser.getSalt()))) { MapString, Object session ActionContext.getContext().getSession(); session.put(loginUser, dbUser); return SUCCESS; } addActionError(用户名或密码错误); return INPUT; } public String register() { if (userService.exists(username)) { addActionError(用户名已存在); return INPUT; } String salt MD5Util.randomSalt(); user.setSalt(salt); user.setPassword(MD5Util.md5WithSalt(user.getPassword(), salt)); userService.save(user); return SUCCESS; } // username、password 的属性必须有 setter / getter }逻辑说明login() 先按用户名查库再比对加盐后的密码比对成功就把用户对象塞进 Session返回 SUCCESS 走重定向失败返回 INPUTStruts2 会把错误信息通过 addActionError 带到 JSP。register() 先查重再生成随机盐并加密最后入库。参数说明login() 方法名要和 struts.xml 里 对应返回字符串 SUCCESS/INPUT 和 result 的 name 对应这是 Struts2 的控制流约定绕不过去。ActionContext.getContext().getSession() 是 Struts2 对原生 Session 的一层 Map 封装在 Action 里用它比直接拿 HttpServletRequest 更干净。3.2 validate() 校验与 MD5 加盐参数怎么设如果只在 login() 里做判断表单为空、密码格式不对这些问题会让代码越来越臃肿。ActionSupport 提供 validate()它在 execute 或自定义业务方法执行前自动调用。校验失败时 Struts2 自动返回 input不再进入业务方法。Override public void validate() { if (username null || username.trim().isEmpty()) { addFieldError(username, 用户名不能为空); } if (password null || password.length() 6) { addFieldError(password, 密码至少 6 位); } if (password ! null !password.equals(confirmPassword)) { addFieldError(confirmPassword, 两次密码不一致); } }注意 validate() 对所有 Action 方法生效。如果 register 要校验确认密码而 login 不需要就把校验写在 validateRegister() 里这是 Struts2 的约定命名validateXxx() 只校验名为 xxx 的方法。字段错误会通过 s:fielderror 标签回显在 JSP 对应位置比弹窗友好得多。密码存储这块早期项目直接明文存库管理后台打开就是一片裸密码太吓人。常见做法是 MD5 加盐每个用户注册时生成随机盐存到 user 表的 salt 字段密码存 md5(salt password)。MD5 现在做碰撞已经不难练习项目够用上生产建议换 BCrypt这词记住了答辩被问「密码安全怎么做的」能答上来。3.3 登录态用 Session 还是拦截器先讲清差别再选登录状态存 Session 是最直接的做法登录成功 session.put(loginUser, user)登出时 session.remove(loginUser)JSP 里判断 session 里有没有 loginUser 决定显示欢迎语还是登录按钮。但 Session 只管「记住你是谁」不管「哪些页面必须登录才能看」。如果完全不设防用户直接在浏览器地址栏输入 /user/list.action 就能绕过登录看到用户列表。控制访问得靠拦截器Struts2 的拦截器在 Action 执行前拦一道第 6 章我会给完整代码。现在先把登录注册本身跑通别急着一次到位。这里有个容易混淆的点Struts2 的 Session 不是 HttpSession 本身而是 Map 封装。绝大多数场景下两者效果一样但如果你在拦截器里拿原生 HttpSession 存了东西在 Action 里用 ActionContext 的 Session 是拿不到的反过来也一样。统一用 ActionContext.getContext().getSession() 能少踩一个隐形的坑。4. 用户信息管理列表、分页、模糊查询与增删改4.1 手写 PageBean 分页pageNum、pageSize 与总页数计算用户列表不能一次全查出来用户量一大页面就卡分页是必须的。先写一个通用 PageBean不依赖任何框架JDBC 版本够用换 MyBatis 也照搬public class PageBeanT { private int pageNum; // 当前页码 private int pageSize; // 每页条数 private long total; // 总记录数 private int pages; // 总页数 private ListT data; // 当前页数据 public PageBean(int pageNum, int pageSize) { this.pageNum pageNum 0 ? 1 : pageNum; this.pageSize pageSize 0 ? 10 : pageSize; } public void setTotal(long total) { this.total total; this.pages (int) Math.ceil(total * 1.0 / pageSize); } }总页数用 Math.ceil 向上取整99 条数据、每页 10 条总页数就是 10 而不是 9。pageNum 小于 1 时兜底为 1防止用户手动改 URL ?pageNum0 或负数把 SQL 带崩。pageSize 默认 10管理端一般 1020 合适别一页 100 条翻页体验差。DAO 层两个方法配合count() 查总数findPage(pageNum, pageSize) 查当前页。public ListUser findPage(int pageNum, int pageSize) { String sql SELECT id, username, nickname, email, create_time FROM t_user ORDER BY id DESC LIMIT ?, ?; return queryRunner.query(sql, new BeanListHandler(User.class), (pageNum - 1) * pageSize, pageSize); } public long count() { String sql SELECT COUNT(*) FROM t_user; return queryRunner.query(sql, new ScalarHandler()); }LIMIT 第一个参数是偏移量等于 (pageNum - 1) * pageSize。注意分页查询千万不能查出 password 和 salt 字段列表页根本用不到查出来反而有泄露风险。ORDER BY id DESC 让新注册的用户排前面管理端看列表比较符合直觉。4.2 列表查询与模糊搜索SQL 参数化别拼接模糊搜索的坑几乎都在 SQL 拼接上。有人图省事直接写 SELECT ... WHERE username LIKE % keyword %一旦 keyword 里带单引号要么 SQL 报错要么被注入。参数化写法是唯一推荐public ListUser search(String keyword, int pageNum, int pageSize) { String sql SELECT id, username, nickname, email, create_time FROM t_user WHERE username LIKE ? OR nickname LIKE ? ORDER BY id DESC LIMIT ?, ?; String like % keyword %; return queryRunner.query(sql, new BeanListHandler(User.class), like, like, (pageNum - 1) * pageSize, pageSize); }keyword 到 like 的拼包在 Java 侧做SQL 里始终是 ?这是底线。再提醒一个细节如果用户搜的是 % 或 _MySQL 会把这些当成通配符导致搜索结果超出预期。要彻底处理得用 ESCAPE 子句实际项目里可以先 trim 掉首尾空格一般课程设计做到参数化已经能过关。Action 侧接收 keyword 参数和 pageNum 一起传给 serviceservice 把 PageBean 放进 requestJSP 用 Struts2 标签迭代HttpServletRequest request ServletActionContext.getRequest(); request.setAttribute(pageBean, pageBean);JSP 里用 s:iterator 配合 s:a 渲染分页链接这是 Struts2 项目里最常见的列表写法。搜索条件和分页参数在翻页时要带上 keyword否则第二页搜索条件丢了这个坑在第 5 章还会提到。4.3 删除和编辑的回显dispatcher 与 redirectAction 的区别删除和编辑在 result 配置上要特别小心。删除成功后如果还用默认的 dispatcher 转发跳回列表页时浏览器地址栏还是 delete.action用户一按 F5 就又删一次。正确做法是重定向action namedelete classaction.UserAction methoddelete result typeredirectActionuser/list/result /action三个 result 类型对比按场景选result type行为适用场景dispatcher默认服务端转发URL 不变表单校验失败回显、编辑页回显redirectAction重定向到另一个 ActionURL 变化增删改成功后跳列表redirect重定向到任意 URL跳站外或静态资源编辑回显适合用 dispatcher按 id 查出 User 放进 request转发到 edit.jsp表单里把原值填好。public String edit() { User target userService.findById(id); ServletActionContext.getRequest().setAttribute(user, target); return edit; // struts.xml 里配置 result nameedit/WEB-INF/pages/user-edit.jsp/result }注意编辑表单里 s:textfield nameusername value%{user.username} /value 属性要从 request 里的 user 对象取值。漏了 value 绑定点编辑进去表单全是空的这是新手最容易忽略的一步。收回 id 时用隐藏域 s:hidden nameid /不要放在 URL 上减少误操作。5. Struts2 排错手册启动期与运行期的五个高频坑5.1 No action mapped 与 404先查三处配置现象Tomcat 启动不报错一访问 login.action 就出现一大段英文异常开头是 There is no Action mapped for namespace [/] and action name [login] associated with context path。原因struts.xml 里没配这个 action或者 namespace 对不上或者 class 路径写错。说白了就是「Struts2 翻遍所有 package 找不到能处理这个请求的 Action」。解决顺序第一打开 struts.xml 确认 action name 是不是 loginnamespace 是不是 /URL 是不是 /工程上下文/login.action第二确认 class 属性写的是 UserAction 的全限定名而不是简单类名第三开发阶段把 struts.devMode 临时设成 true报错信息会给出更明确的原因。这个错我见过无数回九成是 namespace 多个斜杠或者 class 名拼错不用怀疑框架坏了。另一种 404 现象访问 /login.action 返回 Tomcat 的 404 页面且 URL 后缀不是 .action。原因是 web.xml 的 filter url-pattern 写成了.action 或 /而 struts.xml 里 action 的访问后缀被改过。保持 web.xml 用 /、struts.xml 里 struts.action.extension 默认 action两者配套就不会出这问题。5.2 中文乱码与参数为 null八成是编码和 setter 的问题现象一登录页输入中文用户名Action 里打印出来是乱码或者注册成功后数据库里存的是 æ±‰å— 这类乱码。原因请求编码没统一。Struts2 默认会按 struts.i18n.encoding 处理参数编码这个常量没设 UTF-8或者设了但 JSP 页面本身的 contentType 不是 UTF-8MySQL 连接串里也没带 characterEncodingutf8三层里任何一层断了都会乱。解决三层全部设齐。struts.xml 里加 JSP 顶部 page 指令 contentTypetext/html; charsetUTF-8 pageEncodingUTF-8JDBC URL 加 ?characterEncodingutf8useSSLfalse。现象二表单提交后 Action 里的 username 一直是 null页面提示用户名不能为空。原因要么 JSP 的 input 标签 name 和 Action 属性名不一致要么 Action 缺 setUsername() 方法要么 JSP 用了 Struts2 标签但表单没提交到 action 对应的 URL。Struts2 的参数注入靠 setter这是约定不是配置少写一个 setter 参数就静默丢一个字段没有任何报错属于典型的静默黑匣子。解决逐个属性检查 setter表单 name 和 Action 属性名逐个对齐。排查时可以在 Action 方法第一行打日志把 username 打出来空就说明参数没进来比猜快得多。5.3 依赖冲突与 Struts2 2.5 的 DMI 玄学锁版本是后悔药现象启动时抛 NoClassDefFoundError异常栈里出现 javassist 字样同时项目里引了 Hibernate。原因Hibernate 3/4 和 Struts2 2.5 对 javassist 的版本要求不一致Maven 依赖树里出现两个版本的 javassist类加载器先加载了旧版。解决在 pom.xml 里显式锁定 javassist 版本用 dependencyManagement 统一或者干脆 JDBC 路线不引 Hibernate。这个问题的麻烦在于报错位置很随机看起来像玄学实际就是依赖树乱了记住一点出问题先 mvn dependency:tree别急着怀疑代码。现象Struts2 2.5 项目里配了 访问 user_list 却 404报错 No action mapped。原因2.5 默认开启 strict-method-invocation通配符调用的方法名受到白名单限制不在允许列表里的方法一律拒绝。老教程在 2.3 上能跑搬到 2.5 就废这是版本行为差异不是配置复制错了。解决三种方案任选。第一种最省事struts.xml 里加 但动态方法调用能不开就不开它等于放宽了方法调用边界第二种是把通配符 action 改成显式 method 配置第三种是给包加 白名单把需要通配调用的方法名列进去。我一般推荐第三种白名单比全局开关安全这个「后悔药」留给接手的人也是负责任的写法。6. 再进一步登录拦截器、密码强度与验证清单6.1 自定义登录拦截器十分钟内把校验逻辑收口Session 只解决「记住了谁」不解决「谁必须登录」。给整个用户管理模块加一道拦截器比在每个 Action 方法里重复判断 session 干净得多public class LoginInterceptor extends MethodFilterInterceptor { Override protected String doIntercept(ActionInvocation invocation) throws Exception { MapString, Object session ActionContext.getContext().getSession(); if (session.get(loginUser) null) { return loginRedirect; } return invocation.invoke(); } }struts.xml 里把它配进默认拦截器栈并排除 login、register 两个 actionpackage namedefault namespace/ extendsstruts-default interceptors interceptor namelogin classinterceptor.LoginInterceptor / interceptor-stack namemyStack interceptor-ref namedefaultStack / interceptor-ref namelogin param nameexcludeMethodslogin,register/param /interceptor-ref /interceptor-stack /interceptors default-interceptor-ref namemyStack / /package注意自定义拦截器必须引用 defaultStack否则 Struts2 的模型驱动、参数注入、校验这些基础拦截器全部失效Action 里参数突然全变 null这是入坑率极高的操作。方法过滤用 MethodFilterInterceptor比直接在 doIntercept 里判断 action 名优雅。6.2 这套系统怎么验证三条检查路径检查项操作预期结果注册链路访问 register.action注册新用户成功后跳登录页数据库 t_user 新增记录且密码为密文登录链路用新账号登录再直接访问 user/list.action正常进入列表未登录访问 user/list.action 被拦回登录页数据完整性新增 3 个用户后翻页、搜索关键字分页页码正确搜索结果只含匹配项且能跨页保留密码安全这块MD5 加盐只是及格线。如果这是要交付的系统把 MD5 换成 BCrypt 只需要动 MD5Util 一个类UserService 完全不改这是我建议的收尾动作。最后说点个人习惯。以前我照着老教程做这套系统在 No action mapped 上耗了两天后来学会一个笨办法拿到任何 Struts2 老项目第一件事把 struts.xml 里所有 action 的 name、namespace、method 抄成一张表访问任何 URL 先在这张表里查查不到就别碰代码。这套「登录注册 用户管理」做下来你学到的不是某个框架的 API而是一条完整控制层的思考方式。希望帮到你。本文还有配套的精品资源点击获取