多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

JSP+Struts2+Spring+MySQL宠物网站:SSH架构毕业设计从搭建到避坑全解析

JSP+Struts2+Spring+MySQL宠物网站:SSH架构毕业设计从搭建到避坑全解析 简介面向Java Web开发学习者与毕业设计人群这份资源打包了一套完整的宠物网站项目技术栈涵盖Spring与Struts2框架、JSP前台、MySQL后台同时提供毕业论文、数据库脚本、源代码和项目辅导视频。内容覆盖用户注册登录、商品浏览、购物车与订单管理等典型功能模块清晰呈现B/S结构系统从需求分析、功能模块设计、数据库设计到编码实现和系统测试的完整开发链路。压缩包共1444个文件包含282个js、266个png、128个css、111个jsp、93个java、92个class、54个jar等既有前端页面样式、也有后端逻辑和依赖库另含sql数据库脚本、mp4视频与doc文档整体约135.69MB。目前已有68人学习适合有一定编程基础、希望系统掌握Java Web开发或筹备毕业设计的学生。文档中附有详细技术说明和代码示例可深入学习Spring、Struts2与JSP的整合方法、JDBC数据库操作以及单元测试与性能测试的执行流程目录结构清晰便于按模块查阅和二次开发实践。1. JSPStruts2SpringMySQL 宠物网站为什么它是毕业论文最常见的成套项目打开任何一个毕业设计资源网站搜“宠物网站”大概率会撞见同一个技术配方JSP 做页面展示、Struts2 做请求分发、Spring 管理业务对象、MySQL 负责数据落地。它算不上时髦却是一周之内能把“能跑的完整 Web 系统”交付出来的最稳组合。这套四层架构对应的是一个非常具体的需求学生需要一个技术栈完整、功能肉眼可见、论文有东西可写的项目而 JSP 到 Servlet 容器、Struts2 到 Action、Spring 到业务 Bean、MySQL 到表结构每一层都边界清晰每一层都够写进毕业论文的管理过程与实现细节。下面直接带你拆这套宠物网站的架构把核心模块复现出来并说清楚跑起来之后最容易在哪些地方翻车。2. 从一次领养申请看四层调用链Struts2、Spring、JSP、MySQL 各自的边界2.1 一次领养申请请求的完整路径谁先执行、谁后执行理解这套老派但不失效的 Web 架构最好的方式不是背概念而是盯住一次完整的领养申请。用户在宠物详情页点“申请领养”表单被提交到类似/adoptApply.action的地址接下来的每一步都是框架替你编排好的浏览器把请求交给 web.xml 里注册的 Struts2 核心过滤器StrutsPrepareAndExecuteFilter过滤器根据 URL 后缀查到 struts.xml 中对应的 Action 类Action 类不直接碰数据库它通过 Spring 注入的 Service 对象调用业务方法Service 再调用 DAO数据访问对象DAO 借助 Spring 的 JdbcTemplate 或 MyBatis 执行 SQLMySQL 把结果返回给 DAO再一层层传回 ActionAction 把结果放进 request 或 session 作用域最后返回一个字符串逻辑视图名Struts2 根据它找到 JSP 页面并渲染输出。这个链路里四件事分得很开JSP 只负责把数据变成 HTML不写业务Struts2 只负责接请求、跳视图、做拦截不写 SQLSpring 只负责把 Action、Service、DAO 这些对象创建出来并注入彼此同时管理事务边界MySQL 只负责存和查。这样的分层在毕业论文里特别好讲因为每一层的代码量都不大、职责单一画架构图、写模块设计、配测试数据都容易落到纸面上。很多初学者拿到这类项目源码后第一反应是“到处都在 new 对象”其实这里有一个容易忽略的机制Struts2 的 Action 是由框架创建的不是你在代码里new出来的所以 Action 里那些 Service 属性全靠 Spring 注入。这也是为什么该类项目里 struts.xml、applicationContext.xml 和 web.xml 三份配置文件缺一不可。下面这张表把每一层常见的产物和典型代码位置列清楚层典型产物关键配置或文件视图层JSP 页面、JSTL 标签、EL 表达式src/main/webapp/WEB-INF/jsp/控制层Struts2 Action 类、result 跳转struts.xml业务层Service 接口与实现类applicationContext.xml 里的bean数据层DAO 接口与实现类、SQLapplicationContext.xml 里的数据源或 MyBatis 映射2.2 JSP 视图层为什么在毕设阶段仍然够用ELJSTL 渲染个人信息页前后端分离现在已经成了主流但对这种单个宠物网站来说上 Vue 或 React 意味着要额外处理跨域、接口文档、前端构建而毕设要求的是“完整可运行、有交互页面、能讲清原理”。JSP 的整页渲染在这个体量下恰恰是性价比最高的选择一个c:forEach循环就能把宠物列表打印在表格里一个${pet.name}就能在详情页显示宠物名不需要任何异步请求。拿“个人信息展示页面”这种最常见的热搜词来说它本质就是把当前登录用户对象的值塞进表单里。典型写法是 Struts2 的 Action 里把 user 对象放进 request% page contentTypetext/html;charsetUTF-8 % % taglib prefixs uri/struts-tags % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % div classprofile-card h2个人中心/h2 p昵称${sessionScope.loginUser.nickname}/p p邮箱${sessionScope.loginUser.email}/p p注册时间c:out value${sessionScope.loginUser.registerTime} //p c:if test${sessionScope.loginUser ! null} a hrefpet/toEditProfile.action编辑资料/a a hrefadopt/listMyApply.action我的领养记录/a /c:if /div这段代码里有两个细节值得注意。第一个是${sessionScope.loginUser.nickname}它要求登录成功后把 user 对象放进 sessionAttribute 的名字不能写错否则页面上就是一片空白第二个是c:out专门用来输出文本能规避一些 XSS 风险比直接写${...}更稳。用 JSP 做视图还有一个隐蔽优势JSP 可以通过% include %或 JSTL 的c:import复用头部导航和底部版权信息宠物网站里的头部导航、后台管理菜单都可以抽成公共片段论文里的“页面复用设计”一节也有东西写了。2.3 数据库四张核心表user、pet、adopt_apply、message 的字段设计宠物网站再怎么加功能核心业务也离不开四张表用户表、宠物表、领养申请表、站内留言表。很多毕业论文项目失败在“表建了一堆字段随意”到最后写 SQL 时连状态都用字符串。我一般建议直接给出建表 SQL 并从一开始把类型和默认值钉死。CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT MD5 或 BCrypt 后的密码, nickname VARCHAR(50) DEFAULT NULL, email VARCHAR(100) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 1普通用户 2管理员, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE pet ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 宠物昵称, breed VARCHAR(50) DEFAULT NULL COMMENT 品种, age INT DEFAULT NULL COMMENT 年龄单位月, gender TINYINT DEFAULT 0 COMMENT 0未标记 1公 2母, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待领养 1已申请 2已领养, photo_path VARCHAR(200) DEFAULT NULL COMMENT 图片相对路径, description TEXT COMMENT 宠物介绍, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物信息表;字段设计的核心是“状态用 tinyint 不用 varchar”。等你在列表页写“待领养 / 已申请 / 已领养”的展示逻辑时就会明白用 0、1、2 这样的数字Java 端只需要一个switch或MapInteger, String就能翻译而如果用字符串前端筛选和数据库索引都会变扭。create_time用 DATETIME 并给默认值CURRENT_TIMESTAMP这样插入时不用手动塞时间。MySQL 的排序需求集中在ORDER BY create_time DESC所以索引不一定要单独建但 like 搜索高频的name和breed如果数据量大可以后续再加组合索引。领养申请表则要记录谁在哪一刻申请了哪只宠物并做唯一约束避免同一个人反复申请同一只。3. 从零复现最小骨架三份 XML 配置与登录模块的 Action/Service/DAO 拆分3.1 项目目录结构与 Maven 依赖先照着建一个 SSH 骨架拿到这类“源代码数据库”项目第一步不是急着看代码而是先把目录结构立起来。用 Maven 管理依赖是最省事的做法源代码里没有 Maven 配置就自己补一个结构通常是pet-web/ ├── pom.xml ├── src/main/java/ │ ├── com.example.pet.action/ # Struts2 的 Action 类 │ ├── com.example.pet.service/ # Service 接口与实现 │ ├── com.example.pet.dao/ # DAO 接口与实现 │ └── com.example.pet.entity/ # 实体类 ├── src/main/resources/ │ ├── struts.xml │ └── applicationContext.xml └── src/main/webapp/ ├── WEB-INF/web.xml └── jsp/ # 所有 JSP 页面放这里实体类在这里比 Spring Boot 项目更“原始”通常就是一个属性一个 getter/setter 的 POJO对应数据库表的字段。pom.xml 里最关键的是三个依赖坐标struts2-core、spring-web和mysql-connector-java另外还需要struts2-spring-plugin它的作用是让 Struts2 在创建 Action 时去 Spring 容器里找同名 Bean而不是自己反射 new 一个这一点不配好Action 里的 Service 永远是 null。版本选择上我一般建议保守Struts2 用 2.5.x 系列Spring 用 5.1.x 系列两者配合成熟且网上可查的报错资料最多。3.2 三份 XML 配置的分工web.xml、struts.xml、applicationContext.xml这套架构最大的“劝退点”是三份配置文件的互相引用关系。实际职责并不复杂web.xml 是整个应用的入口注册 Spring 的上下文监听器和 Struts2 的核心过滤器struts.xml 定义请求 URL 和 Action 类的映射applicationContext.xml 定义业务对象如何创建和注入。先看 web.xml 的最小配置web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener 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-mapping /web-app这里url-pattern是/*意味着所有请求都会经过 Struts2 过滤器。上下文监听器会在容器启动时加载 classpath 下的 applicationContext.xml。紧接着看 struts.xml它负责把.action请求映射到具体类struts package namedefault extendsstruts-default action namelogin classloginAction methodlogin result namesuccess/jsp/index.jsp/result result nameerror/jsp/login.jsp/result /action /package /struts注意action namelogin classloginAction中的 class 不是一个全限定类名而是 Spring 容器里 Bean 的 id。这就是struts2-spring-plugin在起作用Struts2 发现这个 Action 的 class 值在 Spring 容器中存在就会直接从 Spring 拿实例于是loginAction里通过注解或 XML 注入的UserService才能被正确赋值。如果这里写了全限定类名框架会自己 new Action注入全部失效。applicationContext.xml 这边配置数据源、事务和业务 Beanbean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/pet_db?useSSLfalseamp;characterEncodingUTF-8amp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword value你的密码/ /bean bean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplate property namedataSource refdataSource/ /bean bean iduserDao classcom.example.pet.dao.impl.UserDaoImpl property namejdbcTemplate refjdbcTemplate/ /beancharacterEncodingUTF-8和serverTimezoneAsia/Shanghai是 MySQL 5.7 以上版本最常见的两个坑前者决定中文字符能不能正确读写后者决定时间类型能否正常解析。jdbcTemplate作为 DAO 的公共依赖被注入这样 DAO 里不用自己管连接开关事务边界由 Spring 的DataSourceTransactionManager统一控制这也是毕业论文里“事务管理设计”一节的核心素材。3.3 登录模块落地Action 调 Service、Service 调 DAO 的典型写法配置通了之后写一个登录模块就能把三层真正串起来。先从 DAO 开始// UserDaoImpl.java public class UserDaoImpl implements UserDao { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Override public User findByUsernameAndPassword(String username, String password) { String sql SELECT id, username, nickname, email, role, create_time FROM user WHERE username ? AND password ?; ListUser list jdbcTemplate.query(sql, (rs, rowNum) - { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setNickname(rs.getString(nickname)); u.setRole(rs.getInt(role)); return u; }, username, password); return list.isEmpty() ? null : list.get(0); } }DAO 用 JdbcTemplateSQL 里的?占位符由框架帮我们预编译避免字符串拼接带来的 SQL 注入风险。在这个项目里密码字段如果是明文存储那就是给答辩老师送分题至少要在 Service 层做一次 MD5 摘要再入库更稳妥的做法是加盐。Service 层只保留业务规则不出现 SQL// UserServiceImpl.java public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao userDao; } Override public User login(String username, String password) { if (username null || username.trim().isEmpty()) { throw new BusinessException(用户名不能为空); } String encrypted Md5Util.md5(password); return userDao.findByUsernameAndPassword(username.trim(), encrypted); } }Action 层负责接参数和决定跳转结果。Struts2 里表单里的 input 字段会自动映射到 Action 的同名属性上前提是提供 setter// LoginAction.java public class LoginAction extends ActionSupport { private String username; private String password; private UserService userService; public void setUsername(String username) { this.username username; } public void setPassword(String password) { this.password password; } public void setUserService(UserService userService) { this.userService userService; } Override public String execute() throws Exception { User user userService.login(username, password); if (user null) { addActionError(用户名或密码错误); return ERROR; } ActionContext.getContext().getSession().put(loginUser, user); return SUCCESS; } }三个关键点依赖注入用的是 setter 方式这要求 struts.xml 里classloginAction与 Spring 容器中 Bean 的 id 同名登录成功的用户放进ActionContext的 session后续 JSP 用${sessionScope.loginUser}取用addActionError会把错误信息带到 JSP 页面的s:actionerror/标签处显示这是 Struts2 原生的校验反馈机制比手动request.setAttribute更规范。4. 把宠物网站做厚分页、图片上传、坐标定位与模糊搜索的落地写法4.1 宠物列表分页LIMIT 与 PageBean把总数、页数、当前页算清楚宠物网站首页和领养列表都逃不开分页。初学者最常见的翻车是“查全部数据再在 Java 里截取”数据一多页面直接卡死。正确做法是把分页交给 MySQL用LIMIT offset, pageSize只取当前页需要的数据同时单独查一次总数计算总页数。我习惯写一个通用的 PageBeanpublic class PageBeanT { private int currentPage; // 当前页 private int pageSize; // 每页条数 private long totalCount; // 总记录数 private int totalPage; // 总页数 private ListT data; // 当前页数据 }DAO 里的分页查询是直接拼 SQLpublic ListPet findPetsByPage(int currentPage, int pageSize) { int offset (currentPage - 1) * pageSize; String sql SELECT id, name, breed, age, status, photo_path, description FROM pet ORDER BY create_time DESC LIMIT ?, ?; return jdbcTemplate.query(sql, (rs, rowNum) - { Pet pet new Pet(); pet.setId(rs.getInt(id)); pet.setName(rs.getString(name)); pet.setStatus(rs.getInt(status)); return pet; }, offset, pageSize); } public long countPets() { String sql SELECT COUNT(*) FROM pet; Long total jdbcTemplate.queryForObject(sql, Long.class); return total null ? 0 : total; }参数上currentPage从请求参数获得前端页码从 1 开始offset的算法(currentPage - 1) * pageSize是分页最容易写错的地方一旦写成currentPage * pageSize第一页就会跳过前 pageSize 条数据。总页数计算用(totalCount pageSize - 1) / pageSize注意这个是整数除法。JSP 底部分页栏用 JSTL 生成上一页、下一页和页码数字核心是给翻页链接带上currentPage参数比如pet/list.action?currentPage2Struts2 会把它映射到 Action 的同名属性并转到对应页面的重查流程。4.2 宠物图片上传与页面坐标定位fileUpload 拦截器与图片回显路径宠物网站必然要上传宠物照片。Struts2 的fileUpload拦截器是默认启用的一部分Action 里用三个约定命名的属性接收文件File photo接收上传的临时文件String photoFileName接收原始文件名String photoContentType接收 MIME 类型。处理逻辑是把临时文件复制到指定目录// PetAction.java 中的上传方法 public String uploadPhoto() throws Exception { String saveDir ServletActionContext.getServletContext().getRealPath(/upload/pet); File dir new File(saveDir); if (!dir.exists()) { dir.mkdirs(); } // 用时间戳随机数重命名避免中文文件名乱码 String ext photoFileName.substring(photoFileName.lastIndexOf(.)); String newName System.currentTimeMillis() _ new Random().nextInt(10000) ext; File target new File(saveDir, newName); FileUtils.copyFile(photo, target); pet.setPhotoPath(/upload/pet/ newName); // 调用 service 更新 pet 表 return SUCCESS; }这里有两个“老手才知道”的细节。其一getRealPath(/upload/pet)拿到的是 Tomcat 当前部署目录下的物理路径重新部署项目后上传的文件会被清空所以真正的生产环境要把图片放到应用外的固定目录毕设阶段用getRealPath可以接受但要在论文里说清楚局限。其二回显时的路径必须和保存路径逻辑一致保存的是/upload/pet/xxx.jpg这种以斜杠开头的相对路径JSP 页面里直接img src${pet.photoPath}才能正确拼接出访问地址如果保存时漏了开头的斜杠图片在火狐浏览器里会挂。“jsp 图片如何对坐标定位”这个问题常被搜到其实有两种理解。如果是给图片加点击热点比如宠物身体部位介绍在 JSP 里用map配合area标签设置坐标即可坐标系就是图片本身的像素坐标系如果是上传后控制图片在页面上的显示位置用 CSS 的position: relative包一层div再调整left/top不要试图用 JSP 语法去算坐标那是浏览器渲染层的事跟 JSP 没有关系。毕设阶段做到“图片不裂、布局不跳”就够了。4.3 宠物搜索与排序LIKE 模糊查询、SQL 注入防范和 CREATE_TIME 倒序列表页的搜索框是最能体现细节的模块。用户在搜索框输入关键词同时选择品种下拉框和排序条件。DAO 层不能为了省事把条件拼成一长串字符串再执行那样基本等于给 SQL 注入开门正确做法是用 JdbcTemplate 的占位符动态拼 SQLpublic ListPet searchPets(String keyword, String breed, String orderBy) { StringBuilder sql new StringBuilder( SELECT id, name, breed, age, status, photo_path FROM pet WHERE 11); ListObject args new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql.append( AND (name LIKE ? OR breed LIKE ?)); args.add(% keyword.trim() %); args.add(% keyword.trim() %); } if (breed ! null !breed.trim().isEmpty()) { sql.append( AND breed ?); args.add(breed.trim()); } if (age.equals(orderBy)) { sql.append( ORDER BY age ASC); } else { sql.append( ORDER BY create_time DESC); } sql.append( LIMIT ?, ?); args.add(offset); args.add(pageSize); return jdbcTemplate.query(sql.toString(), rowMapper, args.toArray()); }WHERE 11在这里不是为了“炫技”而是让后面的条件都能直接用AND拼接避免每个条件都要判断是否已经是第一条写起来干净也不容易错。LIKE的参数是%keyword%加上占位符后用户输入%或_也不会破坏 SQL 结构。排序条件orderBy从页面传入时必须用白名单校验比如只允许age或time否则直接把用户输入拼进ORDER BY一样能出问题。MySQL 排序还有一个细节ORDER BY create_time DESC在宠物数据量不大时走的是文件排序性能没问题但如果以后做了“下架宠物优先排在最后”的需求就要在状态字段上建索引再考虑ORDER BY status ASC, create_time DESC的组合排序。5. 避坑与排查JSPStruts2Spring 这套经典组合的五个常见翻车现场5.1 版本冲突启动秒挂的 ClassNotFound 与 NoSuchBeanDefinition现象Tomcat 一启动控制台就抛ClassNotFoundException: org.objectweb.asm.ClassVisitor或NoSuchBeanDefinitionException页面打不开黑匣子一样无从下手。原因Struts2 2.5 系列和 Spring 5.x 都依赖 ASM 字节码库两套 jar 包里的 ASM 版本不一致会冲突NoSuchBeanDefinitionException则多半是 struts.xml 里 Action 的 class 写的全限定类名或者 Spring 扫描路径没覆盖到 Service 实现类导致容器里根本没有loginAction这个 Bean。解决Maven 项目直接用mvn dependency:tree看 ASM 是谁引进来的把低版本的排除掉Spring 侧确认applicationContext.xml里context:component-scan扫到了com.example.pet.service.impl用 XML 配 Bean 的则要逐个检查 id 是否和 struts.xml 里的 class 值完全一致。这类问题查起来最费时间我一般先把 struts.xml 注释到只剩一个 Action能跑再逐个加。5.2 EL 表达式不生效长这样的${pet.name}原样显示在页面上现象浏览器里显示的是${pet.name}这个字符串本身而不是宠物名。代码逻辑反复看都正常后端数据也在 request 里。原因web.xml 声明的 Servlet 版本太低Tomcat 默认把 EL 解析关闭或者 JSP 页面里写了% page isELIgnoredtrue %。如果用的是老工程改造web.xml 还是 2.3 的 DTD 声明EL 就是默认不解析的。解决web.xml 换用 3.0 以上版本 schema前面 3.2 节的写法就带version3.1同时搜索全局把isELIgnoredtrue删除。注意改完 web.xml 必需要重新发布项目只重启动 Tomcat 有时不生效这个坑我踩过不止一次。5.3 中文乱码MySQL 连接串、JSP 编码与数据库字符集三层一起检查现象注册用户输入中文昵称保存到数据库变成??或者 JSP 页面显示数据库里已有的中文变成乱码。原因乱码不是单一原因而是三层叠加。第一层MySQL 连接串没写characterEncodingUTF-8JDBC 驱动用默认编码把中文转成错误的字节第二层数据库或表用了latin1字符集第三层JSP 页面本身没设置pageEncoding。解决三个地方一起修。连接串补上characterEncodingUTF-8还可以加useUnicodetrue建表语句统一DEFAULT CHARSETutf8mb4已经建好的表用ALTER TABLE pet CONVERT TO CHARACTER SET utf8mb4补救JSP 页面头部保留contentTypetext/html;charsetUTF-8。排查乱码最直接的办法是先用 MySQL 命令行插入一条中文数据看是 Java 写入乱还是数据库存储乱定位到层再动手。5.4 静态资源被 Struts2 拦截CSS、JS、图片全部 404现象页面 HTML 结构出来了但没有一点样式控制台一堆 404请求的/css/style.css被 Struts2 拦走了。原因web.xml 里 Struts2 过滤器的映射是/*它把 CSS、JS、图片这些静态资源的请求也当成 Action 去匹配struts.xml 里自然找不到对应的 Action于是直接 404。解决让 Struts2 放过静态资源。常见做法是在 struts.xml 里配constant namestruts.action.excludePattern value/css/.*,/js/.*,/images/.*,/upload/.*/把静态资源目录排除在外或者把静态资源全部放到WEB-INF外面的独立目录并用容器默认 Servlet 处理。毕设项目我推荐第一种因为它最直观——列表里出现新静态目录时手动加一条排除规则即可。5.5 Action 取不到表单参数属性驱动与模型驱动别混着用现象表单里明明填了用户名密码提交后 Action 里username却是 null或者更隐蔽第一次提交有值第二次就没了。原因Struts2 的 Action 有两种接收参数的方式。属性驱动是页面字段直接对应 Action 的属性要求 getter/setter 齐全模型驱动是 Action 实现ModelDrivenT接口表单字段直接对应 model 对象的属性。两者混用时框架优先按模型驱动处理如果 model 对象没有为某字段提供 setter数据就丢了。解决选定一种整站保持一致。毕设项目用属性驱动最简单每个表单对应 Action 的一组同名 setter虽然代码量略大但调试时一眼能看出字段从哪来。如果真的用模型驱动记得把表单字段名写成user.username这种带前缀的格式JSP 里用s:textfield nameuser.username/Action 里getModel()返回一个非 null 的 user 实例并且这个实例要被注入或 new 出来——null 的 model 是所有“参数丢失”里最隐蔽的原因。6. 答辩与演示从可运行代码到可信演示的验证清单6.1 演示脚本按“注册→登录→浏览→搜索→申请→审核”的顺序走答辩现场最怕的不是代码有 bug而是演示顺序混乱导致操作反复失败。我建议按业务闭环来排演示流程每一步都提前准备好确定的测试账号和测试数据先注册一个新账号展示前端校验和后端防重复用该账号登录进入个人中心在宠物列表页演示分页和搜索用预置的关键词搜出结果点击一只状态为“待领养”的宠物提交领养申请之后切换管理员账号在后台看到这条申请并审核通过最后回到普通用户视角看到宠物的状态变更为“已领养”。整个链路走完Struts2 的请求分发、Spring 的事务、MySQL 的数据变化全部被覆盖到。测试数据建议直接用 SQL 脚本插入 20 只宠物覆盖不同品种、不同状态排序和筛选的演示效果才明显。有一个血泪经验答辩前一天不要改任何代码只把数据库重新导入一遍、用今天的演示脚本完整走一遍确认环境是热的这比临时“优化”几个界面重要得多。6.2 答辩高频追问分页、事务、SQL 注入与框架选型答辩老师大概率会围绕这四个点追问第一分页“为什么用 LIMIT 而不是一次性查全”你要答出LIMIT offset,pageSize只通过网络传输一页数据、MySQL 在数据量大时配合索引能走 range 扫描第二事务“在哪里控制的”你要指得出 applicationContext.xml 里的DataSourceTransactionManager和 Service 实现类上的Transactional注解结合“申请领养时同时更新 pet 状态和插入申请记录两条 SQL 必须在同一事务”来举例第三SQL 注入“怎么防的”直接说 JdbcTemplate 的占位符预编译第四框架选型“为什么用 JSPStruts2 而不是 Spring Boot”不要只说“学校要求的”可以讲这套方案的层次更清晰、每层职责肉眼可见适合论文展开设计。不要准备太长的解释词两句话讲清楚一个机制剩余时间留给老师把问题问得更深反而显得扎实。最后想提醒一句实话这类项目最大的风险是只有“能跑”但没有“自己的思考”如果你只是原样运行那只能保底在数据库表设计上加入一个选题里没有的小字段、在搜索模块加一点用户友好的排序都会让答辩现场的观感完全不一样。希望帮到你。本文还有配套的精品资源点击获取
返回列表