这段时间正好在整理一个手头刚收尾的项目就是基于SpringBoot和Vue做的健康打卡与评测系统。做的时候没少踩坑从数据库设计到前后端联调再到最后部署上线每一步都有一堆细节值得拿出来聊聊。尤其是一些只会在真实业务里遇到、文档里根本不会写的问题比如当天重复打卡的并发处理、时区导致的数据错位、前端权限控制和接口数据结构的磨合这些不实际做一遍很难体会。这套系统本身不算复杂核心就是三块业务用户健康打卡、健康状态评测也就是综合判定以及后台的管理与统计。技术栈用的是SpringBootMyBatisMySQL作为后端前端Vue全家桶。正好借这个机会把这个项目的完整设计思路、核心实现和排坑过程拆开讲讲给准备做类似信息采集、打卡上报、状态评估系统的同学一个真实可靠的参考。1. 系统全景与设计思路1.1 需求拆解这到底是个什么系统先把这个系统的业务边界说清楚。疫情打卡健康评测系统本质就是一个带规则引擎的信息采集和状态评估系统。用户端要完成的是每日健康信息上报体温、症状、行程接触情况然后由系统根据预设规则自动生成一个健康状态等级。管理端要完成的是用户管理、打卡数据审核查看、健康状态的统计分析、异常状态的预警处理。一开始我和几位同事讨论的时候很多人想复杂了觉得要做成类似医疗系统那样复杂的东西。后面我们把需求收敛成几个核心问题健康状态怎么定义和区分需要一个从输入到输出的规则引擎输入是体温数值、症状选项、接触史选项输出是健康等级。打卡怎么防止漏打、重复打、乱打需要有打卡窗口期、防重复提交机制和数据校验。管理端看什么核心是“今天多少人打卡了”和“这些人的健康状态分布”以及异常人员明细。数据怎么沉淀日报、周报、趋势分析这些都需要历史数据支撑。把这些问题想清楚系统的边界就出来了。1.2 技术选型为什么是SpringBootVue而不是别的技术选型这块老实说SpringBootVue已经是这个赛道最主流的搭配了但主流不等于没有讲究空间我还是来说说选它的底层逻辑。后端用SpringBoot理由很直接生态成熟、上手曲线平缓、社区资料多。更重要的是它和MyBatis配合非常顺滑中小型业务系统用这个组合做快速交付效率是最高的。SpringBoot的自动配置帮省掉了大量繁琐的XML配置让团队可以专心写业务代码这对一个需要快速上线的系统来说是决定性的优势。前端选Vue而不是React核心考量是团队技术栈的匹配度和构建效率。Vue3的Composition API配合Vite开发服务器在开发体验上非常舒服热更新基本是毫秒级的。加上Element Plus组件库后台管理类页面的排版布局几乎不用从零写CSS。如果换成React那一套虽然也不是不行但在同样的交付周期内要完成的东西会多不少。MyBatis在持久层的定位则是“半自动”的灵活性。相比JPA的全自动映射MyBatis把SQL控制权完全还给开发者。对于这种有大量统计查询的业务场景——比如按日期分组统计打卡率、按健康等级聚合人数——手写SQL比JPQL或者Criteria API要直观得多也更容易优化执行计划。后期排查慢查询的时候把SQL拉出来直接EXPLAIN就行效率高太多了。1.3 系统整体架构设计系统采用前后端完全分离的架构前端单独部署通过HTTP接口与后端通信。整体数据流向大致是用户操作前端页面 → 前端调用后端REST接口 → 后端Controller接收请求并校验参数 → 调用Service层处理业务逻辑 → 通过MyBatis访问MySQL数据库 → 结果逆向返回前端渲染。权限模型采用经典的RBAC设计预设了三种角色普通用户、部门管理员、系统管理员。普通用户只能看到自己的打卡页面和记录部门管理员可以查看本部门数据系统管理员有全部权限。接口风格统一走RESTful返回结构用统一Result包装包含状态码、消息和数据三个字段。这样做的好处是前端处理逻辑统一错误处理也简洁不用面对乱七八糟的返回结构去写一堆if-else。2. 数据库设计打卡与健康评测的地基数据库是这类系统最要命的部分。疫情打卡系统表面看就是一张打卡表实际上牵扯到用户信息、打卡数据、评测规则、异常记录、系统通知等多个维度设计不到位后面写代码会非常难受。2.1 核心表结构设计整个系统我在最开始设计的时候规划了6张核心表用户表、打卡记录表、健康评测规则表、异常记录表、通知公告表、操作日志表。用户表核心字段包括用户ID、用户名、密码BCrypt加密存储、真实姓名、部门ID、手机号、角色类型、账户状态。这里有个设计细节值得说明——为什么用户表要把部门和角色单独关联出来而不直接存字符串因为后面统计“某个部门今日打卡率”的时候需要根据部门维度做关联查询。如果只是存一个“部门名称”字符串一旦部门改名历史数据的部门信息就全乱了。所以部门信息单独建表维护用户表存部门ID作为外键关联。同理角色也采用关联表方便后期扩展权限粒度。打卡记录表的核心字段设计上我没有采用传统的一张“大宽表”存所有打卡信息而是分成两部分主表存打卡基础信息用户ID、打卡日期、打卡时间、当日体温、健康等级、数据提交IP明细表存具体的症状选项和接触史选项用户ID、记录ID、选项编码、选项值。分表的原因在于主表需要高频查询和统计字段太多会拖慢查询效率明细表的字段是可扩展的如果后期要在表单里加一个“是否接种疫苗”的选项只要在选项字典里加数据就行不需要改表结构。评测规则表的设计则是规则引擎的配置中心。字段包括规则编码、规则名称、适用条件JSON格式存储的条件表达式、判定结果健康等级、优先级、启停状态、备注。举个例子一条规则可以是当体温超过37.3且症状项包含“咳嗽”时判定结果为“关注”。这些规则如果不入库硬编码在Java逻辑里后期调整规则就只能发版这在业务上是无法接受的。所以我把规则做成配置化的管理员可以直接在管理后台改规则内容不用动一行代码。2.2 关键字段选型与设计细节有几个字段的选型我想特别说一下这些都是实际项目里踩过坑之后才确定的方案。时间字段统一用datetime而不是timestamp。这是一个很多人忽略的坑。timestamp的范围到2038年就会溢出而且MySQL的timestamp会自动处理时区转换如果服务器时区设置不对就会出现数据错乱的情况。datetime则不会有时区问题存的是什么取出来的就是什么。配合Java端统一使用LocalDateTime可以彻底避开时区带来的隐性问题。体温字段我用decimal(4,1)来存。为什么不直接用float或者double因为浮点数在MySQL里很容易产生精度误差比如35.8在比较的时候可能会变成35.79999...导致条件判断出错。decimal精确存储比较运算也不会出问题。4,1的意义是最大支持999.9精度到小数点后一位对于人体体温数据完全够用。状态类字段我都用tinyint存数字编码而不是直接用字符串存中文描述也没有直接用枚举值。原因很简单在数据库层面用数字做条件查询效率更高而且改动描述文案不用改数据库。比如健康等级字段我在代码里定义一个枚举类1代表正常、2代表关注、3代表异常显示给用户看的时候再映射成文案。这样既保证了数据库层面的查询性能又保留了代码层面的可读性。索引设计方面打卡记录表的核心索引是(用户ID, 打卡日期)的联合唯一索引。这个索引是我在设计阶段就确定的因为它直接支撑了两个核心需求一是防止同一用户在同一天重复提交打卡记录数据库层面就把这个约束卡死了二是查询用户某段日期范围内的打卡记录时这个索引能极大地缩小扫描范围。2.3 防重复打卡的数据库级解决方案防重复打卡这个问题我第一版是在代码层面用查询判断来实现的。用户提交打卡时先去查一下有没有当天的记录查到了就提示“今日已打卡”。这个方案在测试环境怎么测都没问题一到上线后高峰期就出问题了。问题出在并发上。两个请求几乎同时进来都先执行了查询操作发现都没有当天记录然后都继续执行插入操作。这就导致同一个人出现了两条当日打卡记录。虽然代码逻辑看着没问题但实际并发情况下就出现了竞态条件。后面我把解决思路下沉到了数据库层面给打卡记录表加上了(用户ID, 打卡日期)的联合唯一索引。一旦有重复插入数据库直接报Duplicate entry错误应用层捕获到这个异常后转成业务提示“今日已打卡”。这个方案从架构上就杜绝了并发重复提交的可能而不是在应用层面疲于应对。这个设计思路其实适用于所有需要“防重”的业务场景比如订单号的唯一约束、领取优惠券的用户券模板组合约束。能用数据库约束解决的并发问题就不要依赖应用层代码。数据库的ACID特性就是用来保证数据一致性的这是它的本职工作。3. 后端核心实现打卡与评测的业务逻辑后端这块是整个系统的大脑承载了用户认证、打卡数据校验、健康评测、统计查询等核心逻辑。我将按照实际开发顺序从工程搭建到核心功能实现逐个来讲。3.1 工程搭建与基础设施工程结构我按常见的分层架构来组织。controller包放接口层service包放业务逻辑mapper包放MyBatis的数据访问接口entity包放数据库实体类common包放通用工具类和统一返回结果。创业初期容易犯的错是在Controller里堆业务逻辑导致Controller几百行Service层形同虚设。我的原则是Controller只做三件事接收参数、调用Service、返回结果。所有业务判断、事务控制、数据组装都在Service层完成。这样做的好处是接口层很薄逻辑清晰也方便写单元测试。统一返回结果的设计我用了一个泛型类Result 包含code、message、data三个字段。所有接口的返回值都用这个类包装前端根据code判断业务是否成功0表示成功非0的code对照错误码表有明确的业务含义。比如10001是参数错误、10002是未登录、10003是打卡已提交、20001是登录失败等。错误码表维护在系统字典里前后端约定好就按这个来。登录认证这块我用了Sa-Token框架。选它而不是Spring Security核心原因是Sa-Token足够轻量API设计也更符合国内开发者的使用习惯。登录成功后服务端会生成一个token返回给前端前端存到localStorage里后续每次请求在请求头里带上这个token后端通过拦截器统一校验。3.2 健康打卡接口的实现细节打卡接口是整个系统最核心的接口。前端提交的数据包括体温、症状选项列表、接触史选项列表、补充说明文本。这些数据在后端经过三道校验关卡才会落库。第一道是登录状态校验通过拦截器统一处理没有合法token的请求直接在网关层就被拦截了根本到不了Controller。第二道是参数合法性校验体温的值域校验范围必须在34.0到42.0之间、选项编码是否存在、说明文本长度是否超限等用Spring的Validated注解配合自定义校验器实现。第三道是业务校验检查当天是否已经打卡、打卡时间是否在允许窗口期内。这里我把打卡窗口期做成配置项放在系统参数表里。默认是每天早上6点到晚上22点。为什么要做成配置而不是写死因为不同阶段的管控要求是动态调整的比如某个时期要求必须中午12点前完成打卡如果写死在代码里改这个时间就得上线发版。配置化之后管理员后台改一下参数前端刷新就能生效。关于打卡数据的落库流程我专门用了事务处理。因为一次打卡要同时写入主表和明细表两张表只要有一张表写入失败整个事务就要回滚。这里我用Transactional(rollbackFor Exception.class)注解即使RuntimeException之外的异常也会触发回滚机制。打卡成功之后系统会紧接着触发健康评测逻辑对本次提交的打卡数据做规则匹配生成健康等级。评测结果更新到打卡记录主表的健康等级字段里同时如果评测结果是“异常”会同步生成一条异常记录推送到管理端待处理列表。3.3 健康评测规则引擎的实现思路健康评测规则引擎是这套系统的技术亮点也是业务的核心逻辑之一。整个评测过程可以用一个流程图来描述获取打卡明细数据 → 依次匹配评测规则 → 高优先级命中则直接返回该规则设定的健康等级 → 全部规则未命中则返回默认的正常等级。规则匹配部分我设计成解析JSON格式条件的方式。规则表里那条记录的适用条件字段存的是一个JSON数组每个条件项包含字段名、比较操作符和阈值。举个例子{field: temperature, operator: gt, value: 37.3}代码层解析这段JSON获取本次打卡数据中的temperature字段值用“大于”操作符和37.3做比较运算返回true或false。多条条件的规则用逻辑关系连接同样通过规则表的逻辑运算符字段配置。是按照“与”还是“或”来匹配由配置决定。比如体温超过37.3且存在咳嗽症状这是“与”的关系体温超过38.5或存在呼吸困难症状这是“或”的关系。规则的优先级数字越小代表优先级越高。比如可能同时存在“体温大于等于38.5判定异常”和“体温大于37.3且咳嗽判定关注”两条规则如果用户体温38.6且有咳嗽两条规则都能命中。这种情况下优先级在前面的规则说了算整个评测过程采用最大匹配原则——匹配所有规则中优先级最高的一条为最终结果。这个引擎设计的最大的好处是规则调整不需要代码上线管理员通过后台维护规则表实时生效。这对疫情这类业务来说几乎是刚需因为判定标准谁也无法预判明天会不会变规则配置化让系统有了极高的响应速度。3.4 MyBatis实战动态SQL与统计查询MyBatis在这套系统里的应用场景丰富既有简单的单表CRUD也有复杂的多表关联统计。这里我分享两个使用心得。第一个是动态SQL的合理使用。在管理端的记录筛选功能里筛选条件是可变的可以只看某一天可以按部门筛选可以按健康等级筛选也可以组合筛选。如果为每种情况写一个SQL组合爆炸且维护困难。MyBatis的 标签配合 标签就是为此而生的它在内部会智能处理AND连接符第一个条件前不会多出AND也不需要写“WHERE 11”这种丑陋写法。第二个是统计查询的性能优化。管理端首页有一块统计图表——最近14天每日打卡人数趋势、各健康等级占比、各部门打卡率排行。这个需求的实现需要多次分组聚合查询。在写SQL的时候有几个细节需要注意分组字段先走索引聚合函数扫描的字段越小越好不要在WHERE条件里对索引字段做函数运算比如DATE()套在日期字段上会导致索引失效。SELECT DATE_FORMAT(check_date, %Y-%m-%d) AS date_label, COUNT(*) AS total_count, SUM(CASE WHEN health_level 1 THEN 1 ELSE 0 END) AS normal_count, SUM(CASE WHEN health_level 2 THEN 1 ELSE 0 END) AS attention_count, SUM(CASE WHEN health_level 3 THEN 1 ELSE 0 END) AS abnormal_count FROM health_check_record WHERE check_date DATE_SUB(CURDATE(), INTERVAL 13 DAY) AND check_date CURDATE() INTERVAL 1 DAY GROUP BY DATE_FORMAT(check_date, %Y-%m-%d) ORDER BY date_label DESC这个语句我用了CASE WHEN配合SUM做条件统计一次扫表就能拿到所有等级的数量比写四个子查询效率高得多。DATE_FORMAT格式化输出保证了JS直接可以渲染图表前端拿到的就是现成的标签。索引方面由于check_date本身有索引范围查询可以走索引虽然分组操作会不可避免产生临时表但这个量级的日常数据完全可以接受。-- 部门打卡率统计联动查部门表和打卡记录表 SELECT d.dept_name, COUNT(DISTINCT u.user_id) AS total_users, COUNT(DISTINCT h.id) AS check_count FROM sys_dept d LEFT JOIN sys_user u ON d.dept_id u.dept_id AND u.status 1 LEFT JOIN health_check_record h ON u.user_id h.user_id AND h.check_date CURDATE() GROUP BY d.dept_id, d.dept_name这个查询有个易踩的坑关联打卡记录表时如果不加h.check_date CURDATE()条件会导致一个用户的历史打卡记录也关联出来COUNT(DISTINCT h.id)统计的就是全部历史数据而不是今天的。我把这个日期条件放在LEFT JOIN的ON子句里过滤掉非当天的记录再统计结果才是准确的当天打卡率。4. 前端Vue实现从表单收集到可视化展示4.1 工程初始化与目录结构前端部分我用的是Vue3ViteElement Plus的组合。Vite创建项目的命令很简单几秒钟就能拉起一个基础模板安装依赖后目录结构按模块划分api目录存放所有接口请求封装router目录管理路由和权限控制store目录用Pinia管理全局状态views目录按业务模块拆分页面组件utils目录放公共工具函数。axios的二次封装是前端工程化的第一个关键点。我在utils目录下新建了一个request.js文件主要做了三件事统一在请求头里注入token字段统一处理响应拦截当后端返回的code不等于0时统一提示错误消息当code等于401或token过期时自动跳转到登录页并清除本地登录态。import axios from axios; import { ElMessage } from element-plus; import router from /router; import { getToken, removeToken } from /utils/auth; const request axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }); request.interceptors.request.use(config { const token getToken(); if (token) { config.headers.Authorization token; } return config; }); request.interceptors.response.use( response { const res response.data; if (res.code ! 0) { ElMessage.error(res.message); return Promise.reject(new Error(res.message)); } return res.data; }, error { if (error.response error.response.status 401) { removeToken(); router.push(/login); } ElMessage.error(error.message || 请求失败); return Promise.reject(error); } );这里有个经验点VITE_API_BASE_URL这个环境变量在开发环境和生产环境的值不同。开发时指向本地后端地址生产环境指向服务器后端地址。都是通过项目根目录的.env.development和.env.production文件分别配置的通过环境切换避免在代码里写死地址。4.2 健康打卡页面的交互设计打卡页面不是简单的表单提交我设计了一个更贴合场景的交互流程用户进入页面首先看到的是今日打卡状态总览——如果今天已经打过卡了页面直接展示打卡结果和健康等级不可再次编辑。如果还没打卡则展示打卡表单。表单布局采用分组展示的形式。基础信息组包括体温的输入和当天的日期显示。身体症状组是几个可多选的选项包括发热、咳嗽、乏力、呼吸困难、味觉嗅觉减退等。接触情况组包含是否接触过确诊或疑似病例、最近14天是否去过中高风险地区等选项。每个选项对应一个编码值提交给后端。体温输入组件我做了即时校验前端就限制在34.0到42.0之间超出直接标红不给提交。前端校验和后端校验的关系是前端校验提升用户体验后端校验保证数据安全。两者都做才是完整方案。前端不校验会让用户提交后拿到错误提示体验很糟糕后端不校验则可能存在绕过前端直接调接口的恶意数据。提交成功后的反馈我做了结果页跳转。接口返回评测出来的健康等级和评分明细前端根据等级显示不同的结果卡片正常显示绿色风格关注显示黄色风格异常显示红色风格。结果页还展示评测的依据摘要比如“体温36.5℃ 无异常症状 无风险接触史”等。让用户知道自己为什么被判定为这个等级这是评测系统透明性的关键。4.3 管理后台的数据看板与图表管理端首页我做了四个核心指标卡片和两个趋势图的布局。四个指标分别是今日应打卡人数、今日已打卡人数、今日打卡率、异常人数。这些数据通过一个聚合接口一次性获取避免多次请求造成页面加载卡顿。趋势图用了ECharts的折线图展示最近14天打卡率的走势。ECharts的组件引入我按需引入没有全量加载这样构建体积能优化不少。在图表数据处理上我直接在组件里接收后端返回的统计数据数组映射成两个坐标轴的数据源不需要前端做额外的数据包装。数据表格部分是异常记录的列表展示包括异常人员姓名、部门、异常类型、体温数据、症状描述、打卡时间、处理状态。列表页用了Element Plus的el-table组件和el-pagination分页组件。由于后端接口已经实现了分页查询前端只需要在页码变化时重新请求对应页的数据即可渲染是响应式的。权限控制方面我用了路由守卫结合用户角色实现。在路由配置里给每个页面标记需要的角色类型用户登录后在Pinia里存储角色信息。路由守卫在每次路由跳转前检查用户角色是否包含该页面所需的角色不匹配就重定向到无权限页面。菜单栏也使用角色过滤管理员看到的菜单项和普通用户看到的不一样。5. 开发过程中的坑与排查记录5.1 跨域问题的三种解法本地开发阶段最容易遇到的就是跨域问题。前端运行在5173端口后端运行在8080端口浏览器跨域请求被拦截这是几乎所有前后端分离项目开发者的第一道坎。第一种解法是后端直接开启CORS配置在SpringBoot里写一个配置类实现WebMvcConfigurer接口重写addCorsMappings方法。这个方法最简单但生产环境跨域通常不是通过这种方式解决。第二种解法是前端开发服务器用Vite的proxy代理在vite.config.js里配置server.proxy将/api下的请求转发到后端地址。这个方法开发环境非常爽浏览器的请求都发给同源的前端服务器前端服务器再做转发相当于绕开了浏览器的同源策略。第三种解法是生产环境的Nginx反向代理把/api路径的请求反向代理到后端服务。我实际项目里的做法是开发环境用Vite的proxy代理生产环境用Nginx反向代理后端仅保留允许特定来源的CORS配置作为兜底。5.2 时间处理引发的血案项目做完联调阶段出现过一次打卡日期错乱的问题。用户在晚上23点50分打卡系统显示的打卡日期却变成了第二天。排查了半天发现是后端取日期时用了new Date()然后在前端展示时通过JavaScript的toLocaleDateString()转成了前端所在时区的日期。如果用户在中国UTC8使用服务端却部署在UTC时区的服务器上晚上23点50分在UTC时区已经变成了第二天的凌晨前端再转回UTC8时区日期自然就变了。解决方法是Java后端统一用LocalDate.now()配合系统的默认时区设置取日期前端不自行转换日期字段直接展示后端传来的字符串。同时在Nginx和后端JVM的启动参数里都强制指定为UTC8时区确保整个数据链路的时间基准一致。5.3 MyBatis条件查询的坑开发管理端数据筛选功能时出现过筛选条件失效的问题。用户选择了“只看异常人员”列表却还是返回了全部人员的数据。查看日志发现传入的状态参数是字符串“3”但数据库字段类型是tinyint。MyBatis在进行字符串和整型比较时MySQL做了隐式类型转换本应该走索引的字段因为类型转换导致索引失效反而触发了全表扫描。这在小数据量时表现为“筛选结果多了一条记录”大数据量时则是明显的查询缓慢。定位到是类型问题后我在前端提交参数前明确转成数字类型后端同时做参数类型强校验。这个问题的根因是前后端参数类型不一致必须在接口文档里明确每个字段的类型前后端联调时严格对齐。6. 部署上线与运维要点6.1 前端构建与Nginx配置前端部署我走的是标准的构建发布流程。本地执行npm run buildVite会把所有资源打包到dist目录。dist目录里面的文件就是纯静态资源扔到Nginx的html目录下就能跑。Nginx配置里除了常规的静态文件服务外还有两个关键点需要配置。一个是单页应用的history路由模式需要配置try_files把所有不存在的路径都重定向到index.html否则用户直接访问某个子路由地址时会报404。另一个是API的反向代理配置location /api并指向后端的地址。server { listen 80; server_name your.domain.com; location / { root /var/www/html; index index.html; try_files $uri $uri/ /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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }6.2 后端Jar包部署与守护后端部署方式就是SpringBoot标准的打包体系。执行mvn package打成jar包然后扔到服务器上执行。但直接用java -jar命令启动有一个问题——终端断开连接进程就会挂掉服务器重启也不会自动拉起。我用了systemd来做进程守护。在/etc/systemd/system目录下新建一个服务配置文件写上执行启动的命令、日志输出位置、环境变量等信息。配好后systemctl enable命令设置开机自启systemctl start立即启动。进程意外挂了systemd会自动拉起省心很多。MySQL的备份我写了定时脚本每天凌晨3点用mysqldump备份全量数据库保留最近30天的备份文件。这类系统的数据是一天一量的增量积累历史数据比程序本身还值钱备份必须到位。6.3 哪些地方值得升级与扩展如果这套系统后续要继续演化我梳理了几个方向。人脸识别接入可以解决代替打卡的问题。当前的架构是“账号密码登录→填表打卡”存在一个漏洞风险A用户拿B用户的账号密码登录后替打系统无法识别是本人操作。引入人脸识别校验打卡人的身份把打卡时的自拍照和系统留底照片做比对能从物理上排除代替打卡的可能。规则引擎可以接入更复杂的决策逻辑。目前的规则是“条件组合-优先级判定”的线性结构如果后续需要引入贝叶斯概率模型或者多因子综合评分当前的JSON条件配置方式在可读性和可维护性上就会成为瓶颈。消息通知模块可以接入多通道推送。目前系统里的通知只做了站内信而且仅限于管理端待办提醒。如果要做成完整方案应该扩展短信通道和APP推送通道打卡提醒、异常预警都需要主动触达用户而不仅仅是登录系统后看到消息。写在最后这个项目从开发到上线最大的感受是业务规则变动频繁的系统一定要把规则做成配置而不是代码。健康评测系统最核心的不是CRUD接口写得多么高效而是规则引擎到底灵活不灵活能不能在业务口径调整的时候快速响应。另外一条个人经验前后端分离项目的沟通成本被严重低估了。很多看似技术上的Bug比如日期显示不对、筛选结果不对最终定位到根因都是双方对字段定义的理解不一致。接口文档一定要在动手开发之前写好字段类型、取值范围、时间格式、错误码语义都要逐字对齐。这套健康打卡评测系统技术上没有用了什么高深的东西但把基础组件、设计模式、工程化实践都串联得比较完整。如果你想练手类似的信息采集和状态评估类系统从这套体系着手是个不错的起点。