
开头先交代一下背景。接到这个“基于 javaweb 和 mysql 的 ssmmaven 律师事务所律师管理系统”需求时我下意识就知道这是个教科书级别的工程实践题目JavaWeb 老牌技术栈SSM 三件套Maven 管依赖MySQL 存数据JSP 做页面。听起来没什么新意但等你真正开始设计业务表结构时才会发现律师管理系统的难点根本不在框架配置而在业务关系太杂——律师、当事人、案件、开庭、收费每一步都互相牵连。这篇文章我会把整个项目从需求拆解、数据库设计、框架搭建到核心功能落地再到部署排坑的完整过程讲清楚。如果你是正在做同款课程设计或毕设的在校生或者律所里想搞一套内部管理系统但不知道怎么落地的行政人员这篇可以直接当作参考蓝本照着改一改就是一套能跑的东西。1. 需求拆解律师事务所管理系统该管哪些事1.1 角色与核心业务场景做系统之前先得搞清楚律所的实际工作流。前台接待一个当事人登记来访信息然后把案件录入系统指定承办律师律师登录系统查看自己的案件列表更新案件进展管理员负责全局把控看所有案件的进度、收费情况维护律师资料和用户账号。所以我最终将系统角色定为两类管理员负责律师信息维护、用户账号分配、案件总览、收费记录查看。律师登录后只能看分配给自己的案件并修改对应案件的进度和开庭信息。这种角色划分在毕设答辩里很容易讲清楚权限关系简单不必做复杂的 RBAC但又在业务上说得通。实际小型律所里前台接待和行政财务往往也是通过管理员账号来操作的所以两类角色已经完全够用。1.2 功能清单与模块划分需求落到功能模块我整理了如下的清单登录模块用户名密码登录Session 记录登录态拦截器做访问控制退出登录。律师管理模块律师信息的增删改查支持按姓名、执业方向、手机号模糊搜索。当事人管理模块当事人基本资料包括姓名、证件号、联系方式、住址等信息。案件管理模块案件登记、律师指派、案件状态流转待立案、审理中、已结案、已归档以及开庭时间和地点的维护。收费管理模块每个案件关联的律师费和诉讼费记录记录收费金额和状态。这套功能对律所来说就是最核心的“收案—办案—结案”闭环。别一开始就想着加一堆花哨功能先把这条主线走通系统才有实用价值。1.3 优先级排序先做哪些功能才能让系统真正用起来如果是从零开始写我建议功能开发顺序按这个来第一优先级登录权限 律师管理 案件管理。这三块是系统骨架做完整个项目就已经能演示了。 第二优先级当事人管理 收费管理。这两个模块是业务闭环不可或缺的部分但逻辑相对简单可以在骨架跑通后见缝插针地补上。 第三优先级统计报表、图表、导入导出。这类功能是加分项有时间再做。我见过不少同学上来先写复杂的功能模块结果最基本的律师列表都还没跑通。先把摸底的数据流打通后面所有功能都是往这个框架里塞东西效率会高很多。2. 技术选型SSM Maven MySQL/JSP 这套组合为什么还能打2.1 为什么是 SSM而不是一上来就 Spring Boot题目明确要求了 JavaWeb SSM这个组合在现在看确实有点“复古”但反倒是学校课程设计和毕业设计里最常见的要求。SSM 的好处在于它在传统 JavaWeb 里保留了一条非常完整的技术链路Tomcat 容器启动项目Spring 负责对象管理SpringMVC 接收请求MyBatis 操作数据库JSP 在服务端渲染页面。这个流程会把 Servlet、Filter、拦截器、IoC、ORM 这些概念全部暴露在你面前。而 Spring Boot 把这些细节都封装了虽然开发快但答辩时老师问 HTTP 请求从进入 Web 容器到返回页面之间发生了什么就很容易回答不上来。如果你以后工作要写较复杂的业务中间件或者在老项目里做维护基本都得面对 SSM 这套代码。所以不要觉得它老就嫌弃能把这套东西讲清楚底子是扎实的。2.2 Maven 在项目里到底扮演什么角色没有 Maven 的时候JavaWeb 项目最痛苦的事就是手工下载 jar 包然后在 IDEA 里逐个添加到模块依赖里。版本冲突、漏包、传给别人后缺一堆 jar 文件这些都是经典问题。Maven 进项目之后一切 jar 管理都变成了在 pom.xml 里声明坐标Maven 自动去仓库拉取。Maven 在这类系统里做了三件非常重要的事依赖管理Spring、SpringMVC、MyBatis、MySQL 驱动等所有 jar 都由 Maven 统一管理版本。构建打包执行 mvn clean package 后可以直接生成一个可部署的 war 包。传统 JSP 项目不像 Spring Boot 那样自带 Tomcatwar 包扔到 Tomcat 的 webapps 目录里就能运行。环境可复制pom.xml 本身就是项目环境的完整描述别人拿到项目后 mvn install 就能拉齐所有依赖不会因为个人电脑环境不同导致“我这儿能跑你那儿跑不了”。2.3 JSP 与 JS 的分工JSP 和 JS 的工作边界一定要清晰。JSP 负责服务端渲染尤其是列表页的循环展示用 JSTL 的 c:forEach 直接遍历后台传过来的集合非常方便。JS 则负责客户端交互类的事情比如表单校验、弹窗确认、Ajax 局部刷新以及删除后自动刷新当前页这种操作。简单来说需要服务器端数据的地方用 JSP EL JSTL需要动态交互的地方用 JS 发起 Ajax 请求后台返回 JSON前端重新渲染局部内容。合理分工后页面代码不会乱成一锅粥维护成本也会低很多。2.4 项目分层与包结构包结构设计对后面扩展非常重要。我习惯这样分com.law ├── controller // 控制层接收请求并调用业务 ├── service // 业务层接口 │ └── impl // 业务实现 ├── dao/mapper // MyBatis 数据访问层接口 ├── entity // 数据库实体类型 └── common // 公共工具类、全局常量、拦截器等 resources ├── spring // Spring 和 SpringMVC 配置 ├── mapper // MyBatis XML 映射文件 └── jdbc.properties // 数据库连接配置 webapp └── WEB-INF ├── web.xml └── views // JSP 页面目录这种分层的意义在于职责分明Controller 只做参数接收和视图跳转Service 只做业务逻辑Mapper 只做 SQL。后面任何一个环节出了问题定位都非常快。3. 数据库设计一张业务表如何撑起整个律所3.1 核心表结构与字段解释数据库设计这部分是整个项目最值得花时间的地方。我曾见过有人把律师和案件的所有信息塞进一张大表结果字段越堆越多到最后一个实体加个执业方向字段都要改表结构。正确的做法是把每个主体拆成独立表让数据各自归位。系统最终需要 5 张核心表用户表、律师表、当事人表、案件表、收费表。用户表 sys_user字段名类型说明idint主键自增usernamevarchar(50)登录账号唯一passwordvarchar(64)登录密码MD5 加密后存储real_namevarchar(50)真实姓名rolevarchar(20)角色admin/lawyerstatusint状态1 启用0 禁用create_timedatetime创建时间律师表 lawyer字段名类型说明idint主键user_idint关联登录账号的可空外键namevarchar(50)律师姓名gendervarchar(10)性别phonevarchar(20)手机号specialtyvarchar(100)执业方向如婚姻家庭、刑事辩护titlevarchar(50)职称如专职律师、合伙人statusint在职状态当事人表 customer字段名类型说明idint主键namevarchar(50)姓名gendervarchar(10)性别phonevarchar(20)联系电话id_cardvarchar(20)证件号addressvarchar(200)住址create_timedatetime登记时间案件表 case_info字段名类型说明idint主键case_novarchar(50)案件编号业务查询全靠它titlevarchar(100)案件标题customer_idint当事人外键lawyer_idint承办律师外键courtvarchar(100)受理法院court_datedatetime开庭时间case_typevarchar(50)案件类型statusint状态0 待立案 / 1 审理中 / 2 已结案 / 3 已归档create_timedatetime收案时间update_timedatetime最后更新时间收费表 case_fee字段名类型说明idint主键case_idint案件外键fee_typevarchar(50)费用类型如律师费、诉讼费amountdecimal(10,2)金额remarkvarchar(200)备注pay_statusint0 未支付 / 1 已支付pay_timedatetime支付时间这套结构已经能满足律所系统 90% 以上的日常业务场景字段没有过分冗余也没有遗漏核心信息。3.2 表关系设计案件、律师、当事人如何关联表之间的关系核心在案件表。一份案件只能由一个律师承办但一个律师名下可以挂多个案件所以 lawyer 与 case_info 是一对多关系。当事人与案件同理一方当事人可以有多起案件所以是一对多关系。案件和收费是严格的一对多一个案件有多个费用记录。在 MySQL 中一般不强制使用物理外键约束因为线上项目经常会因为外键导致删改数据困难。我建议在表设计阶段把逻辑外键关系画清楚但在建表语句里只在需要索引的查询字段上创建普通索引不建物理外键。比如 case_info 表的 lawyer_id 和 customer_id 都要加索引否则案件列表按律师筛选时会慢。3.3 初始化数据与状态枚举的设计建完表之后一定要准备一份 insert 数据。开发阶段如果每次都要手工往库里录数据效率非常低。我习惯直接写 initialization.sql里面放入默认管理员账号、3~5 个律师、5 个当事人、5 条案件数据并且案件覆盖所有状态这样在开发时列表页、详情页、统计页都有数据可看页面效果一眼就能验证。状态字段建议用数字而不是中文。因为 JSP 页面显示时可以通过三元运算符或者 MyBatis 的 SQL 查询直接做转换数据库只存数字可以让状态值更稳定避免出现“审理中”和“审 理中”这种不一致的情况。页面显示转换我一般这样处理c:choose c:when test${case.status 0}待立案/c:when c:when test${case.status 1}审理中/c:when c:when test${case.status 2}已结案/c:when c:when test${case.status 3}已归档/c:when /c:choose4. SSMMaven 骨架搭建从零到跑通一个查询4.1 创建 Maven 项目与配置 pom.xml用 IDEA 新建 Maven 项目时选择 webapp 原型骨架当然也可以直接创建一个空 Maven 项目然后手动补上 src/main/webapp 目录结构和 web.xml。个人更推荐后者因为 webapp 原型生成的目录经常版本老旧还要手动改。pom.xml 里的核心依赖如下properties spring.version5.3.18/spring.version /properties dependencies !-- Spring -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.11/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency !-- 数据库驱动和连接池 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.14/version /dependency !-- JavaWeb 基础 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies版本搭配有个关键点Spring 5.x 与 mybatis-spring 2.x 搭配才不会有兼容问题如果把 mybatis-spring 用成 1.x 版本启动时容易报 ClassNotFound 之类的错误。MySQL 驱动如果连的是 MySQL 8 数据库驱动类要换成 com.mysql.cj.jdbc.Driver这就是很多同学报错找不到驱动类的根源。4.2 Spring、SpringMVC、MyBatis 三方配置整合配置是 SSM 最容易劝退人的地方。我按最小化原则来写只保留必须的配置项。web.xml 里需要配置三个东西Spring 容器监听器、SpringMVC 的前端控制器、字符编码过滤器。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_4_0.xsd version4.0 !-- 加载 Spring 容器 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- SpringMVC 前端控制器 -- servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping !-- 字符编码过滤器 -- filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping /web-appapplicationContext.xml 核心配置context:component-scan base-packagecom.law.service/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.law.dao/ /bean tx:annotation-driven transaction-managerdataSourceTransactionManager/spring-mvc.xml 核心配置mvc:annotation-driven/ context:component-scan base-packagecom.law.controller/ mvc:resources mapping/static/** location/static// bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean配置全部做完以后在 pom.xml 所在目录执行mvn clean install如果 BUILD SUCCESS说明骨架已经准备好了。4.3 第一条业务链路查询律师列表骨架建好后别急着做复杂功能先跑通一条最简短的链路就是登录后展示律师列表。我以律师列表作为验证链路先写控制器Controller RequestMapping(/lawyer) public class LawyerController { Resource private LawyerService lawyerService; RequestMapping(/list) public String list(Model model) { ListLawyer list lawyerService.findAll(); model.addAttribute(lawyers, list); return lawyer/list; } }Service 层Service public class LawyerServiceImpl implements LawyerService { Resource private LawyerMapper lawyerMapper; Override public ListLawyer findAll() { return lawyerMapper.findAll(); } }Mapper 接口public interface LawyerMapper { ListLawyer findAll(); }Mapper XMLselect idfindAll resultTypecom.law.entity.Lawyer SELECT id, name, gender, phone, specialty, title, status FROM lawyer WHERE status 1 /select这行链路通了说明 SpringMVC 路由、Spring 依赖注入、MyBatis 数据库访问、JSP 视图渲染全部正常。之后的开发基本就是复制这条链路的模式写一个实体、一个 Mapper、一个 Service、一个 Controller、一个 JSP 页面循环往复。5. 核心功能实现登录、律师管理、案件流转5.1 登录与权限控制登录模块最容易被做成摆设。比如只在页面上判断用户是否登录后端完全不设防这样的系统等于没有登录。后端权限控制一定要放在拦截器上。我定义了一个 LoginInterceptor在请求进入 Controller 之前检查 Sessionpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login); return false; } return true; } }然后在 spring-mvc.xml 里配置拦截路径排除登录接口和静态资源mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ bean classcom.law.common.LoginInterceptor/ /mvc:interceptor /mvc:interceptors密码存储也别直接明文入库。登录注册类项目的首个安全底线就是密码加密。这里用最简单的 MD5 加固定盐处理工作项目里推荐 BCrypt但毕设场景 MD5 足以说明你考虑到了安全问题public static String md5(String source) { String str source law; try { MessageDigest digest MessageDigest.getInstance(MD5); byte[] result digest.digest(str.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : result) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }登录接口逻辑很直白根据用户名查询用户比对密码把用户信息放进 Session最后按角色跳转到不同首页。5.2 律师管理增删改查与搜索律师管理模块是典型单表 CRUD代码结构相对固定。需要分页时我直接引入 PageHelper 分页插件避免自己手写 LIMIT 参数dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper/artifactId version5.3.2/version /dependency在 MyBatis 配置里注册插件然后在 Service 调用第一行加 PageHelper.startPage(pageNum, pageSize)后续查询就自动带上 LIMITpublic PageInfoLawyer search(String keyword, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListLawyer list lawyerMapper.search(keyword); return new PageInfo(list); }页面端用 JSTL 标签展示数据c:forEach items${pageInfo.list} varlawyer tr td${lawyer.name}/td td${lawyer.gender}/td td${lawyer.phone}/td td${lawyer.specialty}/td td a href${pageContext.request.contextPath}/lawyer/edit?id${lawyer.id}编辑/a a hrefjavascript:void(0) onclickdeleteLawyer(${lawyer.id})删除/a /td /tr /c:forEach这里有个小细节删除操作不要用普通链接直接发送 GET 请求因为搜索引擎爬虫和浏览器预加载都可能触发危险操作。用 onclick 跳出一个确认框后再发起 POST 请求。5.3 案件管理最核心的业务流转案件管理是整个系统的重点它涉及到三张表的联动。在录入案件时需要从下拉框中选择当事人和承办律师select namecustomerId c:forEach items${customers} varcustomer option value${customer.id}${customer.name}/option /c:forEach /select select namelawyerId c:forEach items${lawyers} varlawyer option value${lawyer.id}${lawyer.name}/option /c:forEach /selectController 接收案件参数时customerId 和 lawyerId 只作为普通 int 参数即可在 Service 层把外键写入 case_info 表。这样设计符合职责分工Controller 不直接操作持久层。案件状态更新时我建议在 Service 中做简单的业务校验防止发生不合法跳转比如已归档的案件不允许再跳回“审理中”public void updateStatus(Integer caseId, Integer newStatus) { CaseInfo existing caseMapper.findById(caseId); // 简单的业务规则归档之后不允许继续流转 if (existing.getStatus() ! null existing.getStatus() 3) { throw new RuntimeException(已归档案件不可修改状态); } caseMapper.updateStatus(caseId, newStatus); }类似这种状态约束不太复杂但能体现你对业务细节的思考。5.4 JSP JS 交互的落地技巧列表页的翻页是必然需求我直接用 JSP 页面拼接分页链接后端把当前页码和总页数通过 PageInfo 传到 Model 里。分页控件ul lia href?pageNum1首页/a/li lia href?pageNum${pageInfo.prePage}上一页/a/li lispan${pageInfo.pageNum} / ${pageInfo.pages}/span/li lia href?pageNum${pageInfo.nextPage}下一页/a/li lia href?pageNum${pageInfo.pages}末页/a/li /ul另一种场景是 Ajax 删除。删除按钮不刷新整页而是先发一个请求到后端成功后再 reload 当前表格function deleteCase(id) { if (!confirm(确定要删除该案件吗)) { return; } $.post(contextPath /case/delete, {id: id}, function (data) { if (data.code 200) { location.reload(); } else { alert(删除失败 data.msg); } }, json); }JSP 页面最好都使用统一的 basePath 变量来拼接请求路径。在 JSP 引入头部的通用片段里定义c:set varctx value${pageContext.request.contextPath}/这样所有链接都写成${ctx}/lawyer/list不会因为部署路径变化导致全站 404。6. 实操中的排坑MySQL 连接、Maven 依赖、JSP 部署6.1 MySQL 连接不上最典型的三种原因本地在 Linux 或 Mac 上跑项目时最容易遇到ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。这个错误几乎都是 MySQL 服务没有正常启动或者 socket 文件路径不对。解决办法是先确认服务状态再去检查配置文件里的 socket 路径。不是密码错误第一步查服务这是最重要的排查方向。第二种很常见的坑是 MySQL 8 的驱动类问题。如果你的 MySQL 是 8.0 以上版本jdbc.properties 里要这样配置jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/law_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai注意 serverTimezone 参数不能少否则会报时区错误useSSLfalse 是为了关闭 SSL 握手不然可能出现 ssl 连接错误。第三种是数据库连接池配置正常但中文乱码。检查三处是否统一为 UTF-8jdbc.url 里的 characterEncoding、web.xml 里的 CharacterEncodingFilter、JSP 页面顶部的 pageEncoding。这三处只要有一处不一致中文数据就可能变成问号。6.2 Maven 依赖引不进来仓库和版本冲突排查开发中最烦人的问题就是 pom.xml 里写了依赖代码里明明有 importIDEA 却一直标红提示找不到类。通常原因是本地仓库没有下载完整或者 IDEA 缓存了旧索引。我遇到这类问题后的标准处理步骤mvn clean install mvn dependency:tree先用 dependency:tree 看依赖树能清楚看到每个 jar 是否被依赖以及版本冲突的情况。如果是 IDEA 索引问题就点 File - Invalidate Caches 重启 IDEA如果是依赖缺失就删掉本地仓库对应目录再重新 mvn 拉取。另外强烈建议在 Maven 的 settings.xml 里配置阿里云镜像仓库不然第一次构建项目下载依赖会慢到怀疑人生mirror idaliyun/id mirrorOfcentral/mirrorOf nameAliyun Maven Repository/name urlhttps://maven.aliyun.com/repository/central/url /mirror6.3 传统 JSP 项目如何打包部署SSM 项目是标准 war 包。部署时可以确认 target 目录里生成的 war 文件然后放入 Tomcat 的 webapps 目录或者用 IDEA 的 Tomcat 部署插件直接运行。这里特别提醒 Tomcat 版本问题如果你用 Tomcat 10 来跑这个项目大概率会直接启动失败。因为 Tomcat 10 已经把 javax.servlet 换成了 jakarta.servlet而项目里的 javax.servlet-api 依赖和内置代码都是老的 javax 包两者不兼容。SSM JSP 项目老老实实配 Tomcat 9 就对了这是很多同学部署时最容易忽略的版本坑。另外在 IDEA 里运行此类项目时需要确保 webapp 目录被正确标记为 Web 资源目录。如果明明配置都对访问 JSP 却 404就去 Project Structure 里的 Web Module 设置里确认 Deployment Descriptor 和 Web Resource Directory 是否指向正确位置。7. 复盘与扩展这个系统的价值在哪里7.1 实际开发中我体会最深的几个点整个项目做完我最大的感触是这类系统真正的难点从来不是框架而是把“现实业务翻译成数据模型”。比如开庭时间冲突。真实的律所里同一个律师在同一时间段不能有两个开庭安排这个约束如果只在页面层做提醒数据库层其实很难完全阻止并发情况。但即便不做严格约束至少在录入开庭时间时通过查询该律师名下已存在同一天的案件来做一个人性化提示效果就很不一样。再比如归档保护。已归档案件不应该允许直接删除而应该保留历史记录。很多同学做删除功能时不管状态直接 delete这在真实业务中是危险的。我建议在 Service 层对删除做前置校验public void deleteCase(Integer caseId) { CaseInfo existing caseMapper.findById(caseId); if (existing ! null existing.getStatus() 3) { throw new RuntimeException(归档案件不可删除); } caseMapper.deleteById(caseId); }第二点体会是权限检查绝不能只写在 JSP 里。菜单隐藏了不等于接口保护了。后端一定要用拦截器或过滤器统一拦截这是安全底线。7.2 后续可以怎么继续扩展如果拿到这个项目后想继续做深方向非常清晰第一接文件上传功能。把案件证据材料、起诉状、判决书等都传到服务器固定目录案件详情页提供下载入口。只要保证上传文件名重命名避免中文文件名和路径穿越问题即可。第二改造成前后端分离。保留现有 SSM 接口把 JSP 换成 Vue 或 React前端通过 Restful API 调用。这个改造其实不会很难因为现有接口已经按 Controller 分层了把返回视图改成返回 JSON 即可。第三增加统计报表。用 ECharts 在管理员首页展示近期收案数量、各类型案件占比、律师办案排名这些 SQL 主要是 count 和 group by但在答辩或项目展示时效果非常加分。第四引入日志切面用 AOP 统一记录用户操作。对审计有要求的律所场景非常实用。这个项目做到能跑只是起点能把这些细节考虑进去才算真正理解了一个管理系统该如何设计。希望这篇复盘能让你在动工前少走一点弯路。