☰
SpringBoot+Vue+MyBatis实战:隔离管理系统从数据库到部署全复盘
2026/10/9 3:03:50 网站建设 项目流程

2025年还在找疫情隔离管理系统源码的人,大概率是毕设或者项目急需。半年多前我完整做了一版基于SpringBoot+Vue+MyBatis+MySQL的隔离管理系统,从数据库设计、后端接口到前端页面全部跑通,也踩了无数环境配置的坑。这篇就把整个系统从业务梳理到部署上线的全过程复盘一遍,重点讲清楚每个技术选择背后的原因、核心功能的实现方式,以及那些常规文档里根本不会写的坑。合适的人包括:正在做管理系统类毕业设计的学生、公司内部需要快速搭一套台账/人员管理系统的后端开发,以及想学SpringBoot+Vue全栈项目完整链路的朋友。

先说明一下,这套系统虽然名字里带“隔离”,但它本质上就是一个典型的信息管理系统:有人员登记、状态流转、健康监测记录、物资出入库、统计看板。你以后要是把“隔离人员”换成“住院病人”“项目工人”“公寓住户”,这套技术框架和业务设计几乎可以直接复用。

1. 先搞清楚业务边界:隔离管理系统到底管什么

很多人做管理系统一上来就先建表,结果写到一半发现功能越来越乱,各种状态互相冲突。这个项目的业务边界,我建议先按角色拆,再按流程串。

1.1 核心角色与功能模块划分

一套能实际用的隔离管理系统,至少要支撑四类角色:系统管理员、医护或驻点工作人员、隔离人员本人、物资管理员。不同角色关注的数据完全不同,权限模块不能省。

  • 系统管理员:维护系统用户、角色、菜单,管理隔离点和房间,看全局统计。
  • 医护或驻点工作人员:负责隔离人员登记、每日健康监测录入、解除隔离审核、异常情况复核。
  • 隔离人员本人:查看自己的登记信息、房间号,每天提交体温和症状。
  • 物资管理员:维护物资信息、入库出库、看库存预警。

根据这些角色,功能模块最终收敛成七个:用户权限管理、隔离点管理、房间管理、隔离人员管理、健康监测管理、物资管理、数据统计看板。每个模块都不需要做得多花哨,但主流程必须完整。我见过很多项目把侧重点放在图表上面,结果人员台账一塌糊涂,这属于本末倒置。

1.2 功能优先级排序:哪些功能必须做,哪些可以后补

如果你是独立开发,我建议先做主线,也就是:人员登记 -> 健康监测 -> 解除隔离。这条流程跑通了,系统就立住了七成。接着补房间和隔离点管理,因为人员登记的时候必须选房间,没有房间管理就只能乱填。最后再做物资管理和统计看板。

我实际开发的时候犯过一个错:先做统计图表,因为图表看起来“有成果”,结果台账录入还没写完,前后端一联调就发现字段对不上。后来我调整了顺序,把精力集中在核心流程上,整套系统才真正能用。

一个大致的比例供参考:核心业务流转占60%,权限和基础配置占20%,统计报表占10%,其他细节占10%。对毕设或者中小型项目,这个比例很稳妥。

1.3 状态机设计:隔离记录是怎么流转的

隔离管理系统最容易出问题的不是CRUD,而是状态流转。一个隔离人员的状态并不是随便改的,必须走固定流程:

待登记 -> 已登记 -> 隔离中 -> 待解除 -> 已解除

如果健康监测出现异常,会插入一个“异常待复核”状态;如果中途转出,则进入“已转出”终态;解除前还要有一个“待解除”的中间态,方便审批操作。

这个状态流转如果用字符串散乱写在业务代码里,后期一定会乱。我的做法是在MySQL里用一个tinyint字段存数字状态码,在Java代码里维护一个状态枚举,再配合字典表让前端翻译成中文标签。这样无论报表统计还是条件查询,都能基于同一套口径。

2. 技术选型的过程:为什么偏偏是SpringBoot+Vue+MyBatis+MySQL

这套技术栈看起来像是固定搭配,但每个选择背后都有实际理由。尤其是现在网上搜“springboot版本太高”“vue安装及环境配置”这类词的人越来越多,说明版本和兼容性已经成了大家最头疼的问题。

2.1 后端框架:SpringBoot能省掉多少配置

十年前做SSH或SSM,光springmvc.xml、mybatis-config.xml、数据源配置就要写一大堆。SpringBoot的价值在于把这些固化成了约定,一个@SpringBootApplication就能起服务,依赖也全部换成starter统一管理。

对于隔离管理系统这种业务其实不算复杂的项目,SpringBoot是最稳的选择。它有非常成熟的生态,分页插件、参数校验、Redis、MQ都可以通过starter直接引入。我这套系统里主要用到了spring-boot-starter-web、spring-boot-starter-validation,配合MyBatis或MyBatis-Plus,日常开发效率很高。

这里要专门提一个非常现实的问题:SpringBoot版本。现在最新版本已经到3.2甚至更高,但如果你照抄网上的教程,会发现很多老代码全部失效。我最早用SpringBoot 3.1写这个项目,结果javax.servlet变成了jakarta.servlet,MyBatis Plus的starter也要换版本,同事传给我的工具类几乎全部报错。后来为了保证稳定,我把版本降到SpringBoot 2.7,两天内项目就顺利跑通。

我的建议是:如果你的项目不需要虚拟线程、GraalVM这些新特性,就老老实实用2.7,资料多、踩坑少,用人成本也低。

2.2 MyBatis还是JPA:我的选择是MyBatis

JPA和Hibernate在中小型项目上确实开发快,但管理系统的报表查询非常复杂,经常出现多条件组合、动态排序、多表关联,用JPA的Specification写起来很绕。MyBatis允许我直接写SQL,隔离人员列表这种“多条件组合查询+分页”的场景,一段动态SQL就搞定,SQL执行计划也能直接EXPLAIN分析。

MyBatis的面试题里常问一级缓存和二级缓存。一级缓存默认开启,范围是SqlSession,同一个Session中执行两次相同查询会走缓存;二级缓存默认不开启,按namespace隔离,相当于Mapper级别。在管理系统中,我不建议开二级缓存,因为很容易出现数据不一致。比如你在后台手动修改了一条数据,缓存没刷新,列表页还在显示旧值,排查起来非常耗时。与其花精力维护缓存一致性,不如直接关掉,性能损失并不明显。

2.3 前端为什么选Vue 3

管理系统的前端特点是表单多、表格多、交互中规中矩,Vue的学习曲线比React平缓,中文资料也最全。我用的是Vue 3 + Element Plus + Axios + Vue Router。

Vue 3的Composition API让我能把某个业务相关的变量和方法集中在一起,不用像Vue 2那样到处mixins。配合Element Plus,后台界面的开发效率非常高。

但Vue的环境配置确实是个坑。我见过很多人在npm install阶段就卡半天,大概率是Node版本不对。如果你用Vite创建Vue 3项目,Node版本最好在18以上;如果用webpack,还需要注意node-sass和Node版本对应。还有人在IDEA里开发Vue项目,发现热更新不生效,多半是没装Vue官方插件或者Node路径没配置好。处理办法很直接:卸载node_modules,换Node版本,重新安装。

2.4 数据库版本:MySQL 5.7还是8.0

MySQL 8.0的窗口函数、JSON支持、性能提升都很好,但运维成本比5.7略高。我本机开发用的MySQL 8.0,最后部署的服务器是5.7。为了避免切换版本时出问题,代码里我尽量避免用8.0特有语法,尤其是窗口函数,能不用就不用。

MySQL安装也是个高频问题。Windows环境下装8.0要注意my.ini里的字符集配置,比较稳妥的做法是直接设置character-set-server=utf8mb4,不然建表时中文容易乱码。Linux服务器上用yum或apt安装后,第一件事就是确认root用户认证插件,MySQL 8.0默认可能是caching_sha2_password,老版本JDBC驱动连不上,需要在连接串上加allowPublicKeyRetrieval=true。

3. 数据库设计:隔离业务如何落到表结构

技术选型定了以后,我最先做的事情不是写代码,而是建表。表设计是这种管理系统的灵魂,字段没想清楚,后面每一层都在补窟窿。

3.1 核心表清单

我最终设计了12张表,按照功能域可以分成四组:

表名功能域用途核心字段
sys_user系统管理用户账号id, username, password, real_name
sys_role系统管理角色id, role_code, role_name
isolation_point隔离点管理隔离点id, point_name, address, manager
point_room房间管理房间id, point_id, room_no, status
isolate_person人员管理隔离人员台账id, person_name, id_card, room_id, status
health_record健康监测每日体温和症状记录id, person_id, temperature, record_time
material物资管理物资基础信息id, material_name, spec, total_stock
material_in_out物资管理出入库流水id, material_id, type, quantity, create_time
task_info工作管理工作人员任务id, title, assignee_id, status
notice_info公告管理站内公告id, title, content, publish_time

项目如果还要做登录日志、操作审计,可以再加两张表,但核心业务有上面这些就够了。

3.2 关键表设计:隔离人员表与状态流转

隔离人员表是系统的核心,字段要够用但不能堆砌。我的建表SQL如下:

CREATE TABLE `isolate_person` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `person_name` varchar(50) NOT NULL COMMENT '姓名', `id_card` varchar(20) DEFAULT NULL COMMENT '身份证号', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `gender` tinyint(1) DEFAULT NULL COMMENT '性别 0女 1男', `room_id` bigint(20) DEFAULT NULL COMMENT '房间ID', `check_in_date` datetime DEFAULT NULL COMMENT '登记/入住时间', `plan_leave_date` datetime DEFAULT NULL COMMENT '计划解除时间', `actual_leave_date` datetime DEFAULT NULL COMMENT '实际解除时间', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态 1已登记 2隔离中 3待解除 4已解除 5已转出 6异常待复核', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_room_id` (`room_id`), KEY `idx_status` (`status`), KEY `idx_id_card` (`id_card`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='隔离人员信息表';

这个表有几个设计细节值得注意。第一,status单独加索引,因为列表页和统计页几乎都会按状态过滤。第二,身份证号这类高频查询条件也要索引,数据量到了几万条,没有索引的查询会明显变慢。第三,MySQL表名、字段名下划线风格,Java对象用驼峰,后面配置文件里必须开启map-underscore-to-camel-case,这算基础操作。

3.3 多表关联的落地:房间、隔离点、人员之间的关联

房间和隔离点是两棵树:一个隔离点下有多个房间,一个房间只能分配给一个隔离人员。我的做法是让isolate_person表冗余room_id,point_room表冗余point_id,人员查询时一次性把房间号、隔离点名称都查出来,避免前端做多次请求。

SELECT p.*, r.room_no, po.point_name FROM isolate_person p LEFT JOIN point_room r ON p.room_id = r.id LEFT JOIN isolation_point po ON r.point_id = po.id

有人会把房间和隔离点设计成多级字典,但实际业务中隔离点数量并不多,直接用两张表最清晰。

3.4 多对多关系的落地:以物资分配为例

物资和隔离点、批次之间是多对多关系。我设计了material_in_out表作为中间表,每一条记录代表一次出入库动作,type字段区分方向和入库还是出库。

CREATE TABLE `material_in_out` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `material_id` bigint(20) NOT NULL, `point_id` bigint(20) NOT NULL, `type` tinyint(1) NOT NULL COMMENT '1入库 2出库', `quantity` int(11) NOT NULL COMMENT '数量', `operator_id` bigint(20) DEFAULT NULL COMMENT '操作人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_material_id` (`material_id`), KEY `idx_point_id` (`point_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里我反复提醒过团队:不要把库存直接累加到material.total_stock上。虽然那样写代码简单,但历史记录会全部丢失,审计和追溯完全做不到。正确做法是保留流水表,库存通过SQL实时计算,或者每天凌晨定时汇总到一张库存快照表。

4. 后端代码实现:拿“新增隔离人员登记”串起整个链路

后端接口设计直接决定前端开发是否顺畅。这里我用最核心的功能“新增隔离人员登记”来拆解从Controller到Service再到Mapper的完整实现。

4.1 目录结构与分层

我使用的包结构如下:

com.example.isolation ├── controller ├── service │ └── impl ├── mapper ├── entity ├── dto ├── common │ ├── Result.java │ ├── PageResult.java │ └── BusinessException.java └── config

Controller只做请求接收和参数校验,Service负责业务逻辑,Mapper只做数据访问。有些小型项目会省掉Service层,直接把Mapper放Controller里,但一旦业务逻辑复杂起来,代码就会变成几百行一个方法。分层看起来多写了几个类,后期维护会非常舒服。

4.2 Controller层设计:参数校验与统一返回

新增人员的接口我是这样写的:

@RestController @RequestMapping("/api/person") public class IsolatePersonController { @Resource private IsolatePersonService isolatePersonService; @PostMapping public Result<Void> add(@RequestBody @Valid IsolatePersonDTO dto) { isolatePersonService.addPerson(dto); return Result.success(); } }

注意这里没有写任何业务判断,全部逻辑都委托给Service。DTO里通过Validation注解做基础校验:

public class IsolatePersonDTO { @NotBlank(message = "姓名不能为空") private String personName; @Pattern(regexp = "^[0-9]{15,18}$", message = "身份证号格式不正确") private String idCard; @NotNull(message = "房间ID不能为空") private Long roomId; }

有一个身份证校验的坑我后面会细说,这里先提一句:身份证有15位和18位两种合法长度,正则不要写死成18位。

统一返回结构体Result的好处,是前端只需要处理一种标准结构。前端判断code是否等于200,不等于就弹出message,等于就直接拿data用。

4.3 Service层业务逻辑:状态变更的约束

新增登记并不是简单插一条记录,它要做的事情包括:检查房间是否存在且未被占用、检查身份证号是否已登记、插入隔离人员记录、把房间状态更新为“已占用”、记录操作日志。为了保证数据一致性,这个流程必须放在一个事务里。

@Service public class IsolatePersonServiceImpl implements IsolatePersonService { @Resource private IsolatePersonMapper isolatePersonMapper; @Resource private PointRoomMapper pointRoomMapper; @Transactional(rollbackFor = Exception.class) @Override public void addPerson(IsolatePersonDTO dto) { if (isolatePersonMapper.countByIdCard(dto.getIdCard()) > 0) { throw new BusinessException("该身份信息已登记"); } PointRoom room = pointRoomMapper.selectByIdForUpdate(dto.getRoomId()); if (room == null || room.getStatus() != 0) { throw new BusinessException("房间不存在或已被占用"); } IsolatePerson person = new IsolatePerson(); BeanUtils.copyProperties(dto, person); person.setStatus(1); isolatePersonMapper.insert(person); pointRoomMapper.updateStatus(dto.getRoomId(), 1); } }

这里的selectByIdForUpdate很关键,它会对房间行加锁,防止两个并发请求把同一间房分配给两个人。如果你用普通select,再update,就会出现典型的高并发超卖问题。这个细节在真实项目里非常重要,面试官也爱问。

4.4 Mapper层动态SQL:多条件查询怎么写

隔离人员列表页的搜索条件非常多:姓名、手机号、状态、隔离点、日期范围。用注解SQL维护这种查询会非常痛苦,我直接写在XML里:

<select id="selectPersonPage" resultType="com.example.isolation.entity.IsolatePerson"> SELECT p.*, r.room_no, po.point_name FROM isolate_person p LEFT JOIN point_room r ON p.room_id = r.id LEFT JOIN isolation_point po ON r.point_id = po.id <where> <if test="dto.personName != null and dto.personName != ''"> AND p.person_name LIKE CONCAT('%', #{dto.personName}, '%') </if> <if test="dto.status != null"> AND p.status = #{dto.status} </if> <if test="dto.pointId != null"> AND po.id = #{dto.pointId} </if> <if test="dto.startDate != null"> AND p.check_in_date &gt;= #{dto.startDate} </if> </where> ORDER BY p.create_time DESC </select>

动态SQL的<where>标签会自动去掉开头的AND,这个细节让SQL拼接干净很多。分页我用PageHelper,一行代码就能把列表变成PageInfo。

还有一个实用经验:查询条件不要散成Mapper接口上的一串参数,那样每加一个筛选条件就要改方法签名。最好封装一个QueryDTO对象,方法只接收一个参数,以后加条件只需要改DTO里面的字段和XML里的if判断。

4.5 健康监测、解除隔离等接口的实现思路

除了人员登记,还有两个核心接口:录入健康监测记录、解除隔离审核。

健康监测记录的逻辑很简单,插入记录时更新人员状态为“异常待复核”或继续保持“隔离中”。解除隔离则要检查是否所有健康记录都正常,如果存在异常,不允许直接解除。

这里我把业务规则放在Service,而不是前端按钮里。前端可以不显示解除按钮,但后端也必须校验,这是管理系统最基本的安全底线。

4.6 MyBatis的常见坑与解决办法

最后专门说几个MyBatis的坑。第一个是字段映射,MySQL表字段是下划线风格,Java是驼峰风格,必须在application.yml里开配置:

mybatis: configuration: map-underscore-to-camel-case: true

这个配置漏掉的后果是查询出来的对象某些字段全是null,排查半天都找不到原因。第二个是Mapper方法有多个参数时必须加@Param注解,否则会报There is no getter错误。第三个是二级缓存问题,建议直接关掉,尤其是这种需要实时统计的管理系统,缓存带来的性能提升远没有数据一致性问题严重。

5. 前端Vue实现:页面不是堆组件

后端接口再规范,前端页面要是乱成一团,用户照样不会用。这个系统前端我用Vue 3 + Element Plus + Vue Router + Pinia,整体体验比较顺畅。

5.1 项目初始化与目录结构

创建项目用Vite,一行命令:

npm create vite@latest isolation-ui --template vue cd isolation-ui npm install

安装依赖时最容易出问题。npm源慢就换镜像,node-sass和Node版本不匹配就统一版本,别在一个问题上磨太久。

我推荐的src目录结构:

src ├── api # 接口请求定义 ├── components # 公共组件 ├── layout # 页面布局 ├── router # 路由配置 ├── store # Pinia状态 ├── utils # 工具函数 └── views # 页面视图

views下面按业务模块建子目录,比如person、health、room、material、dashboard。不要在views里塞一堆无关组件,后期维护成本很高。

5.2 Axios请求封装与跨域代理

Axios如果不做统一封装,每个页面都要处理错误弹窗、登录超时,代码会非常冗余。我封装了一个request.js:

import axios from 'axios' import { ElMessage } from 'element-plus' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '系统异常') return Promise.reject(new Error(res.message)) } return res }, error => { ElMessage.error(error.response?.data?.message || '网络异常') return Promise.reject(error) } ) export default request

开发环境的跨域问题,我是通过Vite代理解决的,不在后端写@CrossOrigin,否则上线Nginx还得再处理一次:

// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

5.3 隔离人员管理页面的实现逻辑

列表页通常由四个区域组成:搜索区、表格区、分页区、新增编辑弹窗。搜索条件我封装成一个响应式对象:

const queryParams = reactive({ personName: '', status: null, pointId: null, pageNum: 1, pageSize: 10 })

请求列表的时候把这个对象直接传给接口,后端用PageHelper分页。表格里的状态字段用字典翻译成Tag,比如“已登记”显示蓝色,“隔离中”显示橙色,“已解除”显示绿色。这个小细节会让页面专业很多。

5.4 路由与权限控制

管理系统不能把菜单暴露给所有角色。我用Vue Router的路由守卫做登录认证,再把用户角色存到Pinia或LocalStorage,在菜单渲染和按钮权限处做判断。

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') } else { next() } })

如果项目再复杂一点,可以做成动态路由,后端返回菜单结构,前端通过addRoute动态注册。隔离管理系统这种规模,先用静态路由和按钮权限就够了,不要过度设计。

5.5 组件化的一些实践

我抽了几个公共组件:部门树选择器、状态字典Tag、分页表格、可拖拽弹窗。菜单和表格的操作列经常需要自定义内容,我习惯在组件里用Vue插槽。比如状态Tag组件会暴露一个默认插槽,方便调用方塞自定义按钮。最终效果就是页面代码短了很多,新增模块时主要写配置和业务逻辑,不用反复复制粘贴样式。

6. 从本地到上线:环境搭建、部署和常见问题

很多人项目在本地能跑,一到服务器就各种问题。这里我把部署全流程捋一遍,按这个顺序操作,基本不会翻车。

6.1 MySQL安装与初始化

服务器装MySQL,CentOS用yum,Ubuntu用apt。装完第一件事是检查root认证方式。MySQL 8.0默认可能是caching_sha2_password,如果你的JDBC驱动版本太老,要在连接串上加allowPublicKeyRetrieval=true和useSSL=false。

初始化数据库:

mysql -u root -p < isolation.sql

isolation.sql里面除了建表,我还会插入初始用户、角色、菜单数据。否则前端登录进去连菜单都没有,项目根本没法演示。

6.2 SpringBoot打包与运行

后端打包:

mvn clean package -DskipTests

打出来的jar直接放服务器:

nohup java -jar isolation-system.jar --spring.profiles.active=prod > output.log 2>&1 &

生产环境数据库密码不要硬编码在application.yml里,可以通过环境变量覆盖:

export DB_PASSWORD='yourpassword' java -jar isolation-system.jar --spring.datasource.password=${DB_PASSWORD}

6.3 Vue项目打包与Nginx部署

前端打包:

npm run build

生成dist目录后上传到服务器。Nginx配置如下:

server { listen 80; server_name your-domain; root /opt/isolation-ui/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }

try_files这行是必须的,否则Vue Router的History模式刷新页面会404。另外要注意,proxy_pass后面带了/api/路径,后端接口地址就必须以/api开头,否则路径对不上。

6.4 上线后的日志与备份

上线后我最关注两件事:日志和数据库备份。日志用Logback按天滚动,保留30天;数据库每天凌晨用mysqldump全量备份,保留7天。这个不复杂,但一旦服务器出问题,能直接救命。

0 2 * * * mysqldump -u root -p'password' isolation > /backup/isolation_$(date +\%Y\%m\%d).sql

crontab里的百分号记得转义,这个细节我踩过坑,写进定时任务里不转义会直接执行失败。

7. 我实际开发中踩过的坑(经验总结)

这几个坑每一个都是真金白银换来的,有的花了半天排查,有的甚至导致数据不一致。单独拎出来讲讲。

7.1 SpringBoot版本太高引发的一连串问题

刚开始做项目,我图新鲜用了SpringBoot 3.1,结果网上的老教程和工具类用不了,javax被换成jakarta,MyBatis Plus版本不兼容,启动直接报错。最后我把整个项目降到SpringBoot 2.7,问题全部消失。

结论很简单:除非你有明确的新特性需求,比如虚拟线程、GraalVM,否则不要盲目追新版本。新版本意味着新规范、新bug、新兼容问题,对业务系统来说稳定才是第一位。

7.2 前端跨域问题排查

开发时Vite代理配置了,后端也加了@CrossOrigin,结果接口还是报错。后来看浏览器Network面板才发现,请求被代理了两次,路径从/api变成了/api/api,后端当然匹配不上。

排查方法很简单:永远先看浏览器里请求的实际URL和响应状态码,再去想代理和CORS问题。跨域绝大多数是路径配错,不是跨域本身。

7.3 Long类型精度丢失

后端主键如果用数据库自增Long,超过JavaScript安全数后,前端拿到的ID末尾几位会变成0,导致编辑时选了错误的记录。解决办法是让后端在序列化时把Long转成String:

@JsonSerialize(using = ToStringSerializer.class) private Long id;

或者全局配置ObjectMapper的ToStringSerializer。这个坑在大数据量、分布式ID场景下非常典型。

7.4 时间字段时区差8小时

健康监测记录的record_time字段,前端拿到后发现比本地时间早8小时。原因是Jackson默认时区与MySQL会话时区不一致。解决办法是在application.yml统一设置:

spring: jackson: time-zone: Asia/Shanghai date-format: yyyy-MM-dd HH:mm:ss

如果还不对,再检查JDBC连接串有没有加serverTimezone=Asia/Shanghai。

7.5 身份证号校验别写死18位

这个算业务坑。身份证早期有15位的,后来又出现了18位。如果你用正则写死18位,一批历史数据就录不进去。后来我直接把数据库字段设为varchar(20),后端校验同时兼容15位和18位,不再用@Pattern一棍子打死。

7.6 别把统计查询和业务写在同一个热链路

数据看板统计SQL很重,如果和核心业务共用同一个事务和连接池,高峰期会互相拖累。我后来把统计查询改成只读数据源,或者让定时任务每5分钟算一次汇总表,前端直接查汇总结果,速度提升非常明显。

最后分享一个我一直在用的后端基础配置,包含数据源、MyBatis、Jackson和日志,基本上从零到能跑只差一个main方法:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/isolation?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: ${DB_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.isolation.entity configuration: map-underscore-to-camel-case: true cache-enabled: false logging: level: com.example.isolation.mapper: debug

开发这种管理系统,最大的心得是先想清楚业务规则再动手写代码。权限、状态、并发、时间格式这些细节,看起来不显眼,却是系统能不能稳定运行的关键。技术只是工具,真正的难度在于把“状态流转”和“数据一致性”这两件事做对。希望这篇复盘能帮你少走点弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询