大学四年转眼就到毕设季,Java Web方向的同学十有八九都在纠结同一个问题:做个什么系统既能让答辩老师觉得有分量,又不至于把自己折腾到脱发?如果你恰好对中医药方向感兴趣,或者只是想找一个业务逻辑清晰、技术和业务能完美结合的题目,那这套SpringBoot+Vue的中药实验管理系统,绝对值得你好好研究。我前阵子刚好陪跑完一个学弟的整个毕设过程,对这里面的技术选型、数据库设计、接口规划以及那些教材里永远不会写清楚的坑,可以说是门儿清。这篇文章我就把整个项目从头到尾拆开,从核心需求一路讲到代码实现和避坑实操,保证你看完能对如何复现、如何扩展、如何在答辩时讲出深度,有一个完整清晰的思路。
1. 项目整体设计与技术选型思路
1.1 为什么偏偏是中药实验管理?
先说说选题这件事。很多同学选毕设题目,要么跟风做个商城系统,要么做个毫无业务逻辑的CRUD管理后台,答辩时老师一问“你这个系统的难点和亮点在哪里”,直接就接不上话。中药实验管理系统不一样,它天然具备一个真实业务场景的所有要素:有人员(学生、教师、实验员)、有流程(申请、审批、实验、记录)、有数据管理(药材信息、实验项目、结果数据),还带着一点行业特色(中药炮制、有效成分提取这些)。
更重要的是,中药实验管理系统的业务逻辑是“有层次”的。它不是一个单纯的增删改查,而是包含了状态流转:学生提交实验申请,教师审批,审批通过后安排实验,实验完成后登记结果,结果需要复核。这个流程串起来之后,你的系统就有了“生命周期”的概念,这在答辩时是一个非常容易讲清楚的业务亮点。
我记得学弟当时开题的时候,导师问了一句:“你为什么要做中药的,不做通用的实验管理系统?”这个问题其实很关键。通用的实验管理系统市面上已经有很多了,你做一个“通用版”很难说清楚自己系统的差异化;但把范围缩小到中药实验,系统就可以具备专属功能:比如药材的产地、炮制方法、性味归经这种中药特有信息的管理,比如按方剂维度去关联实验,这些字段和逻辑是“通用实验管理系统”没法覆盖的个性化价值。
1.2 前后端分离架构:为什么铁定是SpringBoot + Vue
近几年Java Web方向的毕设,SpringBoot + Vue这对组合几乎成了事实标准。从毕设的角度去看,这个选型有它的必然性。
后端用SpringBoot,核心价值在于“开箱即用”。不用像老SSH那样配置一堆XML,内嵌Tomcat容器,一个jar包就能跑起来,开发效率确实高。而且SpringBoot生态太成熟了,Spring Security做权限、MyBatis-Plus做数据访问、Redis做缓存,几乎每一个你在毕设中需要的组件都有现成的starter可以引入。
前端用Vue,核心价值在于“渐进式”。Vue的模板语法上手门槛低,对大部分Java方向的学生来说,后端是主战场,前端能做到“够用且好看”就行。Vue + Element UI这套组合,基本上半天就能搭出一个像模像样的管理后台界面,表格、表单、弹窗、分页这些元素全是现成的组件。
另外前后端分离这种架构本身就是答辩的一个考虑。你把Vue项目打包成静态文件丢到Nginx里,SpringBoot以纯API形式对外提供服务,这个部署架构本身就比“前后端不分离的JSP项目”在技术上看起来要现代化很多。答辩老师如果问“为什么选择前后端分离”,你可以很自然地引出接口文档、联调流程、独立部署这些概念,这就是加分项。
1.3 系统角色与权限边界的设计
一个管理系统的核心,首先是用户角色划分。中药实验管理系统我建议按三类角色来设计:管理员、教师、学生。当然也可以再细分出一个“实验员”角色,但作为毕设来说,三类角色足够把权限控制的逻辑讲清楚,再多就容易把自己绕晕。
三类角色的权限边界大概是这样的:
- 管理员:负责系统基础数据维护,用户管理、角色分配、药材信息录入、实验室信息管理,相当于整个系统的“超级管家”。
- 教师:负责实验项目管理,可以创建实验任务、审批学生的实验申请、查看实验结果并对实验结果进行评分或审核。
- 学生:浏览已发布的实验项目,提交实验申请,查看审批结果,实验完成后填写实验报告或登记实验数据。
这个权限模型设计好之后,后端就可以通过Spring Security + JWT去实现接口级别的权限控制。比如学生角色的Token只能访问/api/experiment/apply这类接口,而教师角色才能访问/api/experiment/approve。前端配合Vue Router的路由守卫,根据角色动态生成菜单和可访问的路由表,做出来就是一套“能看明白”的RBAC权限方案。
1.4 技术架构全景图
在动手写代码之前,建议先在文档里把整个技术架构图梳理出来。这个项目涉及的技术组件不算多,但每一层都需要你清楚“这一层是干什么的、为什么这么选”:
- 前端层:Vue 2 + Element UI + Axios + Vue Router + Vuex。Vue 2在毕设场景下依然稳,生态稳定、资料多,遇到问题百度一下基本都有答案。
- 后端层:SpringBoot 2.x + MyBatis-Plus + Spring Security + JWT。MyBatis-Plus的代码生成器可以省掉大量单表CRUD的代码时间,把精力留给核心业务逻辑。
- 数据层:MySQL 8.0,存业务数据;Redis可选,建议加上用于存储JWT的黑名单或验证码,作为加分项。
- 部署层:前端打包后放Nginx,后端打包成jar用systemd托管,MySQL单独部署。
需要特别说明的是,如果你是自己从零开始做,不要被这么多组件吓到。大部分组件的集成都是“复制配置、理解原理”的过程。真正需要你花心思设计的,是数据库的表结构和核心业务流程的状态机。
2. 数据库设计与核心表结构拆解
2.1 从业务反推数据表
很多同学拿到一个项目题目,第一反应就是“我该建哪些表?”然后照着网上找的类似项目一顿抄,结果抄来的表结构要么字段对不上需求,要么逻辑混乱。
正确的思路应该是“从业务流程反推数据表”。我们先把中药实验管理系统里的核心业务场景列出来:
场景一:管理员维护基础数据。包括用户信息、角色信息、药材信息、实验室信息。这些对应的是用户表、角色表、药材表、实验室表。
场景二:教师发布实验项目。一个实验项目会包含实验名称、实验类型(炮制/提取/成分分析等)、使用的药材、实验步骤说明、注意事项。这就需要实验项目表,同时因为一个实验可能用到多种药材,实验和药材之间是多对多关系,需要一张关联表。
场景三:学生申请实验。申请这个动作要记录谁申请的、申请哪个实验、申请时间、申请状态(待审批/通过/驳回)。这里就是实验申请表,或者叫实验预约表。
场景四:实验完成后登记结果。学生需要填写实验数据,比如药材有效成分含量、实验现象描述、实验结果附件等。这对应实验结果表,同时因为实验结果依附于一次具体的实验申请,所以需要和实验申请表关联。
场景五:教师审核实验结果。审核动作本身需要一个状态字段来标记,不需要单独建表。
这么梳理一遍之后,核心数据表就自然地浮现了:
| 表名 | 功能说明 | 核心字段(举例) |
|---|---|---|
sys_user | 用户表 | id, username, password, real_name, role_id |
sys_role | 角色表 | id, role_name, role_code |
herb_info | 中药药材信息表 | id, herb_name, alias, origin, nature_flavor, processing_method |
lab_info | 实验室信息表 | id, lab_name, location, capacity, equipment_list |
experiment_project | 实验项目表 | id, project_name, project_type, content, status, create_by |
experiment_project_herb | 实验项目药材关联表 | id, project_id, herb_id, dosage, usage_method |
experiment_apply | 实验申请表 | id, project_id, student_id, apply_time, status, audit_comment |
experiment_result | 实验结果表 | id, apply_id, result_data, conclusion, report_file, audit_status |
这8张表是整个系统的基础骨架。有了这个骨架之后,后续所有的接口设计都是在围绕这几张表做文章。
2.2 SQL脚本设计与字段命名规范
SQL脚本是整个毕设项目的基础,基础打不好后面全是坑。写脚本的时候有几个实操层面的建议:
关于命名:表名和字段名一律小写,多个单词用下划线分隔。不要用驼峰命名去建表,虽然Java实体类用驼峰没问题,但数据库层面统一用下划线风格,写SQL的时候更清晰,也方便MyBatis-Plus做驼峰映射。
关于类型选择:主键用BIGINT自增,这个没什么好纠结的。日期字段统一用datetime。文本描述类的字段用VARCHAR,但像实验步骤说明、实验结果数据这种可能很长的建议直接TEXT,避免后期测试时写入长文本报错。
关于状态字段:所有涉及状态流转的字段建议统一命名status,用数字表示状态,比如实验申请表的status:0表示待审批,1表示已通过,2表示已驳回,3表示已完成。数字状态比字符串好在哪里?一是存储空间小,二是逻辑判断简单,三是在代码里可以用常量去定义可读性也不差。
关于逻辑删除:建议给每张表都加上deleted字段(0未删除,1已删除)。这个习惯在毕设阶段可能觉得多余,但实际开发中所有公司都是这么干的。MyBatis-Plus对逻辑删除有内置支持,配置一个全局逻辑删除字段名就行了。
2.3 给SQL脚本加上勇敢的注释与初始数据
SQL脚本的另一个功能是别人拿到你的项目能直接跑起来。我见过不少同学交的毕业设计SQL脚本,库表建完之后连一条初始化数据都没有,代码里哪怕是一个下拉框需要的枚举值都要自己现录。这种项目交给老师演示的时候,老师第一步要做的就是“登录系统看看”,如果没有初始管理员账号,演示直接就卡住了。
所以在SQL脚本里一定要包含:
- 初始化管理员账号:admin / 123456(密码在SQL里存BCrypt加密后的密文)
- 初始化角色数据:管理员、教师、学生三个角色各一条
- 初始化基础药材数据:至少录入20条常见中药药材信息,比如甘草、黄芪、当归、金银花等,字段尽量填充完整,这样前端页面展示的时候不至于全是空空如也
- 初始化教师和学生测试账号:方便多角色联调测试
- 初始化几个完整的实验项目数据:包含关联药材的关系数据
初始化数据不要嫌麻烦,这不仅是给评审老师看的,也是你自己联调接口的刚需。写SQL的时候注意编码问题,文件开头统一加SET NAMES utf8mb4;,防止中文乱码。
2.4 表关系设计中的关键决策
在表关系设计上,有几个如果选错后面非常难受的地方:
第一个是实验项目和药材的多对多关系。不要图省事直接在experiment_project表里加一个herb_ids字段存逗号分隔的字符串,虽然查询的时候用FIND_IN_SET也能查出来,但后续如果你想做“按药材反查实验项目”这种功能,就会非常痛苦。老老实实建一张关联表,这是标准做法,也是数据库规范化知识的实际运用。
第二个是实验结果和实验申请的对应关系。我见过有些设计里,实验结果和实验申请各自独立,再通过中间表关联,这纯属给自己挖坑。一个实验申请最终只会产出一份实验结果,这里直接采用一对一关联,在experiment_result表里用apply_id做主键或者唯一外键即可。
第三个是附件表要不要单独建。如果实验成果需要支持上传报告文件,建议单独建一张file_info表统一管理,记录文件的原始名称、存储路径、大小、上传人、上传时间。不要直接把文件路径字符串存到业务表里,因为一个实验申请可能上传多个文件,而且文件控制和业务解耦之后,后面做文件上传接口的时候思路会清晰很多。
3. 后端核心功能实现与接口设计
3.1 项目初始化的正确姿势
SpringBoot项目的初始化,我不建议直接去Spring Initializr网站手选依赖,更不建议从网上找那种两年前的同类项目代码硬套。正确的做法是用 IDEA自带的 Spring Initializr 正常创建,Java版本8或者11都可以,Group和Artifact自己起个顺眼的名字,依赖先只选Web、MySQL Driver、MyBatis-Plus框架后加也行。
创建完项目之后,第一件事就是配置application.yml。这里推荐直接把一套完整的配置在这个阶段写到位:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/herb_lab?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: your-secret-key expire: 604800这里多花十分钟把配置写规范,后面能省下大量排查“为什么乱码”“为什么日期格式不对”的时间。特别注意serverTimezone=GMT%2B8,MySQL 8.0 的时区问题是最常见的启动报错点。
3.2 统一响应结构:别让每个接口各写各的
不少同学写接口的时候,每个接口返回格式都不一样,有的直接返回实体类,有的返回Map,有的返回String提示。这样前端要适配各种不同的结构,联调的时候极其痛苦,而且答辩演示的时候如果某个功能报错,页面直接就是一片技术堆栈,观感很差。
强烈建议一开始就定义一个全局统一响应类Result<T>:
@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }配合一个全局异常处理器,用@RestControllerAdvice把业务异常、参数校验异常、未知异常统一拦截起来,统一转成Result结构返回。这样一来,前端的Axios拦截器只需要写一份响应处理逻辑就够了。
这个统一结构在答辩时完全可以直接讲成“提高了前后端联调效率,规范了接口标准”,属于基本功里面的加分项。
3.3 登录认证与权限控制实战
登录认证是每个管理系统的门面,也是面试官和答辩老师必问的地方。这个项目采用JWT方案,流程是:
用户提交用户名密码,后端校验通过后生成Token,Token里携带用户ID和角色编码,前端将Token存储在localStorage,每次请求在请求头加Authorization: Bearer <token>,后端通过拦截器解析Token并检查接口权限。
核心拦截器逻辑大致如下:
public class JwtAuthenticationInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { token = token.substring(7); try { Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody(); request.setAttribute("userId", claims.get("userId")); request.setAttribute("roleCode", claims.get("roleCode")); return true; } catch (Exception e) { response.setStatus(401); response.getWriter().write("{\"code\":401,\"message\":\"token无效或已过期\"}"); return false; } } response.setStatus(401); response.getWriter().write("{\"code\":401,\"message\":\"未登录\"}"); return false; } }这里有两个实战细节要提醒。第一个是Token过期时间,我建议设置为7天,也就是604800秒。设置太短会导致开发调试时频繁要重新登录,体验很受影响;设置太长又显得不够严谨,7天是个合理的平衡。第二个是Spring Security和JWT拦截器的关系。作为毕设项目,其实不必把Spring Security的完整过滤器链搞清楚,直接用拦截器实现Token校验也是完全可行的。不要为了“技术看起来高级”就硬上Spring Security的复杂配置,不熟悉的情况下配置错了排查起来非常痛苦,很容易把自己卡死。
3.4 实验审批状态流转的实现
实验申请审批是整个系统最有业务深度的模块。状态怎么流转,每一步操作如何校验,这地方代码写得好不好,直接反映你对业务的理解程度。
设计状态机如下:
- 学生提交实验申请 -> 状态为0(待审批)
- 教师审批通过 -> 状态为1(已通过)
- 教师审批驳回 -> 状态为2(已驳回)
- 实验完成并提交实验结果 -> 状态为3(已完成)
在Service层实现这个方法的时候,要有状态校验逻辑:
@Transactional public Result<Void> approve(Long applyId, Long teacherId, Boolean approved, String comment) { ExperimentApply apply = applyMapper.selectById(applyId); if (apply == null) { throw new BizException("实验申请不存在"); } // 核心校验:只有待审批状态才能审批 if (apply.getStatus() != 0) { throw new BizException("当前状态不允许审批操作"); } // 核心校验:该实验项目的创建人才能审批(权限控制到数据级别) ExperimentProject project = projectMapper.selectById(apply.getProjectId()); if (!project.getCreateBy().equals(teacherId)) { throw new BizException("只有该实验项目的负责教师才能审批"); } apply.setStatus(approved ? 1 : 2); apply.setAuditComment(comment); apply.setAuditTime(new Date()); applyMapper.updateById(apply); return Result.success(); }这个方法虽然不长,但包含了两个重要的业务校验:状态校验和归属校验。状态校验保证流程顺序,归属校验保证数据安全。这两个校验讲给答辩老师听,比单纯说“我能增删改查”有说服力得多。
3.5 文件上传与静态资源映射
实验结果可能需要上传实验报告或数据附件。SpringBoot处理文件上传很简单,关键点是上传后的文件存到哪里,以及如何让前端能访问到这些文件。
我的建议是:项目根目录下建一个upload文件夹,文件上传时用UUID重命名,防止文件名冲突,然后将文件路径记录到file_info表。上传成功之后接口返回文件ID和访问URL。
因为前后端分离,前端要能访问到这些静态文件,需要配置一下静态资源映射:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocator("file:" + System.getProperty("user.dir") + "/upload/"); } }这样前端就可以直接拿/upload/xxx.jpg去访问上传的图片或附件了。注意Windows和Linux部署路径拼接有区别,代码里写路径拼接的时候一定不要写死绝对路径,用System.getProperty("user.dir")或者用配置文件去指定路径。
3.6 接口文档的价值与生成方式
接口文档在这个项目里不是可选项,而是必须的交付物。为什么?第一,你自己开发到后期,前后端联调时接口数量多了,没有文档根本记不住参数;第二,毕设答辩时文档直接展示给评审老师看,这是“规范开发流程”的直接证据;第三,面试的时候如果你的项目经历里有“编写接口文档”这条,至少说明你不是单打独斗的野路子。
接口文档的生成方式,我建议直接集成knife4j(对Swagger的增强UI)。引入依赖之后,在配置类上加上@EnableSwagger2注解,然后在每个Controller的方法上写上@ApiOperation注解说明接口用途,用@ApiImplicitParam标注参数含义。
启动项目后直接访问/doc.html,就能看到所有接口的分类列表、请求参数、返回示例,甚至可以直接在页面里调试接口。在答辩演示环节,先展示这个在线接口文档,再切换到前端页面演示功能,整体观感会非常专业。
4. 前端Vue实现要点与联调实操
4.1 前端项目结构和路由设计
新建Vue项目建议直接用Vue CLI,选择Vue 2的版本。项目目录结构建议这样组织:
src/ ├── api/ // 接口请求封装,按模块拆分 │ ├── user.js │ ├── project.js │ └── apply.js ├── assets/ // 静态资源 ├── components/ // 公共组件 ├── router/ // 路由配置 ├── store/ // Vuex状态管理 ├── views/ // 页面组件 │ ├── login/ │ ├── dashboard/ │ ├── user/ │ ├── herb/ │ ├── project/ │ └── apply/ ├── utils/ // 工具函数 │ ├── request.js // Axios封装 │ └── auth.js // Token管理 └── App.vue路由设计要配合角色权限。在router/index.js中定义所有路由,并给需要权限的页面路由加上meta: { roles: ['admin', 'teacher', 'student'] }这样的角色标记。然后在路由全局前置守卫里判断:
router.beforeEach((to, from, next) => { const token = getToken(); if (!token && to.path !== '/login') { next('/login'); return; } if (token && to.path === '/login') { next('/'); return; } const roleCode = getRole(); if (to.meta.roles && !to.meta.roles.includes(roleCode)) { next('/401'); // 无权限提示页 return; } next(); });这个路由守卫的逻辑不复杂,但演示效果很好:不同角色登录后能看到的菜单不同,直接访问无权限的地址会跳转到401页面,说明权限控制是有效的。
4.2 Axios请求封装:一套代码管所有请求
前端请求一定要封装,千万不要在每个组件里直接写this.$http.get(...)。封装的好处是:统一处理Token附加、统一拦截错误响应、统一处理会话过期跳转。
// utils/request.js import axios from 'axios'; const service = axios.create({ baseURL: '/api', timeout: 10000 }); // 请求拦截器:附加token service.interceptors.request.use(config => { const token = getToken(); if (token) { config.headers['Authorization'] = 'Bearer ' + token; } return config; }); // 响应拦截器:统一处理错误 service.interceptors.response.use( response => { const res = response.data; if (res.code !== 200) { Message.error(res.message); return Promise.reject(new Error(res.message)); } return res; }, error => { if (error.response && error.response.status === 401) { removeToken(); router.push('/login'); Message.error('登录已过期,请重新登录'); } else { Message.error('网络异常,请稍后重试'); } return Promise.reject(error); } ); export default service;注意这里baseURL: '/api'配合前后端联调时的代理配置。在vue.config.js里配置一个devServer代理,把/api开头的请求转发到后端的8080端口,这样本地开发时前端不管后端端口是什么,前端代码里一律写相对路径,以后上线了只要改代理配置,不用改任何业务代码。
4.3 实验申请页面的交互实现
实验申请页面是前端最核心的业务页面。整体交互流程设计为:
页面顶部展示当前用户可申请的实验项目列表,点击“申请实验”按钮打开一个表单弹窗,需要选择实验室、填写计划实验时间、备注信息。提交之后,列表刷新,申请记录出现在“我的申请”标签页中,状态为待审批。
这里有一个前端交互细节值得注意:实验项目的详情可能需要展示该项目用到的药材列表。这个列表在后端是通过连表查询返回的,前端在展示项目列表时,如果直接把药材同时查出来,接口数据量会比较大;如果点击详情时才查,又会有一定的加载延迟。你可以在后端做一个小优化:项目列表接口返回的ProjectVO里包含一个herbNames字符串字段,直接拼上所有药材名称用逗号分隔,列表页展示时按字符串直接展示;详情页再调用专门的接口获取完整药材信息和实验步骤。既兼顾了列表的简洁,又能满足详情页的完整性。
4.4 前端权限控制与菜单动态渲染
菜单权限的实现方式,最简单的是用Vuex存一份当前用户的角色编码,然后在侧边栏菜单组件中写v-if判断。这种方式实现简单,对毕设项目来说完全可以。进阶一点的方案是后端登录时返回菜单列表,前端用Vuex存储后动态生成路由和菜单。
进阶方案虽然看起来高级,但对毕设来说有一点过度设计,而且坑比较多。比如刷新页面后Vuex状态会丢失,需要配合路由守卫重新拉取用户信息,这一套逻辑一旦处理不好,刷新就白屏,演示时尴尬度极高。我建议先用v-if这种稳妥方案,把核心业务跑通,如果时间和精力充裕,再研究动态路由方案作为扩展亮点。
4.5 前后端联调中的经典问题
前后端联调阶段碰到的第一个问题基本就是跨域。开发模式下如果你只是用代理转发,跨域问题其实不存在,Vue CLI的 devServer代理对浏览器来说同源。但如果前端不通过代理直接请求后端地址、或者后端单独部署而前端本地直连,就会触发跨域。
后端处理跨域可以在SpringBoot里配置全局CORS:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns("*")和allowCredentials(true)要配合使用,如果你写allowedOrigins("*")再开允许携带凭证,浏览器会直接拒绝,这是常见的坑。
联调期第二个常见问题就是日期格式不一致。后端返回的LocalDateTime序列化成JSON时如果没配置格式,会变成一串数组或者ISO格式字符串,前端展示很难看。前面application.yml里已经配了spring.jackson.date-format,Java8时间类型还需要额外做一下序列化配置,在application.yml里加入:
spring: jackson: serialization: write-dates-as-timestamps: false或者直接用Jackson2ObjectMapperBuilderCustomizer在配置里统一指定LocalDateTime的格式模式。这个细节你要是没处理,前端日期字段十有八九显示成乱七八糟的格式,印象分直接掉一档。
5. 接口文档、SQL脚本与项目部署的完整落地
5.1 接口文档里应该写什么
在线API文档生成之后,还需要整理一份离线版本放在项目交付的文档里。离线接口文档建议按模块组织:
- 登录认证模块:登录接口、获取用户信息接口、修改密码接口
- 用户管理模块:用户分页查询、新增用户、编辑用户、删除用户、分配角色
- 药材管理模块:药材分页查询、药材新增、药材编辑、药材删除
- 实验项目管理模块:项目分页查询、项目详情、新增项目、修改项目、发布/下架
- 实验申请模块:提交申请、审批申请、我的申请列表、申请详情
- 实验结果模块:提交实验结果、审核实验结果、结果列表
每一个接口需要写清楚:请求URL、请求方式、请求参数(参数名、类型、是否必填、说明)、响应结构示例。这种文档写到什么程度算合格?让一个完全没接触过这个项目的同学看文档就能直接调通接口,就算合格。
5.2 SQL脚本的组织与交付标准
SQL脚本在交付时要有清晰的分类,不要一个文件塞到底。建议这样组织:
/sql ├── 1_schema.sql // 建库建表语句 ├── 2_init_data.sql // 初始化数据 ├── 3_test_data.sql // 测试数据(可选) └── README.md // 说明文件1_schema.sql里包含所有建表语句,每张表建立时添加合理的索引,比如外键字段和查询频繁的字段。2_init_data.sql里放的是必填的基础数据。3_test_data.sql里放的是模拟的业务数据,方便老师直接看到系统里已经有内容可以操作演示。
SQL脚本还有一个细节:要给脚本开头加数据库创建语句CREATE DATABASE IF NOT EXISTS herb_lab DEFAULT CHARACTER SET utf8mb4;和USE herb_lab;,保证执行的时候不会因为没选库而报错。
5.3 本地部署与服务器部署
本地部署是最低要求,必须保证在任意一台装了JDK8+和MySQL的电脑上能够按文档步骤跑起来。步骤整理成标准流程:
- 创建数据库并导入SQL脚本
- 修改
application.yml中的数据库用户名密码 - 启动后端,确认8080端口正常访问接口文档
- 前端项目执行
npm install安装依赖 - 执行
npm run dev启动开发模式,浏览器访问前端页面
如果你还想给项目加一点“可部署性”的加分项,可以写一个服务器部署的文档,参考流程是:后端使用mvn package打成jar包,上传到服务器后用nohup java -jar命令运行;前端npm run build生成dist目录,上传到Nginx的HTML目录并配置静态代理和API反向代理:
server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意前端打包时如果用了<BASE_URL>的默认值,资源路径可能会引用绝对路径,需要在vue.config.js里设置publicPath: './',否则打包后静态资源加载不出来,这是前端部署中最常遇到的第一个坑。
5.4 演示数据:不要让评审老师看到空荡荡的系统
最后再聊一个特别容易忽视的问题。答辩演示的时候,评审老师会用自己的账号登录系统,如果登录进去之后列表全是空的,按钮点了也没反应,观感会很差。所以务必在SQL脚本里准备好充足的演示数据。
比如药材信息至少要几十条,实验项目要有已发布的、未发布的、进行中的不同状态,实验申请要有待审批的、已通过的、已驳回的,实验结果要有已提交的、已审核的。如果按照我前面说的3_test_data.sql单独维护这些数据,你在写代码阶段就可以随时用这些数据来测试接口和页面展示,一举两得。
6. 常见问题与排查技巧实录
6.1 启动报错:数据库连接失败
这个错误九成是配置问题。MySQL 8.0的驱动是com.mysql.cj.jdbc.Driver,不要用老的com.mysql.jdbc.Driver。检查url里的serverTimezone参数,Linux服务器上还要确认MySQL服务本身已经启动,可以用systemctl status mysqld检查服务状态,用mysql -u root -p测试本地连接。
6.2 接口报403/401
如果你确认Token已经正确传了,还报401,先检查拦截器排除的路径是否正确。登录接口、注册接口、文档接口必须放行,放行配置建议用路径白名单方式:
String[] excludePaths = {"/api/auth/login", "/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs"};另外注意Swagger文档的请求可能也会被拦截器拦下来,一定要把Swagger相关的路径都加到白名单里,否则打开接口文档页面会变得非常别扭。
6.3 文件上传后地址无法访问
这是前后端分离项目里的经典问题。前端拿到返回的/upload/xxx.png路径后直接作为图片地址放到<img src>里面,因为没有经过代理,浏览器当然访问不到。
解决思路有两种:一种是前端拼接完整的后端请求地址,比如http://localhost:8080/upload/xxx.png;另一种是后端在返回文件地址时直接返回完整URL。最推荐的方式是在后端写一个getFileUrl工具方法,根据当前请求的serverName、serverPort动态拼接出完整可访问的URL,这样不管部署在哪台机器上都不需要改前端代码。
6.4 Vue项目打包后页面白屏
打包后白屏问题基本就是两个原因:一个是前面提到的publicPath路径问题,另一个是路由的history模式问题。如果你用的是history模式路由,在Nginx里没配置try_files回退,刷新页面就404。解决方法有二:要么改Nginx配置支持history模式回退,要么把路由改成hash模式。毕设项目我建议直接用hash模式,省心稳定,地址栏多一个#完全无伤大雅。
7. 项目的扩展方向与个人经验总结
这个项目如果只是做完了事,那就太可惜了。从我的经验来看,答辩时老师最喜欢问的一个问题就是:“你觉得你这个系统还有什么可以改进的地方?”这个问题如果答得好,反而比项目本身更能加分。我建议你可以从以下几个方向入手思考扩展。
第一个方向是引入消息通知机制。当前的系统里,学生提交申请之后,教师并不知情,必须主动刷新页面才能看到。如果能加一个站内信功能,或者在教师登录后的首页加一个待办事项提醒,就能把“被动查看”变成“主动推送”。如果再进一步,集成邮件通知或者WebSocket实时提醒,那这个系统的交互体验就又上了一个台阶。
第二个方向是增加数据统计可视化。实验管理系统本身会产生大量数据,比如每个实验项目的申请数量、通过率、实验完成情况,这些数据用ECharts做一个统计面板,多维度展示系统运行情况,无形之中就为系统增加了一个“数据分析”的亮点,锦上添花。
第三个方向是引入定时任务。比如实验申请超过一定时间未审批自动提醒,实验任务截止时间自动变更状态,这些都可以用Spring的@Scheduled注解实现。定时任务需求量不大,但看起来非常“企业级”。
第四个方向是引入消息队列或缓存。比如学生提交实验结果后,需要通知教师审核,这种异步处理场景可以用消息队列去做。不过作为毕设,这个方向只需要在答辩时讲讲“如果数据量大了我可以怎么优化”,不要真的去集成,不然给自己增加的复杂度远大于收益。
我在陪学弟做完整个项目后的最大感受是:毕业设计的核心逻辑不是炫技,而是“用合适的工具完整地解决一个真实场景下的问题”。一套清晰的表结构、一套规范的接口设计、几个合理的业务校验,比堆砌再多的技术组件都更经得起推敲。如果你准备从零开始去复现这个中药实验管理系统,我的建议是先从梳理业务流程开始,把表结构设计好了再动手写代码,后面每走一步都会非常顺畅。这套思路和骨架,你完全可以类比迁移到你自己的题目上去,能少走太多弯路。