1. 项目概览与核心价值
做毕设选管理系统类题目,估计十个人里有八个都会往 SpringBoot + Vue 这个组合上靠。原因很简单,这套技术栈在当前 Java Web 方向几乎是“标准答案”,项目结构清晰、知识点覆盖面广、答辩时也好讲。但你真去搜“律师事务所案件管理系统”这种题目,会发现一个尴尬的现实:要么是烂大街的图书管理、学生管理改个皮,要么是代码跑不起来、数据库缺表少关系、接口文档跟实际实现对不上号。
这篇博客要聊的这套“SpringBoot + Vue 律师事务所案件管理系统”,算是一个比较完整的参考实现。它的核心价值在于:后端用 SpringBoot 2.x + MyBatis Plus,前端用 Vue 2 + Element UI,数据库脚本、接口文档、源码全部齐活,拿到手改改就能用于课程设计或者毕业设计答辩。更重要的是,它的业务模型不是简单堆 CRUD,而是围绕律所真实工作流设计了案件登记、客户管理、律师指派、开庭提醒、文书归档、收费统计这几个核心模块,数据表之间有清晰的关联约束,前台页面也不是傻白甜的表格套模板,而是分了角色权限、有流程状态流转。
适合谁来参考?一种是正在做 Java Web 方向毕设、想快速搭底子的同学;另一种是刚学完 SSM 或 SpringBoot,想看看一个完整前后端分离项目长什么样、表结构怎么设计、权限怎么控、接口规范怎么定的初学者。如果你已经能独立写增删改查,这个项目你可以直接跳过“看懂”阶段,重点去研究它的表关系设计思路和权限模块实现方式。
2. 技术选型与架构拆解
2.1 为什么是 SpringBoot + Vue 而不是 SSM + JSP
这个问题问的人最多,尤其是一些传统高校的老师还在用 SSM 教 Java Web。从毕设角度讲,SpringBoot + Vue 的优势不在“技术更高级”,而在于架构清晰、前后端职责分离明确,写起来反而比 JSP 时代省心。
后端选 SpringBoot 2.x,核心原因是它对第三方组件的整合成本低。你要接 MyBatis Plus、Spring Security、JWT、Swagger、EasyExcel,基本引入依赖加几行配置就能跑。对比 SSH、SSM 时代那套繁琐的 XML 配置,SpringBoot 的自动配置机制能让你把更多精力放在业务逻辑而不是配置地狱里。
前端选 Vue 2 + Element UI,同样不是因为它多时髦,而是生态成熟、中后台管理系统的轮子齐全。Element UI 的表格、表单、弹窗、树形控件、日期选择器基本覆盖了律所案件管理界面的绝大多数需求,自己手写组件搞不定的,Element UI 能直接救场。Vue 2 虽然进入维护期,但对于毕设项目和中小型管理后台,稳定性足够了,网上资料也多到你想吐。
再往深一层说,前后端分离这种模式本身就很契合律所案件管理系统的业务特点。律师、行政、合伙人三类角色访问的界面路径不同、操作权限不同,前端通过路由守卫和菜单权限控制做界面层拦截,后端通过 Spring Security + JWT 做接口层校验,双层防护。而表单验证、列表筛选、状态切换这些交互在 Vue 里实现,响应速度和体验也远比 JSP 页面刷新舒服。
2.2 整体目录结构与职责边界
一个合格的前后端分离项目,目录结构本身就体现着职责边界。后端是标准的 SpringBoot 分包模式,controller、service、mapper、entity、dto、vo、config、utils 各司其职。请不要把所有 Java 文件都塞进 controller 包,也不要让 service 接口层和实现层混在一起。这套代码里每个模块的业务逻辑都拆了接口和实现,原因只有一个:方便你后期扩展和测试。
前端用 Vue CLI 生成的标准工程,src 下面分 api、router、store、views、components、utils。这里有个关键设计值得借鉴——api 层单独抽出来,每个页面组件不直接写 axios 请求路径,而是通过 api 模块统一管理接口。这样做的好处很实际:后端接口路径如果调整,你只需要改 api 模块,而不用去几十个 .vue 文件里挨个翻字符串。
代码目录结构示例(后端核心部分)
lawyer-management ├── src/main/java/com/example/lawfirm │ ├── controller # 接口入参校验、调用 service、统一返回 Result │ ├── service # 业务逻辑层,接口与实现分离 │ ├── mapper # MyBatis Plus 的 Mapper 接口 │ ├── entity # 数据库实体映射 │ ├── dto # 前端传入参数封装,如 LoginDTO、CaseQueryDTO │ ├── vo # 返回给前端的视图对象,如 CaseStatsVO │ ├── config # 跨域配置、JWT 拦截器、MybatisPlus 分页插件 │ └── utils # JWT 工具类、Redis 工具类、日期处理工具类 ├── src/main/resources │ ├── mapper # 复杂 SQL 的 XML 映射文件 │ ├── application.yml # 数据源、Redis、JWT 过期时间等配置 │ └── sql # 数据库初始化脚本 schema.sql 和 data.sql └── frontend ├── src/api # 统一封装 axios 请求 ├── src/views # 页面组件,按角色模块划分 ├── src/router # 路由配置,含动态路由注入 ├── src/store # Vuex,管理用户信息和权限菜单 └── src/utils # request.js 拦截器、auth.js 存储管理这套结构拿到手,你要做的第一件事不是跑起来,而是先对照目录把请求链路在脑子里过一遍:前端点击按钮 → api 模块发请求 → 后端 controller 接收 → service 处理业务 → mapper 操作数据库 → 数据层层返回 → 前端渲染页面。链路通了,后面所有接口调试都顺理成章。
2.3 权限模型:RBAC 与 JWT 的组合设计
律所案件管理系统里涉及大量保密信息,权限控制不是摆设。这个项目的权限设计用了经典的 RBAC(基于角色的访问控制)模型:用户表关联角色表,角色表关联权限表或菜单表。简单说,就是“用户 → 角色 → 权限”三层,管理员给角色分配权限,用户通过角色获得权限,而不是直接给每个用户单独配权限,那样后期维护会是一场灾难。
认证环节用了 JWT,这是当前 SpringBoot + Vue 分离项目的主流做法。用户在登录接口提交用户名密码,后端校验通过后用 HMAC256 算法生成一个 token 返回前端,前端存到 localStorage 或 Vuex 里,之后每次请求通过 axios 拦截器在 header 里带 Authorization: Bearer token。后端的拦截器校验 token 有效性和过期时间,再用拦截器配置放行白名单,比如 /login、/captcha 这些接口不拦截,其余接口全部走鉴权。
JWT 的优点是无状态、后端不需要维护 session,方便水平扩展。但它也有个明显的坑——token 一旦签发,在有效期内无法主动失效。所以这个项目在用户退出登录时做了两个动作:清除前端本地 token,同时调后端接口把该用户的 token 加入 Redis 黑名单,设置过期时间等于 JWT 的剩余有效时间。这个小细节很值得在答辩时提一嘴,老师会觉得你有思考深度。
3. 数据库设计与核心业务模型
3.1 案件管理系统的实体关系梳理
这套系统的数据库表设计没有追求华而不实的“大而全”,而是围绕律所办案流程提取了六个核心实体:用户、角色、客户、案件、文书、日程/开庭安排。辅助表还有字典表、操作日志表、收费记录表等。
实体之间的关联关系如下:
- 一个客户可以委托多个案件,所以客户表和案件表是一对多。
- 一个案件会被分配到一个主办律师和若干协办律师,案件表和用户表多对多,但你不需要单独搞一张“案件律师关联表”存冗余数据,而是案件表里直接冗余主办律师 id,关联关系用单独的案件协作人表记录。
- 一个案件会产生多个文书,文书表通过 case_id 关联案件表。
- 一个案件可能涉及多次开庭、会见、调解等日程安排,日程表通过 case_id 和 lawyer_id 双向定位。
这种设计是典型的“以案件为中心”的业务建模方式。你写毕设论文时,“系统设计”章节里画 ER 图,按这个逻辑画出来会非常规整,答辩时也能讲清楚为什么每个表存在、为什么某些字段要冗余。
3.2 核心表结构设计解析
下面这张表是整套系统里最关键的案件主表结构。字段设计上除了基本信息,还把状态机字段 case_status 提炼了出来,这个字段直接决定了前端页面按钮的可点击状态和后端业务逻辑的分支走向。
案件信息表 t_case 核心字段
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键自增 |
| case_no | varchar | 案号,规则:LS + 年份 + 流水号 |
| case_title | varchar | 案件标题,列表页主要展示字段 |
| case_type | int | 案件类型,关联字典表:民事/刑事/行政/非诉 |
| case_status | int | 案件状态:1待受理 2办理中 3已结案 4已归档 |
| client_id | bigint | 关联客户表 |
| lead_lawyer_id | bigint | 主办律师,关联用户表 |
| court_name | varchar | 受理法院 |
| case_amount | decimal | 标的金额,用于统计业务收入 |
| filing_date | date | 立案日期 |
| close_date | date | 结案日期 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
| deleted | tinyint | 逻辑删除标记 |
特别注意 deleted 逻辑删除字段,这套系统用的是 MyBatis Plus 的逻辑删除机制,所有删除操作实际上执行的是 UPDATE deleted = 1,而不是物理 DELETE。这样做的价值在于即使误删了案件,数据还在库里,管理员可以通过数据库恢复,另外也保全了关联文书的完整性。
案件状态字段在前端的处理方式是:不同状态渲染不同颜色的标签,并在操作列动态显示可用操作按钮。待受理案件显示“受理”按钮,办理中案件显示“提交结案”按钮,结案后显示“归档”按钮。这套状态驱动的按钮渲染逻辑,比简单的前端 if else 判断更具扩展性,后期加状态只需要改枚举和按钮映射表。
3.3 初始化 SQL 脚本的设计思路
拿到项目先别急着跑,先把 SQL 脚本看一遍。这套脚本不是只建个空表,而是内置了三个角色的测试账号,以及几套演示用案件数据。测试账号分别是 admin(管理员)、lawyer01(律师)、clerk01(行政人员),初始密码统一为 123456,但第一次登录会强制要求修改密码。
SQL 脚本书写上有几个值得借鉴的规范。第一,每张表都有注释,而且是中文字段注释。这个习惯对于你后面写论文的数据字典章节是救命的,直接从数据库导出字段注释就能用。第二,表前缀统一用 t_,索引命名规范成 idx_表名_字段名,虽然麻烦但后期排查慢 SQL 时一眼就能定位。第三,外键约束没有在表结构里物理声明,而是靠应用层逻辑维护。这其实是业内常见做法,物理外键会影响插入删除效率和扩展灵活性,但在 ER 图上你要画出逻辑外键关系,答辩时讲清楚你的取舍理由。
-- 初始化管理员账号,密码为 BCrypt 加密后的 123456 INSERT INTO t_user (username, password, real_name, role_id, status, create_time) VALUES ('admin', '$2a$10$wQ8Jwv3x.Lx4nTz1KqJzM.fMbkUjZqAzXqYqvqYQ7vWZkpZxZc5Z6', '系统管理员', 1, 1, NOW()); -- 初始化演示案件数据 INSERT INTO t_case (case_no, case_title, case_type, case_status, client_id, lead_lawyer_id, court_name, case_amount, filing_date, create_time) VALUES ('LS2024001', '张某诉李某房屋买卖合同纠纷', 1, 2, 1, 2, '北京市朝阳区人民法院', 3500000.00, '2024-03-12', NOW());4. 核心功能模块与实现细节
4.1 登录认证与验证码存储
登录模块在毕设答辩中属于必问环节。这个项目的登录流程不是简单的用户名密码比对,而是分了三步走:前端加载验证码 → 后端生成图形验证码并存储到 Redis → 登录时同时校验验证码和账号密码。
验证码用 Redis 存储,key 是 uuid,value 是验证码内容,过期时间 5 分钟。前端拿到后端的 uuid 存起来,登录时把 uuid 和用户输入的验证码一起提交,后端用 uuid 从 Redis 取出验证码比对,比对完立即删除该 key,防止同一验证码被反复使用。
这套设计把验证码的“一次性”约束做得很到位。很多项目只把验证码存在 session 里,刷新页面就失效,或者干脆不校验。Redis 存储你还可以解释成:如果将来部署多台服务器,session 方案就会失效,而 Redis 是共享存储,天然支持集群环境。这个点是答辩加分的细节。
登录成功后的返回结果不要只给一个 token。请把用户基本信息、角色标识、权限标识、可访问菜单列表一起返回前端。前端拿到菜单列表后通过动态路由注册的方式生成侧边栏导航,这就实现了不同角色登录后看到的菜单不同。
4.2 案件全流程状态管理
前面提到 case_status 字段驱动业务流转,现在拆开来讲具体实现。案件从录入到归档要经过四个状态:待受理、办理中、已结案、已归档。对应的状态流转操作有四个:受理、提交结案、审核结案、归档。
这里有一个容易被忽略的业务点:案件不能由录入人自己直接结案,而是录入人提交结案申请,由合伙人角色审核确认后才能变更为已结案状态。所以案件表里除了 case_status,还需要结案申请人、结案审核人、结案审核时间这几个字段。这种“提交-审核”的双人控制模式虽然增加了代码量,但更贴近律所真实管理流程——毕竟结案意味着收费节点的确认,不能一个人说了算。
后端通过状态机模式实现这些流转时,不要写成一堆 if else 嵌套。这里推荐把状态转移定义成枚举加 Map 的结构,每个状态节点定义允许的下一状态列表和对应操作的服务方法。前端操作按钮请求接口时,后端先检查当前状态是否允许该转移,不允许直接返回带业务码的异常信息。
4.3 客户与案件的双向检索与画像
客户管理页面除了基础增删改查,里面的客户列表用了“左侧树 + 右侧列表”的常见布局。左侧树按照客户的等级或标签分组展示,右侧列表展示客户详情。因为一个律所客户规模通常不夸张,这种做法展示直观,实现也不复杂,后端接口只要支持按等级过滤即可。
客户检索模块做得比较细,支持按姓名、手机号、身份证号模糊搜索。实现上有一个细节:手机号和身份证号这类敏感字段在数据库不能明文存储。该系统的处理方式是 AES 加密存储、模糊检索时先加密再精确匹配。但对于毕设项目的演示效果,你可以保留一个不加密的接口用于展示“模糊搜索”功能,加密逻辑可以在论文中作为安全扩展点描述,答辩时说清楚两种方案的权衡即可。
案件列表页的筛选条件包括:案件状态、案件类型、主办律师、时间段、标的金额范围。实现思路是后端封装一个 CaseQueryDTO 对象接收全部筛选条件,Mapper 层用 MyBatis Plus 的 LambdaQueryWrapper 动态拼接查询条件,而不是手动拼 SQL 字符串。
// 案件条件查询核心代码示意 public PageResult<CaseVO> pageQuery(CaseQueryDTO dto) { LambdaQueryWrapper<TCase> wrapper = Wrappers.lambdaQuery(); // 状态筛选 if (dto.getCaseStatus() != null) { wrapper.eq(TCase::getCaseStatus, dto.getCaseStatus()); } // 主办律师筛选 if (dto.getLeadLawyerId() != null) { wrapper.eq(TCase::getLeadLawyerId, dto.getLeadLawyerId()); } // 案号或标题模糊搜索 if (StringUtils.hasText(dto.getKeyword())) { wrapper.and(w -> w.like(TCase::getCaseNo, dto.getKeyword()) .or().like(TCase::getCaseTitle, dto.getKeyword())); } // 日期范围筛选 if (dto.getStartDate() != null && dto.getEndDate() != null) { wrapper.between(TCase::getFilingDate, dto.getStartDate(), dto.getEndDate()); } // 状态排序、时间倒序 wrapper.orderByDesc(TCase::getCreateTime); return pageResult(this.page(new Page<>(dto.getPageNum(), dto.getPageSize()), wrapper)); }4.4 日程管理与开庭提醒
律师的日程管理是这个系统里最能体现“懂业务”的模块。日程事件类型分开庭、会见、阅卷、会议、其他五类,每条日程关联案件和律师。后端设计了一个日程时间冲突检测接口,创建日程时检查同一个律师在同一时间段内是否已有日程,防止重复安排。
开庭提醒用到了简单定时任务,SpringBoot 的 @Scheduled 注解即可实现。每天固定时间扫描未来三天的开庭日程,给相关律师发送站内消息。这里注意系统消息表单独建一张,前端通过轮询或 WebSocket 接收未读消息数量。WebSocket 方案在毕设中可以作为加分项,但用轮询方案更务实,每 30 秒拉取一次未读消息接口,实现简单且效果差别不大。
4.5 文书管理模块的实现思路
文书管理呈现的是完整的增删改查加预览下载闭环。文书分为协议类、诉讼类、非诉类三种,保存到服务器本地磁盘或 MinIO 对象存储,数据库记录文件名、存储路径、上传人、关联案件。列表页支持按文书类型和关键字过滤,同时提供“套打”功能导出标准格式文书文档。
这块对毕设的重要性在于:文件上传下载是高频面试题和答辩问点。实现时注意三件事。第一,上传接口要限制文件类型和大小,防止恶意上传,配置文件后缀白名单和单文件大小上限。第二,下载接口用流式输出,设置 Content-Disposition 响应头控制浏览器下载文件名。第三,上传的文件名重新生成 UUID 或时间戳加随机数保存,而不是直接用原始文件名,避免中文乱码和文件覆盖问题。
4.6 统计报表的可视化呈现
统计模块面向管理员和合伙人,展示收案趋势、案件类型分布、律师绩效排行、收费统计。前端用 ECharts 绘制柱状图和饼图,后端聚合查询返回统计 VO。这一步真正有技术含量的地方在于聚合查询怎么写,而不是图表怎么画。
以“律师绩效排行”为例,你需要联查案件表里处理完结案件数和收费金额数,按主办律师分组统计,再关联用户表取姓名。MyBatis Plus 的 QueryWrapper 应对这种统计有点吃力,更推荐直接在 Mapper XML 里写原汁原味的 SQL。
SELECT u.real_name AS lawyerName, COUNT(tc.id) AS caseCount, COALESCE(SUM(tc.case_amount), 0) AS totalAmount FROM t_case tc LEFT JOIN t_user u ON tc.lead_lawyer_id = u.id WHERE tc.case_status IN (3, 4) AND tc.law_firm_id = #{lawFirmId} GROUP BY tc.lead_lawyer_id, u.real_name ORDER BY totalAmount DESC这段 SQL 看似简单,但有两个注意点。第一,案件状态要过滤到已结案和已归档,否则把办理中的案件也算进去,绩效数据就是错的。第二,LEFT JOIN 用用户表而不是 INNER JOIN,防止出现案件主办律师被删除后统计记录消失的问题。这类细节做到位了,答辩时才有底气。
5. 接口设计与文档规范
5.1 RESTful API 设计约定
整套系统的接口路径设计遵循 RESTful 风格,资源用名词复数形式,操作通过 HTTP 方法区分。比如案件相关接口就是 GET /api/case/page 列表、POST /api/case 新增、PUT /api/case 修改、DELETE /api/case/{id} 删除。这里没有生搬硬套 REST 规范到极致,比如列表查询用 POST 而不是 GET,原因是查询条件多且复杂,用 POST 传递 JSON 体更清爽,也避免了超长 URL 的问题。
接口返回值统一封装是必选项。Controller 层不准直接返回裸数据,而是统一返回 Result 对象。Result 的字段结构固定为 code、message、data,成功 code 为 200,业务失败 code 为 500,未登录 code 为 401,无权限 code 为 403。前端 axios 响应拦截器统一处理这些 code,401 跳登录页、403 弹无权限提示、500 显示后端返回的 message。
分页查询接口返回值同样有规范,不能只把 list 丢给前端。标准做法是返回 PageResult 对象,内含 total、pages、current、size、records 五个字段。前端表格组件接收时直接赋值,分页插件对接无脑方便。
5.2 接口文档的编写方式与调试技巧
这年头接口文档如果你还是用 Word 写,已经说不过去了。传统 word 文档维护成本极高,后端一改接口路径你就要手动同步文档,漏一处前端骂你三天。这这个项目用的方案是 Knife4j(Swagger 的增强版),后端通过注解自动生成在线接口文档,访问 doc.html 就能看到所有接口的定义、参数示例、返回示例,还能直接在线调试。
Knife4j 注解使用上要养成习惯,每个 Controller 类标注 @Api(tags = "案件管理"),每个接口方法标注 @ApiOperation("分页查询案件列表"),参数对象里的每个字段标注 @ApiModelProperty("案件状态")。这些注解看起来啰嗦,但生成的接口文档对前端联调、对答辩展示、对你自己后期接口维护,都是实打实的提效工具。
接口文档的通用约定里要写明统一认证方式、统一返回结构说明、分页参数约定。这样即便你后期换人维护,或者答辩演示时被人追问,都能给出快速、清晰的接口全貌。不要轻视接口文档,它直接反映你作为开发者是否具备协作意识和交付思维。
6. 环境配置与项目运行全流程
6.1 开发环境与工具版本说明
工欲善其事,必先明版本。这套项目涉及的开发工具版本,先对齐才能少踩坑。
环境依赖版本清单
| 工具 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | 推荐 JDK 8,兼容性最好 |
| Maven | 3.6.x | 依赖管理必备 |
| MySQL | 5.7 或 8.0 | 8.0 需要配置时区参数 |
| Redis | 5.x 及以上 | 用于验证码、token 黑名单 |
| Node.js | 14.x LTS | 前端环境基础 |
| npm/yarn | npm 6+ 或 yarn 1.x | 包管理工具 |
| Vue CLI | 4.x 或 5.x | 创建维护前端工程 |
这里有个前后端版本兼容的经典坑。后端 SpringBoot 2.4+ 把跨域配置的一些底层行为改了,如果你配的spring.web.cors.allowed-origins写法不对,前端请求就会出现“Response to preflight request doesn't pass access control check”错误。解决办法是单独定义 CorsConfig 类实现 WebMvcConfigurer,addCorsMappings 里设置 allowedOriginPatterns("*") 并 allowCredentials(true),这个 pattern 写法在 SpringBoot 2.4 之后是标配。
6.2 数据库初始化与配置文件修改
数据库这一关过不了,后面跑项目全是白搭。第一步,用 Navicat 或命令行创建数据库,字符集选 utf8mb4,排序规则选 utf8mb4_general_ci。这一步用 utf8mb4 而不是 utf8,原因很简单——客户端名字里那些生僻字、特殊符号,utf8 三字节编码存不了。第二步,导入项目里的 init.sql 脚本。脚本执行完检查 t_user 表是否已存在 admin 用户。
第三步,修改后端的 application.yml。重点改四项:数据源 URL 里的 IP 和数据库名、MySQL 账号密码、Redis 地址、以及上传文件的本地存储路径。项目启动前先确认 Redis 已经启动,否则验证码接口会直接报错。
调整 application.yml 时,注意 MySQL 5.7 和 8.0 的驱动差异。MySQL 8.0 使用 com.mysql.cj.jdbc.Driver,URL 里必须带上 serverTimezone=Asia/Shanghai,否则会有时区报错。MySQL 5.7 可以不加时区参数,但如果你是在云服务器上部署,时区差异会导致日期查询出问题,统一加上不是坏事。
6.3 前端启动与跨域联调
前端启动看似简单,npm install、npm run serve 两步走,但前端环境配置有几个地方要提前确认。
第一,node_modules 安装如果报错,优先检查 Node 版本。Vue 2 项目用 Node 14 最稳,Node 16 以上经常出现 node-sass 编译报错,解决办法是卸载 node-sass,改用 sass(dart-sass),同时 package.json 里把编译配置改掉。第二,前端项目的 vue.config.js 里配置了开发服务器代理,把 /api 路径的请求转发到 http://localhost:8080,这样前端开发时请求地址写 /api/xxx 即可,既解决了跨域问题,也为将来部署到 Nginx 做反向代理预留了路径规范。
启动顺序上,先起后端再起前端,保证后端端口 8080 正常响应后,前端 8081 端口访问页面,登录测试。如果登录时验证码图片加载不出来,去查后端控制台报什么错,Redis 未连接是最常见原因。
7. 部署上线与常见问题排查
7.1 前后端分离项目的部署方式
毕设演示往往就是本地起项目,但如果你要部署到云服务器,或者演示环境不能占用本地端口,最好了解标准部署流程。后端打成 JAR 包,通过 java -jar 命令启动;前端 npm run build 生成 dist 静态目录,用 Nginx 托管。
比较省心的方案是:一台服务器上同时装 Nginx 和 Java 环境,Nginx 根目录指向前端 dist 目录,遇到 /api 开头的请求,通过 Nginx location 规则反向代理到后端 8080 端口。这台服务器上你只需要执行两条命令:npm run build 把前端产物传到服务器,mvn package 打包后端 jar 并启动。这套部署方式是小团队快速交付的常见架构,也是你面试时可以拿出来聊的实战经验。
7.2 典型故障排查清单
前后端联调过程中永远不缺问题。整理一份高频故障排查清单,避免心态崩了还不知道从哪里下手。
| 故障现象 | 排查步骤 | 根本原因 |
|---|---|---|
| 登录报“验证码已失效” | 检查 Redis 连接、验证码 key 的过期时间 | 验证码过期时间太短或 Redis 未启动 |
| 前端请求报 401 | 用 Postman 测试接口,检查 token 是否携带在 header | JWT 过期退出或拦截器放行路径配置错误 |
| 登录成功但菜单空白 | F12 查看 network 请求,确认菜单接口返回数据 | 角色权限数据未初始化或前端动态路由格式不对 |
| 上传文件一直失败 | 检查上传目录是否存在、是否有写权限 | 文件存储路径不存在或 Tomcat 临时目录权限不足 |
| 跨域报错 | 检查后端 CorsConfig 配置和后端启动端口 | 配置没重启或 allowedOriginPatterns 写法不对 |
| Table 显示日期差一天 | 检查 MySQL 连接 URL 是否有时区参数 | 数据库时区与服务器时区不一致 |
7.3 毕设答辩时的技术准备
拿到这套项目的源码只跑通还远远不够,答辩时你最容易被问到的就是“项目有什么亮点”和“你在项目里具体做了什么”。建议从以下四个方向准备深度回答。
亮点一,RBAC 权限模型加 JWT 认证,再加上 Redis 黑名单机制解决 JWT 无法主动失效的问题,解释清楚这三者的配合逻辑。亮点二,案件状态机的设计思路,为什么选状态驱动而不是简单字段判断,以及如何保证状态流转既灵活又安全。亮点三,使用数据库逻辑删除替换物理删除,保全业务数据的可追溯性,这个观点在真实企业开发中极具说服力。亮点四,分页条件查询用 MyBatis Plus LambdaQueryWrapper 动态拼接,避免 SQL 注入风险,回答时把 SQL 注入的原理说清楚。
另外,你自己要能看清楚源码里每个核心类的作用,不要一问三不知。最忌讳的状态是:项目跑起来很流畅,但你连入口 Controller 在哪都说不出来。建议答辩前抽半天时间,跟着登录请求链路把 controller、service、mapper、redis、jwt 各环节过一遍。
8. 从毕设到真实项目的进阶建议
拿到一套完整源码,远不是终点。如果你时间充裕,我建议在这套系统上继续做一些低成本高价值的功能扩展,既能练手,也能让答辩更有内容。
加 Redis 缓存是第一个该做的方向。现在案件列表页每次刷新都打数据库,压力不大性能也不差,但你可以把热点案件的详情加缓存,设置合理的缓存过期时间并处理缓存穿透问题。解释清楚 Redis 缓存雪崩、穿透、击穿三个概念,比多做三个 crud 页面有用得多。
用 EasyExcel 实现案件数据的导入导出也是很好的进阶点。律所行政每月都要做案件台账,Excel 导入导出是真实需求场景。EasyExcel 的 API 不算复杂,导入模板校验、导出批量下载、进度条反馈这三个功能做出来,项目立刻有了企业级应用的质感。
把文件存储从本地磁盘迁移到 MinIO,也是值得动手的一步。MinIO 是开源的对象存储服务,能在本地搭建一个简易的私有云存储,用 Java SDK 做文件上传、下载、删除、访问签名。你还能顺便把直传授权机制讲清楚:前端先向后端申请上传凭证,再直接传文件到 MinIO,后端不经过文件字节流,减少内存压力。
再进一步说,如果你已经被问到了 RabbitMQ 或者 MQTT 这类消息队列,可以在日程提醒这个业务上接入 RabbitMQ 做异步通知推送。开庭提醒创建后发布一条消息,消费者去发短信或站内信。这个扩展会让你在技术深度上明显区别于同组其他做增删改查的同学。
我的体会是,毕业设计考察的核心永远不是代码写得多花哨,而是你对一个完整业务闭环的理解程度。这套系统从表结构设计到权限控制,从状态流转到数据统计,已经把一个小型管理系统的核心要素都覆盖了一遍。在此基础上做增量创新,你的收获会远远超出“拿到一个项目跑起来”的层面。如果你还在纠结课题怎么选、系统怎么画、论文怎么写,先把这套源码吃透,再带着自己的扩展思路去开工,整个过程都会顺很多。