1. 项目引子:为什么是“夕阳红”老年服务系统
这年头做毕设,十个里面有八个都是“XX管理系统”,图书、学生、仓库,翻来覆去就那么几套。我当初选“夕阳红”老年服务系统这个题目,倒不是因为跟风,而是真觉得这个方向有得做——老龄化越来越明显,社区养老、居家养老的服务业务正在变多,一个面向老年人的服务预约与管理平台,不管是从业务丰富度还是从技术覆盖面上看,都比再做一遍“图书管理系统”有意思得多。
先交代一下这套系统的技术底子:Java + SSM(Spring + Spring MVC + MyBatis)+ JSP,数据库用的 MySQL,服务器选的 Tomcat,IDE 用的是 IDEA,项目结构是传统的 WAR 包部署方式。这套组合放到今天看确实有点“复古”,但它在教学场景、毕业设计、以及部分传统企业内部系统里依然是存量很大的一条技术路线。说句实在话,这组合能存活到现在是有原因的:SSM 三层架构清晰,Spring 管对象、Spring MVC 管请求分发、MyBatis 管数据持久化,每一层各司其职;JSP 在服务端渲染页面,对老系统来说维护方便,对新手来说结构直观,没有前后端分离那套概念轰炸。
这篇博文我会把整套系统的设计与实现过程掰开讲:需求怎么拆、表怎么建、SSM 三个框架怎么集成、登录鉴权怎么做、权限控制在哪儿拦截、分页和表单回显有哪些坑、部署到 Tomcat 的完整流程,以及我在开发过程中实际踩过的问题和排查思路。如果你是正在选毕设题目、或者在学 SSM 整合但卡在配置阶段的同学,这篇文章应该能帮你省不少事。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体设计与需求拆解
2.1 从业务出发,先搞清楚“老年服务系统”到底要管什么
“老年服务系统”听起来很大,但落到代码上,核心其实就是围绕老人的日常服务流转做一套管理工具。我在做需求分析的时候,先把自己代入到三类角色里去:
- 管理员:管平台、管数据。老人档案、服务项目、服务工单、员工信息、公告内容,这些都归管理员管。
- 服务人员/员工:接单、上门服务、记录服务结果。他们关心的是今天有哪些任务、做完了没有、结果怎么记录。
- 老人或家属(访客):浏览服务项目、查看服务政策、提交服务申请。他们用的是前台展示页面,不需要复杂的后台操作。
这三类角色对应的是三种完全不同的 UI 和使用逻辑,所以系统从一开始就定了“前台展示 + 后台管理”的双端结构。前台是面向老人和家属的信息浏览与申请渠道,后台才是真正承载业务流程的地方。
基于这个拆解,我梳理的核心功能模块如下:
- 老人档案管理:姓名、年龄、性别、住址、家属联系方式、健康状况、是否独居、有无特殊病史。这是系统的基础数据,所有服务都围绕老人展开。
- 服务项目管理:助餐、助洁、助医、助浴、康复护理、精神慰藉等。每个项目需要配置名称、简介、价格/免费标识、服务时间、适用人群。
- 服务预约与工单流转:老人或家属在前台提交预约,员工在后台接单,服务完成后填写记录,管理员对整个流程可见。
- 员工账户管理:不同角色不同权限,服务人员登录后只能看到与自己相关的工单。
- 公告新闻管理:发布养老政策、社区活动通知,前台展示。
- 系统管理:管理员账户维护、菜单权限、操作日志(可选)。
在做功能清单的时候,我给自己定了一个原则:先做“能用”,再做“好用”。第一版只做核心的增删改查和工单流转,不做花哨的图表统计,等核心流程跑通了再补。
2.2 技术选型:为什么是 SSM 而不是 Spring Boot
这个问题几乎每个看到这套系统的人都会问。选 SSM 不是因为不会 Spring Boot,而是基于两个很现实的考量:
第一,这是毕设和教学场景的“标准答案”。 高校课程里大量还是以 Spring + Spring MVC + MyBatis 为主讲内容,Spring Boot 虽然在企业里普及率极高,但课程大纲和实验手册往往跟不上。选 SSM 意味着我在写论文、做答辩的时候,每一层都能讲出对应的原理——Spring 的 IoC 和 AOP、Spring MVC 的 DispatcherServlet 流程、MyBatis 的 Mapper 代理和动态 SQL,这些都是答辩时最好讲、也最容易被老师追问的点。
第二,SSM 的配置过程本身就是硬功夫。 用 Spring Boot 的话,一个注解就能启动项目,很多细节被封装掉了。而 SSM 要求你手动管理 applicationContext.xml、spring-mvc.xml、mybatis-config.xml,还要处理 jar 包冲突、web.xml 配置、过滤器和监听器的加载顺序。把这些跑通一遍,你对“框架是如何工作的”才算有真正的体感。
当然,如果你是想找工作的应届生,我的建议是 SSM 用来交作业,Spring Boot + 微服务才是你要优先补的方向。技术选型没有绝对的对错,看的是场景。
2.3 JSP 在前台页面中的角色取舍
现在一提到 JSP,很多新入行的同学可能只听过名字,没实际写过。真实的感受是:JSP 确实老旧,但在这种传统项目中,它有一个不可替代的价值——服务端渲染带来的简单直接。
前台页面列表、后台管理页面,全部由 Controller 返回 ModelAndView,JSP 里用 JSTL 和 EL 表达式直接取数据渲染。没有跨域问题、没有前端构建步骤、不用配 Vite/Webpack,写完 JSP 文件丢到 webapp 目录下重启即可看到效果。这对以课程设计为目标的场景来说,效率极高。
我为了让 JSP 页面不至于太“2005 年风格”,引入了 Bootstrap 框架做样式,配合简单的 CSS 定制。表格用 Bootstrap 的样式,表单用栅格布局,整体看起来干净整齐,答辩演示时观感完全够用。
2.4 数据库设计:六张核心表的字段与关联
数据库设计我花了挺多时间,因为它直接决定后面的代码是顺手还是别扭。最终落地了六张核心表:
| 表名 | 用途 | 核心字段 |
|---|---|---|
t_admin |
管理员账户 | id, username, password, real_name, role |
t_employee |
服务人员 | id, name, phone, id_card, work_status |
t_elder |
老人档案 | id, name, gender, age, phone, address, health_status, family_phone, is_alone |
t_service_item |
服务项目 | id, item_name, item_desc, price, duration, status |
t_appointment |
服务预约/工单 | id, elder_id, employee_id, item_id, appoint_time, status, remark, create_time |
t_notice |
公告资讯 | id, title, content, publish_time, publisher |
其中 t_appointment 是系统的核心表,关联了老人、员工、服务项目三张表,是典型的“中间表 + 业务表”设计。状态字段我用了 tinyint 类型:0 表示待处理,1 表示已接单/进行中,2 表示已完成,3 表示已取消。这个状态机虽然简单,但已经足够支撑整个工单流转流程。
在设计字段时有一个值得分享的经验:不要在一开始就把表设计得过于复杂。我最初的想法是把老人的健康指标单独建一张表,甚至想加一个定时提醒功能,后来发现这会显著拉长开发周期。毕设有时间边界,先把主流程跑通,扩展功能可以在数据库里预留字段,比如 t_elder 表预留一个 remark,后续想加健康数据再扩展子表,这样对整体结构影响最小。
3. SSM 框架搭建与核心配置详解
3.1 IDEA 创建工程与 Tomcat 配置
这个系统用的是传统 WAR 包的 JSP 项目结构,和现在常见的 Spring Boot 内嵌 Tomcat 完全不同。建工程的时候选的是 Java Enterprise,勾选 Web Application,IDEA 会自动生成 webapp/WEB-INF 目录结构。
需要特别提醒的是 Tomcat 版本和 JDK 版本的匹配问题。我本机用的是 JDK 8 + Tomcat 8.5,这是一对非常稳定的组合。之前试过用 JDK 17 跑 Tomcat 9,结果遇到一堆模块访问限制的问题,后来干脆全部换回 JDK 8 开发。如果你不是必须追新,建议在这个组合上保持一致。
创建完工程后,要在 Project Structure 里把 lib 目录下的依赖包全部 Add to Libraries。这一步容易被忽略,结果写代码时一堆红标,以为是依赖没配置对,其实就是没把 jar 包加进项目依赖里。
3.2 配置文件的“四大金刚”
SSM 中最劝退新手的,就是那一堆 XML 配置文件。我把它们称为“四大金刚”,分别是:
web.xml:Web 容器入口,配置 Spring 监听器、Spring MVC 前端控制器、编码过滤器。applicationContext.xml:Spring 根容器配置,管理数据源、事务、Service 层 Bean。spring-mvc.xml:Spring MVC 子容器配置,开启注解驱动、配置视图解析器、扫描 Controller。mybatis-config.xml:MyBatis 全局配置,配置驼峰映射、日志等。
这里的核心知识点是父子容器的关系。web.xml 里通过 ContextLoaderListener 加载 applicationContext.xml,这是父容器,负责业务层和数据访问层的 Bean;DispatcherServlet 加载 spring-mvc.xml,这是子容器,只负责 Controller 层。子容器可以访问父容器的 Bean,但反过来不行。这个机制理解透了,很多“找不到 Bean”的报错就能自己排查了。
视图解析器我在 spring-mvc.xml 里配置了 JSP 的前缀和后缀:
xml复制<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
</bean>
配置好之后,Controller 里 return 一个 "admin/index",就会自动去 /WEB-INF/jsp/admin/index.jsp 找界面。这里有一个小技巧:把 JSP 放在 WEB-INF 目录下,用户无法通过 URL 直接访问,只能通过 Controller 跳转,安全性要好很多。
3.3 数据源与 MyBatis 集成要点
数据源我用的是阿里 Druid,连接池的好处在于能够复用连接、监控 SQL,比每次新建连接效率高很多。配置如下:
xml复制<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sunset_service?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
注意 URL 里的 characterEncoding=UTF-8,这是中文乱码问题的第一道关卡。很多同学数据库和代码都没问题,但页面还是乱码,多半是这里漏了。
MyBatis 集成时,我用的是 MapperScannerConfigurer,让它自动扫描 com.sunset.dao 包下的 Mapper 接口,避免了手动一个个注册 Mapper 的麻烦:
xml复制<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.sunset.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
对应的 Mapper 接口和 XML 文件按“接口全限定名对应 XML namespace”的规则放置。这里有一个常见的坑:如果 Mapper 接口和 XML 不在同一个包路径下,需要在 mybatis-config.xml 里配置 mapper locations。我在 Maven 项目里踩过这个坑,后来直接在 XML 里加 <mapper resource="mapper/EmployeeMapper.xml"/> 才算解决。
3.4 整合测试:从依赖到页面,跑通第一遍
配置文件的整合过程没办法一蹴而就,我的建议是分三步走:
- 先配 Spring:让 Spring 管理数据源和 Service 层,写一个 JUnit 测试类验证数据库连接。
- 再加 Spring MVC:写一个最简单的 Controller 返回字符串,测试请求能否到达后端。
- 最后整合 MyBatis:通过 Service 调 Mapper 查一条数据,输出到控制台验证。
每一步都完成后再往下走,出问题很容易定位。一次性配完再测试,报错的时候你根本不知道错在哪一层。
我第一次整合时遇到一个特别经典的报错:
code复制Error creating bean with name 'userController': Injection of autowired dependencies failed
原因是 Controller 里的 Service 字段没有加 @Service 注解,或者 Service 实现类没有被 Spring 扫描到。检查 applicationContext.xml 的 <context:component-scan> 是不是包含了 com.sunset.service 包,这个报错基本能解决。
4. 核心功能模块的实现细节与实操记录
4.1 登录与权限控制:拦截器 + Session 的方案
登录功能看起来简单,但你要考虑的问题其实不少:密码怎么存、Session 怎么管理、哪些页面需要登录才能访问、不同角色能访问的页面怎么区分。
我的做法是:
- 密码通过 MD5 加盐存储,不存明文。注册时生成随机盐值,登录时用盐值再算一次比对。
- 登录成功后,将用户对象放入 Session。
- 通过 Spring MVC 拦截器对后台路径进行拦截,未登录则强制跳转到登录页。
拦截器实现逻辑如下:
java复制public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if (user == null) {
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
return true;
}
}
在 spring-mvc.xml 中注册拦截器并设置拦截路径:
xml复制<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/admin/**"/>
<mvc:exclude-mapping path="/admin/login"/>
<mvc:exclude-mapping path="/admin/doLogin"/>
<bean class="com.sunset.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
这样设计的好处是简单直接,不需要引入 Spring Security 那套复杂的过滤器链。对于课堂项目、毕设这类场景完全够用。但如果以后要扩展做“记住我”、“多角色精细权限”,建议还是升级到 Spring Security,安全性和可维护性都会好很多。
4.2 老人档案管理:从分页查询到表单回显
老人档案是系统的核心业务数据,这一块做得最细。
分页查询:我一开始是自己写 LIMIT 手动分页,后来发现太痛苦,换成了 PageHelper 插件。引入依赖后,在 Mapper 查询前调用:
java复制PageHelper.startPage(pageNum, pageSize);
List<Elder> list = elderMapper.selectAll();
PageInfo<Elder> pageInfo = new PageInfo<>(list);
PageHelper 的原理是基于 MyBatis 拦截器,自动在 SQL 后面追加 LIMIT 语句。PageInfo 里封装了总记录数、总页数、当前页等所有分页信息,直接传给 JSP 渲染即可。我的建议是能上插件就上插件,手写分页不仅容易出偏移 bug,而且代码可读性很差。
表单回显:编辑老人的时候,需要把数据库中的值填回表单。“修改”链路我建议这样走:
- 点击编辑按钮,跳转到
/admin/elder/edit?id=xxx。 - Controller 根据 id 查出老人记录,放进 Model。
- JSP 的
<input>标签通过 EL 表达式value="${elder.name}"回显。 - 用户修改完成后提交,Controller 接收参数再执行 update。
这中间有一个高频问题:日期类型回显。如果老人的生日字段是 Date 类型,用 ${elder.birthday} 会输出一串英文格式或时间戳,很难看。我的解决办法是在实体类上用 @DateTimeFormat(pattern = "yyyy-MM-dd") 注解,同时在 JSP 中使用 fmt:formatDate 标签格式化输出:
jsp复制<fmt:formatDate value="${elder.birthday}" pattern="yyyy-MM-dd" />
4.3 服务预约与工单状态流转实现
预约与工单模块是系统的业务核心。前台(老人/家属端)提交预约请求后,状态为“待处理”,后台(管理员/员工端)可以接单、完成、取消。
状态流我用一个枚举类管理,避免魔法数字散落各处:
java复制public enum AppointmentStatus {
PENDING(0, "待处理"),
PROCESSING(1, "进行中"),
FINISHED(2, "已完成"),
CANCELLED(3, "已取消");
}
实现的关键点在于状态变更的权限控制:只有管理员能取消预约,只有员工能接单和完成。这个逻辑我放在了 Service 层里做判断,而不是让每个 Controller 各自处理,避免逻辑分散导致后期维护困难。
创建预约时还需要做一次数据校验:老人字段必须存在、服务项目必须为启用状态、预约时间不能是过去的时间。我在前端做了一个基本校验,在后端 Service 里又做了一层兜底校验,形成双保险。永远不要相信前端传过来的数据,这句话在任何项目里都适用。
4.4 前台展示与资源路径的细节处理
前台页面包括首页展示、服务项目列表、公告资讯详情等。因为前台直接面向公众,不需要登录,所以这些 Controller 路径要排除在拦截器之外。
资源文件的路径处理是个容易出问题的地方。我的工程结构是这样的:
code复制src/main/webapp
├── static/
│ ├── css/
│ ├── js/
│ └── images/
├── WEB-INF/
│ └── jsp/
静态资源在 Spring MVC 中默认会被拦截器拦截,所以必须在 spring-mvc.xml 中配置放行:
xml复制<mvc:resources mapping="/static/**" location="/static/"/>
配置完用 <link href="${pageContext.request.contextPath}/static/css/style.css"> 引入。务必要加上 ${pageContext.request.contextPath},这是 JSP 项目里最容易犯的错——直接写 /static/css/style.css,本地部署在根路径没事,一旦项目在 Tomcat 下改了访问路径就会 404。
5. 部署实战:传统 JSP 项目如何打包与发布
5.1 WAR 包构建的两种方式
这种传统 SSM + JSP 项目,最终交付形态就是 WAR 包,放到 Tomcat 的 webapps 目录下就能运行。构建过程有两条路线:
方式一:IDEA 直接构建 Artifact
在 Project Structure 里配置 Web Application Archive,然后 Build -> Build Artifact -> Build,IDEA 会自动生成 WAR 包。这种方式适合不想折腾 Maven 的同学,但有一个问题:如果依赖 jar 包没有打进 WAR 的 WEB-INF/lib 下,部署后启动就会报 ClassNotFound。
方式二:Maven 打包
如果你用了 Maven 管理依赖,执行 clean package 即可自动产出 WAR 包。关键要在 pom.xml 里引入 Maven War Plugin 并指定输出的最终名称:
xml复制<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>sunset-service</warName>
</configuration>
</plugin>
failOnMissingWebXml 设为 false 是因为 Java EE 较新版本的 web.xml 不再强制要求,但传统项目还是建议保留 web.xml。
5.2 Tomcat 部署的完整步骤
部署过程并不复杂,但有一个先决条件:本地开发用的 MySQL 版本与服务器的 MySQL 版本要兼容。我之前在一台 MySQL 5.7 的机器上开发,生产环境是 MySQL 8.0,结果启动时连接被拒,原因是 MySQL 8.0 默认认证插件是 caching_sha2_password,而驱动用的还是旧版,需要手动替换连接驱动为 mysql-connector-java 8.x 版本,并把 URL 加上 useSSL=false&allowPublicKeyRetrieval=true。
正常流程:
- 把
sunset-service.war复制到 Tomcat 的webapps/目录。 - 启动 Tomcat(
bin/startup.sh或bin/startup.bat)。 - Tomcat 会自动解压 WAR 包并部署。
- 访问
http://IP:8080/sunset-service/验证系统。
如果是 Linux 服务器,记得确认防火墙是否放行了 8080 端口,以及 MySQL 是否允许远程连接。MySQL 默认绑定 localhost,远程部署时需要修改 bind-address 并授权远程访问用户。
5.3 通过 Nginx 反向代理部署(含路径规则)
如果不想让用户直接访问 Tomcat 的 8080 端口,可以在前面加一层 Nginx 做反向代理,用 80 端口对外提供服务。核心配置如下:
nginx复制server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/ {
alias /opt/tomcat/webapps/sunset-service/static/;
expires 7d;
}
}
这里有一个经常被忽略的问题:JSP 请求转发到 Tomcat 后,Tomcat 内部要用 URL 重写来保证路径正确。Nginx 在 proxy_pass 时如果不带 URI 后缀(即后面不带 /),会保持原始的请求 URI 转发,这是正确的;如果配置成了 proxy_pass http://127.0.0.1:8080/,会让 JSP 里基于 request.getContextPath() 生成的相对路径全部失效。我第一配置就栽在这上面,首页能打开,但点击任何链接都 404,排查了半小时才发现是多了一个斜杠。
6. 开发中踩过的坑与排查思路实录
6.1 中文乱码问题的三层防线
乱码问题是 JSP 项目里最高频的问题,没有之一。它可能出现在三层位置:请求参数、数据库存储、页面渲染。我的解决方案是三层同时下手:
第一层,请求编码。 在 web.xml 配置 Spring 提供的编码过滤器:
xml复制<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
第二层,数据库连接编码。 在 JDBC URL 中加入 characterEncoding=UTF-8 参数,同时确保 MySQL 表结构字符集是 utf8mb4(注意不是 utf8,两者在 emoji 和部分特殊字符上有兼容差异)。
第三层,JSP 页面编码。 每个 JSP 文件头部必须写:
jsp复制<%@ page contentType="text/html;charset=UTF-8" language="java" %>
我见过很多同学只写了 pageEncoding="UTF-8",没写 contentType,页面还是会乱码。这两个属性作用不同,都要写全。
6.2 常见报错信息速查表
我把实际开发中最常遇到的报错整理成了一张表,方便你遇到问题时快速定位:
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
ClassNotFoundException: com.mysql.jdbc.Driver |
缺少 MySQL 驱动 jar 或版本不匹配 | 加入 mysql-connector-java 依赖,注意 MySQL 8 用 com.mysql.cj.jdbc.Driver |
org.apache.ibatis.binding.BindingException: Invalid bound statement |
Mapper 接口与 XML 映射文件不匹配 | 检查 namespace、方法 id、parameterType/resultType 是否对应 |
no suitable driver found for jdbc:mysql |
JDBC URL 或驱动配置问题 | 检查 URL 格式,加 jdbc:mysql:// 前缀,确认驱动版本 |
Neither BindingResult nor plain target object for bean name 'xxx' |
表单对象无法绑定到 ModelAttribute | 确认 Controller 方法传入参数与 JSP form:form 的 modelAttribute 名称一致 |
404 错误(页面能找到但请求找不到) |
路径映射问题 | 检查 @RequestMapping 值是否拼写错误,以及静态资源是否被拦截 |
| 页面能启动但不显示数据 | Mapper 没被扫描到或 SQL 写错 | 用日志输出 SQL 执行结果,检查 <context:component-scan> 包路径 |
6.3 排查思路的通用路径
我在排查 bug 时,习惯按照一条固定的路径走,效率比漫无目的“试”高很多:
第一步,看日志。 Tomcat 的 catalina.out 和控制台日志永远是最直接的线索。不要一报错就到处改代码,先把 StackTrace 从头到尾读一遍,定位到具体类和方法。
第二步,确认数据是否正确。 如果查询返回空数据,先直接在 MySQL 里执行同样的 SQL,确认是 SQL 本身的问题还是参数传递的问题。很多“业务逻辑不对”最终发现是 SQL 关联条件写错了。
第三步,确认请求是否到达 Controller。 在方法入口加一行 System.out.println 或使用日志框架输出参数值,比靠眼睛猜可靠得多。Spring MVC 项目我习惯用 SLF4J + Logback 做日志输出,把关键请求参数打印出来。
第四步,怀疑配置时做最小化验证。 当你觉得配置文件有问题但又拿不准时,写一个极简的 Test 类,只加载核心容器验证一个 Bean 是否能创建成功。把复杂系统拆成最小可验证单元,是我用过最有效的定位方法。
6.4 关于 JSP 性能与安全的几个补充提醒
JSP 的性能问题常被拿出来说事:首次访问需要编译、服务端渲染压力大。但在这种小规模业务系统里,这些缺点的影响几乎可以忽略。真正值得注意的反而是安全方面的问题:
- JSP 页面不要直接放在根目录。放在
WEB-INF下,用户无法直接通过 URL 访问,这是最基础的安全措施。 - 小心 SQL 注入。MyBatis 的
#{}是预编译,安全;${}是字符串拼接,不安全。动态排序、动态表名的场景必须用${},这时候要手动校验参数是不是白名单内的值。 - 登录密码别用明文。即使是毕设,也能展示出你具备基本的安全意识,答辩时这是一个加分项。
7. 项目后续可扩展的方向与我的个人体会
写完这套系统,我对“技术服务于业务”这句话有了更具体的感受。SSM + JSP 的组合谈不上新潮,但它在恰当的场景里就是最顺手的选择——配置虽然繁琐,但每一行配置背后都对应一个明确的作用,跑通之后你对整个 Java Web 技术栈的理解深度是那些“一键生成”的脚手架给不了的。
如果你时间比较充裕,可以从以下几个方向做扩展:
第一个方向是数据可视化。给管理员加一个统计看板,展示各服务项目的预约量趋势、老人年龄段分布、服务完成率等。实现方式不复杂,后端查出来数据返回给前端,用 ECharts 画图表即可。只需要新增几张统计用的 Mapper 方法,业务逻辑复用现有的即可。
第二个方向是消息通知。可以在预约状态发生变化时,给“待处理”变成“已接单”的操作记录一条消息到数据库,管理员登录后在顶部看到一个未读消息的红点。这块适合练习 AOP 配合自定义注解的使用——在 Service 方法上标记 @Notify 注解,通过切面统一处理通知逻辑,不用侵入业务代码。
第三个方向是接口化改造。如果以后想逐步从 JSP 过渡到前后端分离,可以把原来的 Controller 层拆分成两层:一部分返回 JSON 数据的 RestController 和一部分返回视图的 Controller 并存,前端逐步替换。但说实话,既然选择了 JSP,这条路在短期内的投入产出比不高,我更建议把技能栈直接切换到 Spring Boot + Vue,而不是在旧项目上做“过渡”。
最后再分享一个实际开发中的小技巧:数据库脚本一定要用版本化管理,我建表后先导出了一份 sunset_service.sql 存入项目根目录,之后每次改动表结构都会重新导出并加上版本号注释(如 -- v1.2 add service_item price field)。这样做不仅在部署上省心——直接执行 SQL 就能把环境搭起来,而且在后期写论文的“系统设计”章节时,有清晰的演进记录可查。这个习惯保持下来,会让你避免无数次“这个字段是哪个版本的数据库里加的”之类的困惑。
做这个“夕阳红”老年服务系统,最大的收获不在于代码量有多复杂,而在于我完整走通了一条从需求分析、数据库设计、框架整合、功能实现到部署上线的闭环链路。希望这篇分享能帮你少走一些我当时走过的弯路,尤其是在那些配置文件和路径问题上。如果你也正在做类似的 SSM 项目,照着上面的步骤一步步踩,遇到问题随时翻一翻常见报错表,比自己闷头折腾要快得多。
