我去年在司法信息化方向做过一个完整项目:基于SpringBoot+Vue的狱内罪犯危险性评估系统。当时需求方要的其实很明确——把传统的纸质评估打分流程搬到线上,用一套可量化的指标体系给在押人员的危险等级做评定,同时把历次评估记录、审批留痕、统计分析全都管理起来。技术栈直接锁定了Java+MySQL+MyBatis这套经典组合,前端用Vue。这篇文章我尽量把设计思路、核心代码、坑点都摊开讲,给正打算做同类业务系统、或者毕设选题往这个方向靠的同学一个完整参考。
之所以选这个题来分享,是因为它属于很典型的“业务逻辑+权限控制+数据报表”三座大山型管理项目,难度适中但对工程化能力要求全面。你做完它,SpringBoot配置、MyBatis动态SQL、Vue路由与组件通信、JWT鉴权、ECharts可视化基本上全都摸了一遍。下面直接从需求拆解开始。
1. 系统到底在做什么:需求拆解与技术定位
1.1 所谓“危险性评估”在业务上是个什么流程
先别急着写代码,这类业务系统最怕需求只停留在口号上。我当时跟业务方反复确认之后,把流程抽象成了四个环节。
第一个环节是“信息采集”。系统需要维护在押人员的基础档案,包括基本信息、罪名类型、刑期区间、入所时间、是否有暴力前科、是否有自杀自伤倾向等。这些东西是评估的数据底座,没有档案信息,后面所有评估都是空谈。
第二个环节是“评估打分”。这是整个系统的核心。系统内置了一套评估量表,里面包含若干一级指标,比如暴力倾向、脱逃风险、情绪稳定性、违规违纪情况等。每个一级指标下又拆了若干二级指标,每个二级指标有自己的评分区间和权重。评估人员(一般是管教民警)根据在押人员的实时表现逐项打分,系统自动算出综合分,再映射为低风险、中风险、高风险三个等级。
第三个环节是“审核与干预”。初评结果不能直接生效,需要经过审批流。评估人提交之后,审核人登录系统查看明细、确认分数,可以“通过”或者“退回重评”。退回时要求填写退回理由,这个理由会留痕,后面可以追溯。
第四个环节是“统计与预警”。系统要能按时间、监区、罪名类型等维度统计风险等级分布,还能把历次评估结果拉成趋势曲线。如果某个人连续两次评估分数都超过高风险阈值,系统要在首页给出预警提示。
这四个环节看着不复杂,但落到系统功能上,就牵扯出用户管理、角色权限、评估模板配置、评估流程状态机、报表图表、消息提醒等多个模块。我当时梳理完功能清单,一共拆出了十几个页面和后端接口。这篇文章后面讲的都是基于这个业务模型来的。
1.2 为什么是SpringBoot+Vue+MyBatis这套组合
选型这事其实不用纠结太久,关键看场景匹配度。这种中小企业级管理信息系统,最怕的不是技术不够新,而是团队协作成本高、部署麻烦、文档少。SpringBoot把传统SpringMVC那套繁琐的XML配置全干掉了,内嵌Tomcat,打一个Jar包就能跑,部署成本几乎为零。对于监狱这种内网环境来说,没有外网依赖、一个包跑起来是绝对刚需。
Vue这边也不用多说,前后端分离已经是这类系统的事实标准。Vue的响应式数据绑定让表单交互和级联联动写起来非常顺手,配上Element UI组件库,一两天就能把后台管理的骨架搭出来。而且Vue的路由和状态管理(Vuex或Pinia)让页面权限控制很好做,跟后端的角色权限体系能对上。
MyBatis是另一个很“老派但务实”的选择。它相比JPA/Hibernate更透明,SQL自己写,出问题好排查,特别适合业务表多、查询条件多变的管理系统。我们这个系统里光评估记录查询就有按时间、按监区、按风险等级、按姓名模糊搜索等好几种组合,用MyBatis的动态SQL来拼条件,比JPA那个Specification好写得多,也直观得多。
MySQL就不用说了,免费、稳、资料多。这套系统数据量级撑死在几十万行以内,MySQL性能完全没压力。真要较真的话,InnoDB引擎、utf8mb4字符集、按主键和常用筛选字段建索引,这三件事做好就够用了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计:评估系统能不能落地,看表结构就知道了
2.1 核心业务表拆解与字段设计
我建表的时候遵循一个原则:业务主表要少,但每张表的字段要能覆盖完整业务场景。最终核心表一共六张,足够支撑这套系统跑起来。
第一张是prisoner_info,在押人员信息表。字段包括id、prisoner_no(编号,唯一索引)、name、gender、age、crime_type(罪名类型)、sentence_start、sentence_end、prison_area(监区)、cell_no(监室号)、admission_date(入所时间)、previous_offense(是否有前科)、violence_history(是否有暴力史)、suicide_risk(是否有自伤自残倾向)、status(在押/已释放)、create_time、update_time。这里我多说一句:罪名类型和监区尽量用字典表管理,不要直接存中文。我第一版直接用varchar存中文,后来统计按监区分组时发现经常有人把“一监区”写成“一监区”带个空格,数据一下就乱了。换成dict_type+dict_value之后,统计干干净净。
第二张是sys_user,用户表。字段有id、username、password(BCrypt加密存储)、real_name、role(枚举:ADMIN/EVALUATOR/AUDITOR)、prison_area(负责的监区范围,管理员可以为空)、status、create_time。这里有个设计细节:评估人和审核人的角色是分开的,这样从权限层面就杜绝了“自己评自己审”的问题。监狱业务对这个有硬性要求,所以我在后端接口上也做了校验,创建评估单时会检查当前用户是否拥有评估人角色。
第三张是assessment_record,评估记录表。这是核心业务表,字段包括id、record_no(评估单号)、prisoner_id、evaluator_id(评估人)、auditor_id(审核人)、assess_date、total_score、risk_level(LOW/MEDIUM/HIGH)、status(DRAFT待提交/PENDING待审核/APPROVED已通过/REJECTED已退回)、reject_reason、create_time、audit_time。这个表我用status字段串起了整个流程状态机,后端用枚举严格约束状态流转,不允许乱跳。
第四张是assessment_template,评估模板表。字段有id、template_name、version、description、is_active、create_time。为什么要单独拆一张模板表?因为评估量表不是一成不变的,业务方可能隔几个月调整指标权重甚至增删指标。如果写死在代码里,改一次就得发一次版本,非常麻烦。模板表加版本号字段,可以保留历史模板,评估记录关联的是评估那一刻的模板,历史数据不会被新模板影响。
第五张是assessment_item,评估指标表。字段有id、template_id、item_name、parent_id(支持二级指标)、item_type(打分项/选择题项)、weight(权重)、score_range(分值范围)、sort_order。这个表是评估打分的核心,二级指标通过parent_id自关联。权重设计我会在下面单独讲。
第六张是assessment_detail,评估明细表。字段有id、assessment_id(关联评估记录)、item_id、score、remark。每一条评估记录对应若干条明细,相当于一个评估单的“购物车清单”。为什么要单独存明细而不直接在评估记录表里存一个总分?因为审核人要看明细,统计时要分析单项指标分布,如果只存总分,这些需求全做不了。
2.2 指标权重设计:算分逻辑里的门道
评估打分最敏感的就是权重。这块我踩过一次坑,一开始直接把权重写死在代码里,后来业务方说要调,前后端改了三处才同步完,第二次我就老老实实把权重挪到数据库了。
权重的设计我建议用一个总分100分制的方案。举个例子,假设模板有四个一级指标:暴力倾向(权重40%)、脱逃风险(权重30%)、情绪稳定(权重20%)、违规违纪(权重10%)。每个一级指标下挂若干二级指标,二级指标平分所属一级指标的权重。比如暴力倾向下面有三个二级指标:近期是否有暴力言行(满分40分中占15分)、是否有持械倾向(占15分)、是否有威胁他人记录(占10分)。
算分逻辑在后端是这样的:遍历该评估记录关联的模板下所有指标,把选中的二级指标的得分乘以权重累加,得出总分。具体公式是total_score = sum(明细得分),因为明细的得分设计时就按权重折算好了,这样前端展示的时候直接加总就行,不用做二次乘法。但展示明细时,我额外加了一个max_score列,让前端能看到得分和满分的比例,比如“8/15”,看起来直观。
风险等级映射我用的是区间判断:总分低于45分判定低风险,45到70分判定中风险,70分以上判定高风险。这个阈值不要写死在代码里,放到字典表里,因为业务方的风险偏好随时会调。系统里给管理员留了一个参数配置页面,改阈值立即生效,后端每次评估前从配置表拉一次。
2.3 索引设计与联表查询的防坑指南
索引这块我吃过亏。最初上线时评估明细查询特别慢,后来发现是没给assessment_detail.assessment_id建索引。其实联想一下就知道,查一个评估单的明细,走的主表关联,但明细表数据一多,全表扫描就拖垮了。后来我加上这个索引,查询时间从毫秒级再到毫秒级,问题解决。
我最终建的索引有这些:assessment_record.prisoner_id(查某个在押人员的历次评估记录)、assessment_record.status(按状态统计待办)、assessment_record.assess_date(按时间范围统计)、assessment_detail.assessment_id(查明细)、prisoner_info.prisoner_no(唯一索引,按编号精确查)。另外一个很重要的点是:不要在索引列上做函数运算。我当时写了一个查询“按月统计评估单数”,用了MONTH(assess_date),结果MySQL根本走不了索引,扫了全表。后来改成assess_date BETWEEN '2024-01-01' AND '2024-01-31',问题就解决了。这个坑很基础但很容易犯。
3. 后端实现:从SpringBoot配置到MyBatis动态SQL
3.1 工程结构与配置文件要点
后端工程我用的标准分层结构,controller、service、mapper、entity、common五层。common里放统一返回结果R、异常处理、JWT工具类、全局异常处理器。结构看起来简单,但足够支撑这套业务了。
application.yml里几个关键配置我贴一下:
yaml复制server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/prison_assessment?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: 123456
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.assessment.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
jwt:
secret: your-256-bit-secret-key-here
expire: 604800
这里有几个点给新手提个醒。第一,serverTimezone=Asia/Shanghai必须有,否则MySQL 8的时区问题会让你日期凭空差8小时。第二,map-underscore-to-camel-case要开,这样数据库里的create_time能自动映射到实体类的createTime,省掉一堆@Results注解。第三,log-impl建议开发阶段开,打日志方便排查SQL问题,上线记得关掉,否则日志文件会爆炸。
JDK版本这里补一句。SpringBoot 2.7系列对应JDK 8和11都行,SpringBoot 3.x强制JDK 17。如果项目还是环境内的老JDK 8,那就老老实实用SpringBoot 2.7.x,别追新版,不然一堆依赖不兼容。我们生产环境就是JDK 8 + SpringBoot 2.7.18,稳稳跑了半年。
3.2 MyBatis映射文件与动态SQL实战
MyBatis的XML写好了是真的舒服。评估记录查询那个接口是动态SQL最典型的场景,我贴一下核心片段:
xml复制<select id="selectAssessmentPage" resultType="com.example.assessment.entity.AssessmentRecordVO">
SELECT r.id, r.record_no, p.prisoner_no, p.name AS prisoner_name,
p.prison_area, r.total_score, r.risk_level, r.status,
r.assess_date, r.create_time,
u1.real_name AS evaluator_name, u2.real_name AS auditor_name
FROM assessment_record r
LEFT JOIN prisoner_info p ON r.prisoner_id = p.id
LEFT JOIN sys_user u1 ON r.evaluator_id = u1.id
LEFT JOIN sys_user u2 ON r.auditor_id = u2.id
<where>
<if test="prisonerName != null and prisonerName != ''">
AND p.name LIKE CONCAT('%', #{prisonerName}, '%')
</if>
<if test="prisonArea != null and prisonArea != ''">
AND p.prison_area = #{prisonArea}
</if>
<if test="riskLevel != null and riskLevel != ''">
AND r.risk_level = #{riskLevel}
</if>
<if test="startDate != null and startDate != ''">
AND r.assess_date >= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND r.assess_date <= #{endDate}
</if>
</where>
ORDER BY r.create_time DESC
</select>
这个SQL有几个细节是经验沉淀。第一,用<where>标签而不是直接拼WHERE 1=1,MyBatis会自动处理掉第一个条件前面的AND,写出来干净也专业。第二,LIKE CONCAT('%', #{prisonerName}, '%'),不要用LIKE '%${prisonerName}%',$是字符串直接拼接,存在SQL注入风险,#{}才是预编译参数。第三,左连接要把所有可能为空的外键都考虑进去,这就是为什么评估人、审核人都用LEFT JOIN而不是INNER JOIN,因为审核还没发生时,评估单上是没有auditor_id的。
打分表入库这个场景用动态<foreach>批量插入,评估时一次提交十几个明细项,逐条insert效率太低。批量插入的写法:
xml复制<insert id="batchInsertDetails">
INSERT INTO assessment_detail (assessment_id, item_id, score, remark)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.assessmentId}, #{item.itemId}, #{item.score}, #{item.remark})
</foreach>
</insert>
注意MySQL的max_allowed_packet如果默认偏小,批量插入一次性塞太多数据会报错。这种评估明细一次最多三四十条,远到不了那个上限,但如果你做的系统有批量导入功能,就要注意分批插入,一次500条比较稳。
3.3 风险评估分值计算与状态流转实现
评估计算这块我在Service层写了一个AssessmentService,核心方法是calculateScore(Long templateId, List<AssessmentDetail> details)。
流程是这样的:先查出模板下所有指标,把二级指标按parent_id归组,计算时遍历前端提交的明细,从指标表里拿到权重和满分,校验分数没超过满分之后累加。计算完成后,根据当前模板的风险阈值区间映射风险等级。评分完成的同时会生成一个record_no,格式是AS + yyyyMMdd + 三位流水号,比如AS20250318001,用于业务追溯。
状态流转这里我用一个简单的状态机。DRAFT可以改为PENDING提交;PENDING状态下只能执行审核操作,审核结果要么APPROVED要么REJECTED;REJECTED的评估单可以由原评估人修改后再次提交,回到PENDING。所有非法跳转,后端都直接抛异常。这个状态机逻辑不多,但用枚举+Map来管理比散落一堆if else强得多。我贴一下核心思路:
java复制private static final Map<AssessmentStatus, List<AssessmentStatus>> TRANSITIONS =
Map.of(
AssessmentStatus.DRAFT, List.of(AssessmentStatus.PENDING),
AssessmentStatus.PENDING, List.of(AssessmentStatus.APPROVED, AssessmentStatus.REJECTED),
AssessmentStatus.REJECTED, List.of(AssessmentStatus.PENDING)
);
public void transition(AssessmentRecord record, AssessmentStatus target) {
List<AssessmentStatus> allowed = TRANSITIONS.get(record.getStatus());
if (allowed == null || !allowed.contains(target)) {
throw new BusinessException("非法的状态流转: " + record.getStatus() + " -> " + target);
}
record.setStatus(target);
}
这个方案后续扩展很容易,比如业务上要加一个“已归档”状态,只需要在枚举里加一个值,然后在Map里配好流转路径就行,调用方完全不用改代码。
3.4 JWT鉴权与接口权限控制的细节
权限这块我用了JWT + 拦截器的组合,没有引Spring Security,原因很简单:这套系统角色少、权限模型简单,Security的配置成本反而成了负担。JWT方案能让我精确控制每个接口的角色要求。
登录成功后,后端签发JWT,把用户id和角色塞进token里。前端每次请求在Authorization头带上token,后端写一个AuthInterceptor拦截器,解析token,把用户信息放到ThreadLocal里,业务代码里直接取当前登录人。
权限控制我用了一个自定义注解@RequireRole("AUDITOR"),标注在Controller方法上。拦截器里先判断方法有没有这个注解,如果有,就比对当前用户角色,不匹配直接返回403。用起来非常直观:
java复制@PostMapping("/audit")
@RequireRole("AUDITOR")
public R<Void> audit(@RequestBody AuditDTO dto) {
assessmentService.audit(dto);
return R.ok();
}
密码存储这里再强调一下,必须用BCrypt加密,不能用MD5(现在随便一个字典库都能逆向MD5)。我用的Spring Security crypto里的BCryptPasswordEncoder,单拎出来用就行,不引整个Security框架。注册或改密时encode,登录时matches校验。
JWT密钥要足够长,我项目里用的是一串32字节以上的随机字符串。expire我设了7天,内网环境下足够用了。如果用默认的密钥(比如网上一堆教程里的secret),别人知道你的密钥就能伪造token,安全性直接归零。
4. 前端Vue实现:管理后台也可以做得好用又好看
4.1 前端工程初始化与路由权限控制
前端用的Vue 2 + Vue Router 3 + Vuex + Element UI这套组合,没有上Vue 3和Vite。原因很现实:这套技术栈资料最多,出问题一搜就有答案,而且团队里其他同事也熟。你要是自己从零学习,Vue 3 + Vite体验会更好,但想快速复用成熟组件生态,Vue 2 + Element UI依然能打。
vue.config.js里配一个开发代理,解决前后端联调跨域问题:
js复制devServer: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
路由设计上我做了两级:一级路由是布局页Layout,二级路由是具体功能页。每个路由的meta里挂了roles字段,用来做前端页面级权限控制。全局前置守卫里这样判断:
js复制router.beforeEach((to, from, next) => {
const token = store.state.token
if (!token && to.path !== '/login') {
next('/login')
} else if (token && to.meta.roles && !to.meta.roles.includes(store.state.role)) {
next('/403')
} else {
next()
}
})
这套方案有个优点:菜单是根据当前用户角色动态渲染的。管理员登录能看到“评估模板配置”和“参数配置”菜单,评估人登录只看到“档案管理”和“评估打分”,审核人登录只看到“待审核”菜单。前端隐藏菜单只是体验层面的控制,真正的安全边界永远在后端接口的@RequireRole注解上,这一点一定要记住。
4.2 评估表单页面:动态渲染打分项的组件设计
评估打分页面是前端最复杂的部分,难点在于指标不是写死的,而是从后端模板接口动态加载的。评估人进入页面,先选在押人员,然后前端请求/api/template/active拿到当前生效的模板,模板里包含一级指标和二级指标,渲染成一个分组动态表单。
这个动态渲染我用的是Element UI的el-form加动态prop。一级指标渲染成el-divider分隔,二级指标根据类型渲染不同组件。打分项有两种:评分输入框和单选按钮组。评分输入框我做了最大分校验,后端也会校验一次,但前端做了用户提示体验会好很多。单选用el-radio-group,选项是“无、轻、中、重”,对应分值映射在后端处理。
组件里比较坑的一个点是:动态表单项的prop必须是一个字符串路径,比如detailMap[12].score这样,el-form的rules才能正确校验。我一开始没注意,所有表单项的prop都写成了score,结果校验规则永远只作用于最后一个字段。排查了半天才发现是prop的问题。
页面提交前,前端会做一次总分试算。把明细项里的分数加上去,实时显示“当前总分:62,预计风险等级:中风险”。这个是纯前端的体验功能,逻辑跟后端保持一致,让评估人在点击提交前就对结果有数。
4.3 统计报表与ECharts图表封装
统计报表这个模块是系统上线后使用频率最高的页面。我做了三个维度:风险等级分布、近半年的评估趋势、各监区平均分对比。
ECharts封装成了一个chart-panel组件,父组件传option进来,子组件负责初始化、resize监听和数据更新。这里有一个重要经验:ECharts实例化之后,组件销毁前一定要调用dispose,否则反复切换路由会内存泄漏。另外当数据更新时,不要直接清空setOption,而是传notMerge参数控制是否需要合并旧配置。比如风险分布饼图直接setOption(newOption, true)即可,但趋势折线图只想更新数据时用smooth的话,不清空旧配置会残留上一次的选中态,视觉上很怪。
我这边的做法是:图表组件里watch option的变化,深比较之后setOption(newOption, true)。用true强制全新渲染,虽然性能略低一点,但数据驱动的后端系统图表根本不会有性能问题,换来的是干净的视觉表现。
首页的预警提示框也是一个前端亮点。页面加载时调用/api/dashboard/alert,后端返回近7天评估为高风险且连续两次高风险的囚犯列表。前端把这些数据渲染成醒目的卡片列表,点击可以跳转到评估详情。这种“数据推送”式的设计比让用户自己去翻列表高效太多了,业务方看到这个功能时明显很满意。
5. 本地运行全流程与典型报错排查
5.1 从零到能跑:环境准备与启动顺序
如果你刚拿到这套源码想跑起来,环境准备千万别跳步。第一步装JDK 8,配好JAVA_HOME环境变量;第二步装MySQL 8,记住root密码;第三步装Maven 3.6以上;第四步装Node.js 14以上(Vue 2推荐14,不推荐18,某些老依赖在18下会报OpenSSL错误)。
数据库这块,工程里我放了一个init.sql,包含建库、建表、初始化字典数据和默认账号。执行顺序是:新建prison_assessment库,执行init.sql,然后修改application.yml里的数据库账号密码,直接启动后端主类。
后端启动成功后,控制台会显示Tomcat started on port 8080,同时你会看到MyBatis打印的SQL日志。看到HikariPool - Start completed就说明数据库连接池初始化成功。如果启动报Failed to configure a DataSource,十有八九是application.yml里数据库地址或账号密码不对,或者MySQL服务没有启动。
前端启动分两步。先npm install装依赖,这一步最容易卡住,建议用国内镜像源:
bash复制npm config set registry https://registry.npmmirror.com
npm install
装完依赖后npm run serve,看到App running at http://localhost:3000就是启动成功了。前端页面如果请求接口报404,先确认代理配置是否生效,以及后端接口路径是否带/api前缀。建议统一让后端Controller的映射都带/api前缀,代理匹配也写'/api',两边不会打架。
5.2 高频踩坑记录与解决办法
这里把我开发过程中踩得最深的几个坑整理一下,都是实打实花时间排查出来的。
第一个坑是MySQL连接驱动版本不一致。SpringBoot 2.7.18默认使用的是MySQL Connector/J 8.0.x,而我本地MySQL是5.7,连接时直接报Public Key Retrieval is not allowed。解决办法有两个:要么升MySQL到8.x,要么在JDBC URL里加allowPublicKeyRetrieval=true。建议直接升级MySQL 8,5.7早就EOL了。
第二个坑是前端跨域Session过期但接口还能调通。这个其实是正常的,因为我们用的是JWT无状态鉴权,Session跟系统没有任何关系。但如果你看到登录后刷新页面就退出登录,那大概率是token存在localStorage但路由守卫里没有正确读取,或者是axios请求拦截器里没有加上Authorization头。我最后把token读取和注入封成了一个request.js模块,所有请求统一走这个模块,问题绝迹。
第三个坑是MyBatis返回的Map键大小写问题。做统计报表时我用SELECT COUNT(*) AS cnt ...,在MySQL里字段别名会转成大写CNT,但MyBatis默认开启下划线转驼峰,导致cnt和CNT映射对不上,实体类里取不到值。最后我在SQL里给别名加了双引号,或者干脆在Mapper接口上直接返回Integer类型,绕开Map转换。
第四个坑是ECharts在el-dialog里显示宽度为0。原因是弹窗打开时图表容器还是隐藏的,ECharts初始化时拿不到正确宽度。解决办法是弹窗打开后用this.$nextTick初始化图表,或者监听dialog的open事件再调用chart.resize()。这是我调了一个下午才定位到的,很多新手都会栽在这个上面。
6. 一点实际的开发建议
最后我想说两个运维层面的事情。内网部署时,后端Jar包可以用java -jar prison-assessment.jar启动,配合nohup在后台运行。前端打包用npm run build产出dist目录,用Nginx托管,记得配置proxy_pass把/api请求转发到后端8080端口。整个部署过程只要服务器有JDK8和Nginx就能跑,依赖很少。
安全性上再补一刀。既然是司法业务系统,日志里不能出现明文密码,接口参数要做基础校验,管理员的初始密码必须强制修改。建议开发时就把操作日志表建好,记录每次评估、审核的关键操作,包括操作人、操作时间、操作内容。这些数据平时没人看,但真要追溯问题的时候,它能救你一命。
这套系统做完,我对SpringBoot+MyBatis这套技术栈的理解上了一个台阶,也对业务流程如何转化为系统功能这件事更有感觉。如果你准备复现或者做类似的管理系统,建议重点把评估计算和动态模板这两块吃透,其余页面无非是增删改查的排列组合。遇到报错多看看控制台和日志,大部分问题都能自己解决。
