如果你接手过一个健康管理系统的需求,应该能体会到这个领域最尴尬的地方:业务看起来很简单,不就是记录血压、血糖、心率、体重,再展示几张趋势图吗?可真要落地的时候,你会发现用户管理、异常预警、历史数据对比、定时提醒、权限控制这些东西全都冒出来了。我去年用SpringBoot和Vue.js完整做了一个健康管理系统,前后端分离,从零开始搭,中间踩了不少坑,也沉淀了一些能直接复用的设计思路。这篇文章就把整个系统的设计与实现过程拆开讲一遍,从需求边界、技术选型到数据库设计、前后端落地细节、部署联调都会覆盖,适合正在做类似课程设计、毕业设计或者企业级健康类产品的同学参考。
先说下这个项目的核心定位:不是只做一个"数据填报+表格展示"的Demo,而是要支撑真实使用场景——用户每天记录自己的健康指标,系统能自动判断指标是否异常,能给用户推送健康建议,还能按周/月生成健康报告。技术上选了SpringBoot作为后端基础框架,用Vue.js做前端单页应用,数据库用MySQL,鉴权用JWT,可视化用ECharts。整套方案不重,但覆盖了一个业务系统最常见的完整链路:登录认证、业务数据管理、规则判断、定时任务、数据可视化、打包部署。
1. 健康管理系统到底在管什么:需求边界与功能规划
很多新手拿到"健康管理系统"这个题目,第一反应就是把体检报告里的所有指标都塞进系统里,什么胆固醇、尿酸、肝功能全上。这种"大而全"的思路往往会把项目拖垮,因为数据维度越多,录入成本越高,用户根本坚持不下来。我的经验是,健康管理系统的核心场景就四个:记录、监测、提醒、报告。
1.1 别一上来就画大而全的架子
系统设计的第一步不是建表,而是先定义清楚"谁在用、每天做什么"。我当时梳理出的用户角色有三种:普通用户、健康管理师(或者叫健康顾问)、系统管理员。
普通用户的核心诉求是"快速记录身体数据 + 看懂自己最近状态"。健康管理师则要看用户列表、异常指标、给出干预建议。管理员负责账号配置、基础数据字典维护(比如常见疾病类型、药品类型、健康指标阈值)。
在这个基础上,功能模块就收敛出来了:
- 用户注册与登录(手机号或邮箱,JWT鉴权)
- 个人健康档案管理(基本信息、既往病史、过敏史、家族史)
- 体征数据录入与维护(血压、血糖、心率、体重、BMI)
- 异常指标预警规则配置(高血压阈值、低血糖阈值等)
- 健康趋势可视化(日/周/月维度的折线图与统计)
- 健康报告生成(月度总结、异常统计)
- 定时提醒(比如每天早晚提醒测量血压)
如果还有富余,可以加一个"健康知识库"或者"膳食建议"模块,但核心是前面这些。我见过不少同学在PPT里列了十几个模块,结果答辩时核心的体征数据逻辑都说不清楚。建议第一版宁可做得窄一点,也要做透。
1.2 从用户故事倒推功能清单
我习惯用"用户故事"来约束功能边界,比直接列功能清单更清晰。举个例子:
- 作为一个高血压用户,我希望每次输入血压后能立刻知道是否偏高,以便我及时调整饮食或用药。
- 作为一个健康管理师,我希望看到某个用户近30天的血压波动趋势,以便判断干预方案是否有效。
- 作为一个普通用户,我希望系统能按时提醒我测血糖,避免工作太忙忘记。
每个用户故事背后都对应一组接口和表结构。比如"输入血压后立刻判断是否偏高",这就不只是增删改查,还需要一个"健康评估服务",它接收用户的血压值、年龄、性别、既往病史,返回"正常/偏高/偏低"状态和建议文案。这个服务在后端实现起来不复杂,但把业务逻辑从Controller里拆出来,后面扩展其他指标(比如血糖、心率)时才不会乱。
所以第一部分总结就一句话:需求分析阶段要克制,把主语(角色)、动作(记录/查看/提醒)、结果(判断/建议/报告)想清楚,再动手写代码。
2. 技术选型背后的取舍:为什么是SpringBoot + Vue.js,而不是其他
技术选型这事儿,没有绝对的对错,但在"快速交付、文档多、社区成熟"这个维度上,SpringBoot加Vue.js的组合几乎是当前国内中小型系统的默认答案。不是说SpringCloud或React不行,而是这个项目体量下,这两者能让你把精力集中在业务本身,而不是折腾框架配置。
2.1 后端选型:SpringBoot怎么用才不浪费
SpringBoot最大的价值是自动配置和约定大于配置。你引入一个spring-boot-starter-web,内嵌Tomcat就起来了;引入spring-boot-starter-data-jpa或MyBatis-Plus,数据访问层的封装也到位了。这个项目我选的是SpringBoot 2.7.x版本,配合MyBatis-Plus,理由是:
- MyBatis-Plus的
BaseMapper可以直接提供单表CRUD,省去大量XML; - 分页插件
PaginationInnerInterceptor对列表查询支持很好; - 代码生成器可以一键生成entity、mapper、service、controller,在项目初期能省不少事。
但要注意,SpringBoot版本不是越高越好。我自己就遇到过"SpringBoot版本太高"导致的坑:高版本(比如3.x)要求JDK17,而很多学校机房或服务器还是JDK8,另外一些老的第三方starter还没适配,依赖冲突一堆。所以如果你没有特别需求,老老实实用2.7.x加JDK8,兼容性最稳。
2.2 前端选型:Vue.js配合Element Plus的开发节奏
Vue.js在前端领域一直以"渐进式"著称,你可以只把它当作模板引擎用在服务端渲染页面里,也可以用Vue CLI或Vite搭建一个完整的单页应用。在前后端分离的架构下,我是这么用的:
- 脚手架用Vue 3 + Vite,比Vue 2 + Webpack的启动速度快很多;
- UI组件库用Element Plus,表格、表单、弹窗、日期选择器这些现成的,和后台管理系统高度匹配;
- 状态管理用Pinia(Vue 3推荐),比Vuex写法更简洁;
- 路由用Vue Router 4,配合导航守卫做登录拦截。
开发效率非常重要。比如Element Plus的el-form自带校验规则,配合el-table和el-pagination,一个带搜索、分页、新增、编辑、删除的页面基本半小时就能搭出来。如果你要画健康趋势图,再加上ECharts的line图就很顺畅了。
2.3 数据库与中间件选型
数据库我用的MySQL 8.0,字符集utf8mb4。为什么不选PostgreSQL或MongoDB?不是它们不好,而是MySQL在这个体量的项目里足够成熟,同学的认知度也高。
定时提醒和健康报告生成用到了SpringBoot的@Scheduled。这个在单体应用里够用,不需要上消息队列。但如果后续提醒量很大,可以考虑用xxl-job或Quartz集群,再配合Redis做分布式锁。第一版没这个必要。
2.4 工程结构规划
很多人搭建SpringBoot项目时喜欢把controller、service、mapper一层层堆在一起,包名五花八门。我推荐按"业务模块"分包,而不是按"技术层"分包。比如:
com.health ├── common // 通用返回结果、异常处理、工具类 ├── config // 全局配置(跨域、拦截器、MyBatisPlus配置) ├── security // JWT鉴权、登录过滤器 ├── module │ ├── user // 用户模块:controller/service/mapper/entity │ ├── record // 体征数据模块 │ ├── report // 健康报告模块 │ └── health // 健康评估模块这样做的好处是,每个模块是高内聚的,改动一个业务点不会牵连到其他无关代码。前端Vue工程里我也习惯按模块分目录:views下放dashboard、record、report等,api目录下按模块放axios请求封装。这个结构在项目变大后尤其好维护。
3. 数据库设计与核心模块实现
数据库设计是整个系统最见功力的地方。健康管理系统涉及用户隐私和大量时间序列数据,如果表结构设计不好,后面写统计SQL时就知道有多痛苦了。
3.1 用户与健康档案表设计
用户表和健康档案表我分成了两张,原因是健康档案的属性比较多,而且并不是每个用户都立刻填写所有字段,分开可以避免用户表过于庞大。
用户表(sys_user)核心字段:
id、username、password(BCrypt加密)phone、emailavatarrole(1普通用户,2管理师,3管理员)create_time、update_timestatus(1正常,0禁用)
健康档案表(health_profile)核心字段:
id、user_id(关联用户)gender、birthday、heightmedical_history(既往病史,JSON或逗号分隔字符串)allergy_history(过敏史)family_historyblood_type
这里要注意:height字段存的是厘米整数,比如175,而不是1.75米,避免浮点数比较误差。birthday建议直接用date类型,后面计算年龄时再用SQL的TIMESTAMPDIFF或Java算,不要在数据库里存年龄,因为年龄会变化。
3.2 体征数据表设计要点
体征数据是整个系统的核心,我建议设计成一张"宽表",而不是一张"多态表"。所谓多态表就是record_type加record_value,虽然灵活,但每行数据含义不确定,统计血压均值和血糖均值时SQL会非常难写。
我的设计是分一张主表health_record加几个明细字段,或者直接按指标类型分表。因为血压、血糖、心率、体重它们的字段完全不同。分表虽然多几张,但查询逻辑清晰:
blood_pressure_record:id、user_id、systolic_pressure(收缩压)、diastolic_pressure(舒张压)、pulse、measured_timeblood_glucose_record:id、user_id、glucose_value、measure_type(空腹/餐后)、measured_timeheart_rate_record:id、user_id、heart_rate、measured_timeweight_record:id、user_id、weight、height、bmi、measured_time
如果你不想分太多表,也可以设计成一张主表存公共字段,然后用detail_json存扩展数据。但我个人不建议,因为JSON字段没法走索引,而且统计时还要解析,性能差。
时间字段统一用datetime,并且加索引。因为健康系统的核心查询是"某个用户某段时间的指标记录",user_id和measured_time联合索引几乎是必须的:
ALTER TABLE health_record ADD INDEX idx_user_time (user_id, measured_time);3.3 统计分析表的冗余设计
健康报告需要统计用户某个月的平均血压、平均血糖、异常次数等。这些数据如果每次都实时从明细表聚合,当数据量大了以后会很慢,而且聚合逻辑分散在SQL里很难维护。所以我设计了一张health_monthly_summary表,按月冗余统计:
id、user_idstat_month(如2025-04)avg_systolic、avg_diastolic、avg_glucose、avg_heart_rate、max_weight、min_weighthigh_pressure_count、low_pressure_count、high_glucose_countupdate_time
这张表由定时任务在每月1号凌晨计算上个月的数据,也可以在用户每次录入数据时用事务同步更新,或者通过增量计算。为了让报告页面响应快,牺牲一点实时性是完全值得的。
另外一个细节:BMI字段很容易被忽视。不少系统在录入体重时让用户填一个BMI,但其实BMI完全可以通过weight / (height/100)^2算出来。数据录入接口应该由后端根据身高和体重自动计算,避免用户乱填。
4. SpringBoot后端落地细节
后端部分我重点讲几个容易出错但决定项目质量的地方:项目初始化、JWT鉴权、业务参数校验、健康评估规则。
4.1 项目初始化与依赖配置
创建一个SpringBoot项目,推荐使用Spring Initializr,然后手动往pom.xml里加依赖。这里有个小技巧:不要只加spring-boot-starter-web,还要提前加好下面几个,不然后面补依赖容易版本冲突:
spring-boot-starter-validationmybatis-plus-boot-starter(3.5.x)mysql-connector-javajjwt-api、jjwt-impl、jjwt-jacksonlombokspring-boot-starter-data-redis(可选,做token黑名单)commons-lang3或hutool-all(工具类)
application.yml里的数据源配置有一处容易踩坑:MySQL8的驱动类已经从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver,URL里还要加useSSL=false&serverTimezone=Asia/Shanghai,否则会报时区错误。
4.2 JWT登录鉴权的完整实现
登录逻辑不复杂:用户提交用户名密码,后端查库,用BCryptPasswordEncoder.matches()校验密码,通过后生成JWT返回给前端。JWT的生成我用的是jjwt库,核心代码如下:
String token = Jwts.builder() .setSubject(userId.toString()) .claim("role", user.getRole()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();这里有一个关键点:secretKey要放到配置文件里,不要写在代码中。HS256签名是对称加密,密钥一旦泄露任何人都能伪造token。生产环境建议用32字节以上的密钥。
有了token之后,自定义一个JwtAuthenticationFilter,在OncePerRequestFilter里解析请求头Authorization中的token,把用户信息放到SecurityContextHolder中。这个过滤器要注册到Spring Security的过滤器链里(如果你用了Spring Security),或者你只用拦截器也行。我第一版没接Spring Security,只用了一个简单的HandlerInterceptor来做登录拦截,效果完全够用。
接口鉴权建议按角色控制:普通用户只能访问自己的数据,管理师可以访问分配给自己的用户数据,管理员可以访问所有接口。在Controller方法上加自定义注解@RequireRole("admin"),然后通过拦截器判断即可,不需要把Spring Security的全套权限模型都搬进来。
4.3 体征数据录入的校验与计算
体征数据录入时最容易出现脏数据,比如收缩压填写成80(人身意外了),血糖填成1000。所以后端一定要做两层校验:
第一层用JSR 303的@Range注解。比如:
@NotNull @Range(min = 50, max = 260, message = "收缩压范围60-260mmHg") private Integer systolicPressure;第二层是业务校验,比如判断血压值是否合理组合:收缩压必须大于舒张压,否则提示"收缩压不能低于舒张压"。另外录入体重时自动计算BMI:
double heightInMeter = height / 100.0; double bmi = weight / (heightInMeter * heightInMeter);计算公式不复杂,但一定要把单位统一。我见过有同学数据库里身高存175,前端传值传成1.75,导致BMI算得离谱,排查了很久才发现是单位问题。
4.4 健康评估与异常预警规则引擎
这是整个系统最体现"健康管理"价值的部分,别把它做成死板的if-else。虽然第一版用if-else也没错,但更好的做法是把规则配到数据库里,做成可配置的。
我在数据库里建了一张indicator_threshold表:
indicator_code(如sys、dia、glu)gender、age_min、age_maxlevel_normal_min、level_normal_maxwarning_message
健康评估服务根据用户的性别年龄,动态查询对应的阈值,再判断当前实测值落在哪个区间。这样如果要调整"高血压"的判定标准,只需要改数据库,不用改代码重新发布。
对于异常数据,可以做一个health_alert表,记录用户、异常类型、异常值、产生时间、是否已读。下次登录时用户就能看到未读的健康预警。预警消息可以做成强提醒,也可以只在首页展示,考虑到做定时推送需要额外接入短信或微信推送,我建议第一版在系统内信和邮件提醒中选一个,先跑通链路。
另外,SpringBoot的定时任务@Scheduled很适合做"每日健康提示"。比如每天早上8点给有高血压记录的用户生成一条"记得按时测量血压并清淡饮食"的提醒。注意,@Scheduled默认是单线程的,多个任务会排队执行,如果任务耗时较长,可以自定义TaskScheduler线程池。
5. Vue.js前端实现要点
前端这块,我觉得比后端更容易翻车的地方在于Vue项目的组织、请求封装和路由守卫。只要把这三个基础打好,后面页面就是堆组件的事。
5.1 项目脚手架与目录组织
我用的是Vite创建Vue3项目:
npm create vite@latest health-web -- --template vue创建完成后加入Element Plus和ECharts:
npm install element-plus npm install echarts npm install axios npm install pinia npm install vue-router@4目录结构如下:
src ├── api │ ├── request.js // axios实例封装 │ ├── user.js // 用户模块接口 │ └── record.js // 体征数据接口 ├── router │ └── index.js // 路由配置与守卫 ├── stores │ └── user.js // 登录状态、用户信息 ├── views │ ├── Login.vue │ ├── Dashboard.vue // 数据概览 │ ├── RecordEdit.vue // 数据录入 │ └── TrendChart.vue // 趋势图 ├── components // 通用组件 └── utils有一个容易被忽视的点:Vue Router的history模式和hash模式。刚开发的调试阶段用createWebHashHistory最省心,刷新页面不会404;打包部署到SpringBoot的静态资源目录时,如果你没有配置转发,也建议用hash模式,否则直接访问/dashboard会报404。不过hash模式对SEO不友好,但后台系统根本无所谓。
5.2 封装axios请求与路由守卫
axios如果不封装,每个页面都在写一遍axios.get('http://localhost:8080/api/...'),不仅代码冗余,token也没法统一处理。我在request.js里做了这些事:
- 创建axios实例,设置
baseURL为/api; - 请求拦截器中从Pinia里取出token,放到
Authorization头; - 响应拦截器中,如果HTTP状态码是401,清空本地身份信息并跳转登录页;
- 统一处理后端返回的
code != 200的业务错误,用ElMessage弹提示。
路由守卫的写法也很固定:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next('/login'); } else { next(); } });注意,如果token存在但已过期,前端无法直接判断,需要后端接口返回401后,在响应拦截器里执行退出登录。
5.3 数据可视化:ECharts展示健康趋势
健康管理系统最直观的展示就是折线图。ECharts在Vue3中的用法很简单,但要避免每次都初始化图表导致内存泄漏。我的做法是:在组件里用ref获取DOM,在onMounted里初始化,在onBeforeUnmount里chart.dispose()。
拿血压趋势图来说,你从后端拿到的数据可能是:
[ { "date": "2025-04-01", "systolic": 135, "diastolic": 85 }, { "date": "2025-04-02", "systolic": 128, "diastolic": 82 } ]前端需要把它拆成两个series:systolic和diastolic。这里有一个极其实用的细节:设置yAxis.min和max时不要用固定值,建议动态计算,比如min = Math.min(...systolicArray) - 10,否则数据波动不大时折线会挤在某一段,看不出变化趋势。
用ECharts自带的dataZoom组件,可以让图表支持缩放区间,对看一个月的数据很有帮助。还可以在tooltip里加上预警线,比如收缩压超过140的区间用不同颜色标记,这样用户一眼就能看出危险时段。
5.4 表单校验与交互细节
录入体征数据时,前端表单校验不能只靠后端。Element Plus的el-form的rules写法很成熟:
const rules = { systolicPressure: [ { required: true, message: '请输入收缩压' }, { pattern: /^(1[0-9]{2}|[1-9][0-9]|50|2[0-5][0-9])$/, message: '请输入合理的收缩压' } ] };还有一个交互细节:血压录入表单中,收缩压和舒张压是并排的两个输入框,很多用户会把两个值填反。你可以用el-tooltip在表单项旁边加一个说明,或者在输入框失去焦点时自动判断:如果收缩压小于舒张压,立即提示"收缩压不能低于舒张压"。这个小交互看似简单,但能极大减少脏数据。
另外,"保存并继续录入"是一个很贴心的功能。健康管理系统的用户往往是每天录入,如果把流程设计成"保存成功后要重新点击菜单进入录入页",会让人很烦躁。保存成功后把表单清空,输入焦点自动落到第一个字段,这个体验会被用户记在心里。
6. 前后端联调与打包部署
联调阶段是前后端矛盾最多的时候,主要就是接口地址、跨域、参数格式不一致。我把整个流程从开发机到服务器走一遍。
6.1 开发环境的跨域处理
前后端分离开发时,前端端口是5173(Vite),后端是8080,直接请求接口会被浏览器同源策略拦截。解决方式有两种。
第一种是后端配置CORS。SpringBoot里写一个配置类:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("http://localhost:5173"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); // 注意:setAllowCredentials(true)时AllowedOrigin不能是"*" UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }第二种是前端用Vite代理。在vite.config.js里:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }我推荐第二种方式,因为开发环境走代理,前端请求不用写绝对地址,以后部署到服务器只要保证同源即可。但要注意:代理配置只在开发模式生效,打包后还要看部署方式。
6.2 Vue打包后塞进SpringBoot的两种方式
这是很多同学纠结的问题。其实就两种方式:
方式一:独立部署。把前端打包成静态文件,放到Nginx里,Nginx反向代理到SpringBoot。生产环境推荐这种,前端静态资源由Nginx处理,后端只处理API,可以单独扩容。
方式二:把前端打包后的dist目录拷贝到SpringBoot的src/main/resources/static下,然后启动SpringBoot时,同一个端口同时提供页面和API。这种方式适合小型项目或个人服务器,省事,不用配Nginx。
如果你想用方式二,要注意两点:
- 如果用了Vue Router的
history模式,SpringBoot要配置一个forward,让非/api的路径都转发到index.html,否则刷新页面就404。简单做法:
@Controller public class PageForwardController { @RequestMapping(value = {"/", "/index", "/dashboard", "/record", "/report"}) public String index() { return "forward:/index.html"; } }- 在
application.yml里确认静态资源路径,默认就有classpath:/static/,你只要把打包后的文件放进去就行。
6.3 服务器部署:宝塔面板+Docker
服务器部署我用的宝塔面板加Docker。宝塔面板对新手非常友好,主要是可视化的文件管理和Nginx配置。Docker则能保证环境一致性,避免"在我电脑上能跑"的尴尬。
Docker部署SpringBoot的标准流程:
- 用Maven打包成jar:
mvn clean package -DskipTests- 写Dockerfile:
FROM openjdk:8-jre-alpine WORKDIR /app COPY target/health-system.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]- 在服务器上构建并运行:
docker build -t health-server . docker run -d -p 8080:8080 --name health-server health-server前端的话,把dist目录放到宝塔的网站目录,新建一个静态站点,配置反向代理/api到http://127.0.0.1:8080即可。注意,服务器上的数据库连接别写localhost,如果SpringBoot容器和MySQL容器不是同一个网络环境,要写MySQL容器的服务名或宿主机的内网IP。我踩过这个坑:直接把localhost写到配置文件里,Docker容器启动后报连不上数据库,因为容器里的localhost指的是容器自身,不是宿主机。
6.4 环境版本匹配问题
结合很多人问到的"SpringBoot版本太高"问题,我多说一句。SpringBoot 3.x技术上没问题,问题是生态兼容。你如果用的MyBatis-Plus、某第三方支付SDK、或者是老的JDK环境,都有可能因为版本不匹配而编译失败。我的建议是:
- 如果JDK是8,SpringBoot用2.x;
- 如果JDK是17+,SpringBoot用3.x,但某些组件要升级到配套版本;
- 前端Node版本也要注意,Vite 5要求Node 18以上,如果你服务器要构建前端,Node版本太低会报错。
部署时还有一个细节:JVM参数。SpringBoot项目如果只是小项目,默认堆内存足够,但如果服务器内存只有2G,建议显式设置:
java -jar -Xms512m -Xmx512m health-system.jar避免JVM自动撑大内存,把服务器拖垮。
7. 常见问题与排查实录
就算前面都做对了,项目在运行过程中还是会遇到各种奇奇怪怪的问题。我这里整理一份排查速查表,都是这个项目里真实遇到的。
7.1 高频问题速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
前端请求/api报404 | Vite代理没生效或后端没有/api前缀 | 检查vite.config.js代理配置;后端context-path设成/api |
| 登录接口返回401但业务正常 | JWT过期或token解析失败 | 检查请求头是否带了Authorization;检查密钥是否一致 |
| 定时任务没执行 | 忘了在启动类加@EnableScheduling | 加上这个注解 |
| MyBatis-Plus分页不生效 | 没配置分页插件 | 添加PaginationInnerInterceptor |
| 数据库中文乱码 | 连接URL缺characterEncoding=utf8 | URL加上useUnicode=true&characterEncoding=utf8 |
| Vue打包后首页白屏 | 资源路径是绝对路径 | vite.config.js里设置base: './' |
| 跨域配置后依然报跨域 | 因为用了*又开了allowCredentials | 用具体域名或关闭Credentials |
前端请求正常但后端报Required request body is missing | 前端Content-Type不对或没有序列化 | 用JSON.stringify,headers里设置application/json |
这些坑大多不是技术难度问题,都是经验问题。比如Vite打包后白屏,就是因为默认base是/,部署到子目录时资源找不到。改一下base: './'就解决了。
7.2 我在实际开发中踩过的典型坑
第一个坑是JWT密钥泄密。一开始我图省事,把密钥写死在JwtUtil里,后来发现代码上传到Git仓库后,同事都看得到密钥,这要是做安全评审绝对过不了。改成从application.yml读取后,又把密钥暴露在配置文件里。最后我是通过环境变量注入密钥,代码里只读取${JWT_SECRET}。如果你只是做课程设计,至少要把密钥放到application-prod.yml里,不要放application.yml。
第二个坑是"血压正常范围"的判定。原来我写死了140/90,后来发现老年人、糖尿病患者的血压控制标准不一样,不能一刀切。所以我改成数据库动态配置阈值,还支持按年龄段区分。这个改动在代码层面不大,但业务效果提升明显。
第三个坑是前端同时展示多个图表时很卡。一开始我在Dashboard里挂载了4个ECharts实例,每次切页面都重新请求数据,结果用户打开页面明显卡顿。后来我用了一个"统一加载再渲染"的思路:先并行请求所有图表数据,数据全部到位后再一次性渲染图表,配合v-show而不是v-if,减少组件销毁重建的开销。
7.3 后续可以扩展的方向
做完了这个系统,我还有一个体会:健康管理系统的业务逻辑远比技术复杂。当前系统的预警规则还比较简单,如果要做成商业产品,还可以加入机器学习模型来预测指标趋势、结合用户行为数据做个性化干预。技术上可以引入SpringBoot整合Flink做实时流数据处理(比如实时接收智能手环的数据),也可以把报告导出做成PDF以方便分享。但这些都是"锦上添花",先把基础链路走通比什么都重要。
我个人在实际操作中的体会是:这种系统最适合"小步快跑"的迭代模式,先把一个核心指标(比如血压)从头到脚做完整,再做第二个指标。因为不同指标背后的业务规则、校验逻辑、可视化方式都会不一样。做完血压模块,再去加血糖模块,你会发现后端多了两张表和一个规则配置,前端多个一个Tab,但整体架构完全不用动。
最后再分享一个小技巧:健康管理系统里的时间字段一定要处理好时区。前端传的measured_time如果带时区,后端存到MySQL时如果没有统一规范,会导致用户看到的趋势图错位8小时。最简单的方式是后端统一用yyyy-MM-dd HH:mm:ss按服务器时区接收,前端用dayjs格式化后再传,不要传ISO带时区的字符串。
这个项目从需求到上线,我大概花了三周多的业余时间。回头复盘,最有价值的不是写了多少代码,而是把"健康管理"这个模糊概念翻译成了清晰的数据结构和业务规则。希望这篇文章能帮你少走一些弯路。