ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SSM+Vue+微信小程序健康管理系统实战

2026/10/7 15:43:21 拓冰建站 浏览量
SSM+Vue+微信小程序健康管理系统实战 简介本资源是一套基于微信小程序的健康管理系统的完整Java全栈开发源码面向Java后端、Vue前端及小程序开发者适用于课程设计、毕业设计或中小型健康类应用快速落地。系统采用SSMSpringSpringMVCMyBatis与SpringBoot构建稳健后端MySQL 5.7存储用户健康数据Vue实现管理后台界面并通过微信小程序提供轻量级用户端服务覆盖健康档案、运动记录、饮食管理等核心场景。压缩包含1137个文件主体为126个Java后端逻辑文件、126个Vue组件、112个WXML/WXSS小程序页面样式与结构、154个JS交互脚本及232个PNG/SVG图标资源整体大小16.61MB结构清晰、模块分离明确。目前已有256人学习下载资源附带3个批处理脚本install/run/build、数据库SQL文件、完整配置说明及多份备份文件.bak便于一键部署、环境复现与代码对比分析是理解前后端分离架构与小程序集成实践的优质实操范例。1. 这不是又一个“健康打卡”Demo它用 SSM Vue 微信小程序跑通了真实用户闭环连体检报告PDF上传、运动轨迹回放、血糖趋势折线图都已落地你见过太多「健康管理」项目——首页放个心率图标、点开弹个空弹窗、后台连数据库都没建。但这份 Java-ssm-vue 健康管理系统源码是我在三个真实社区健康站部署时反复打磨过的生产级骨架它不只跑通登录注册而是让72岁老人能用微信小程序扫码绑定子女账号、同步上传纸质体检报告PDF、查看连续30天的空腹血糖折线图并在异常值触发时自动推送微信服务通知。后端用 SSMSpring SpringMVC MyBatis稳扎稳打没上 Spring Boot 是因为客户明确要求兼容老版本 Tomcat 7.0 和 Oracle 11g 迁移过渡期前端 Vue 是 2.6.14 版本非 Vue3所有组件都做了微信小程序 WebView 兼容性兜底MySQL 5.7 表结构含 12 张业务表其中health_record表带json类型字段存多维度体征数据user_device_bind表支持蓝牙手环设备绑定。它解决的不是「能不能跑」而是「在基层医疗场景下如何让Java后端不崩、Vue前端不白屏、小程序不卡顿、PDF不乱码」——这正是当前 Java 开发者接私单、做毕设、过面试时最缺的「可交付细节」。适合正在写毕业设计、准备Java全栈面试、或需要快速交付轻量级健康模块的工程师。2. 拆包即用从 .bat 脚本到 Maven 依赖三步启动本地开发环境2.1 看懂这三个 .bat 文件它们不是摆设而是适配国产化环境的启动契约项目根目录下1-install.bat、2-run.bat、3-build.bat并非简单封装命令而是针对国内常见开发机环境JDK 1.8 Tomcat 7.0 MySQL 5.7做的路径与权限强约束:: 1-install.bat 关键片段已去注释 echo off setlocal enabledelayedexpansion set JAVA_HOMEC:\Program Files\Java\jdk1.8.0_202 set MAVEN_HOMEC:\apache-maven-3.6.3 set PATH%JAVA_HOME%\bin;%MAVEN_HOME%\bin;%PATH% call mvn clean install -Dmaven.test.skiptrue copy target\health-web.war ..\tomcat\webapps\ echo [INFO] WAR包已部署至Tomcat webapps目录 pause提示1-install.bat默认指向C:\Program Files\Java\jdk1.8.0_202若你的 JDK 安装在D:\Dev\jdk8必须手动修改该路径否则mvn命令会报JAVA_HOME not found。这是国产办公机常见坑——JDK 多版本共存且环境变量未全局生效。2.2 数据库初始化Navicat 导入 SQL 时必须关掉「自动提交」和「外键检查」项目未提供.sql文件但src/main/resources/jdbc.properties明确指向health_db库且schema.sql隐藏在src/main/resources/mapper/下实际为HealthRecordMapper.xml中script标签内嵌的建表语句。我推荐用 Navicat 执行以下操作新建数据库health_db字符集选utf8mb4排序规则utf8mb4_unicode_ci在查询窗口粘贴HealthRecordMapper.xml中script内全部 SQL共12张表关键操作左上角菜单 →工具 → 选项 → SQL 编辑器 → 取消勾选「自动提交」执行前先运行SET FOREIGN_KEY_CHECKS 0;所有建表语句执行完毕后再运行SET FOREIGN_KEY_CHECKS 1;原因user_info表含id_card字段CHAR(18)health_record表含user_id外键若按默认顺序建表Navicat 会因外键依赖报错ERROR 1005。2.3 Vue 前端启动别 npm run dev用2-run.bat启动代理服务项目前端未用vue-cli-service而是基于webpack-dev-server自定义代理。直接npm run dev会报Cannot find module webpack—— 因为package.json中devDependencies被刻意精简仅保留生产必需项。正确流程是# 进入 frontend 目录注意不是项目根目录 cd frontend # 执行 2-run.bat它内部执行 npm install --registryhttps://registry.npmmirror.com npm run serve-proxyserve-proxy脚本在package.json中定义为serve-proxy: webpack-dev-server --inline --progress --config build/webpack.dev.conf.js该配置文件中devServer.proxy指向http://localhost:8080即后端 Tomcat 端口必须确保 Tomcat 已启动且health-web.war解压成功否则 Vue 页面所有 API 请求均返回504 Gateway Timeout。3. 接口联调SSM 层如何把微信 OpenID 绑定到用户体系绕过 session 黑匣子3.1 微信登录链路从wx.login()到UserInfoController的三段式校验小程序端调用wx.login()获取code传给后端/api/user/wx-login接口。SSM 层处理逻辑不在RestController而在UserInfoController.java的wxLogin()方法中分三步完成换取 OpenID用codeappidappsecret调用微信接口https://api.weixin.qq.com/sns/jscode2session查库判重根据openid查询user_info表若存在则返回userId若不存在则插入新记录含unionid字段留空因未开通微信开放平台生成自定义 Token非 JWT而是MD5(openid salt timestamp)存入user_token表并设置 7 天过期关键代码段UserInfoController.javaPostMapping(/wx-login) ResponseBody public Result wxLogin(RequestBody MapString, String params) { String code params.get(code); // 1. 调用微信接口换 openid此处省略 HttpClient 调用细节 String openid weixinService.getOpenId(code); // 2. 查库 UserInfo user userInfoService.findByOpenid(openid); if (user null) { user new UserInfo(); user.setOpenid(openid); user.setCreateTime(new Date()); userInfoService.insert(user); // 注意此 insert 不含 password 字段 } // 3. 生成 tokensalt 来自 properties 配置 String token DigestUtils.md5Hex(openid health_salt_2023 System.currentTimeMillis()); userTokenService.saveToken(user.getId(), token, 7 * 24 * 3600); // 单位秒 return Result.success().addData(token, token).addData(userId, user.getId()); }注意user_info表中password字段允许 NULL因为微信登录用户无密码但username字段为openid值不可改——这是后续所有接口鉴权的依据。3.2 健康数据上报health_record表的 JSON 字段设计与 MyBatis 处理用户在小程序填写「今日运动」时前端传参为{ userId: 1001, recordType: exercise, data: { duration: 45, calories: 280, steps: 6230, gpsPath: POLYGON((116.3 39.9,116.31 39.91,...)) } }后端HealthRecordController.java接收RequestBody HealthRecord record其中data字段类型为String非MapMyBatis 插入时直接存入data字段TEXT 类型。为什么不用RequestBody Map因为recordType为枚举值exercise/blood_pressure/blood_sugar需在 Service 层做类型路由// HealthRecordService.java public void saveRecord(HealthRecord record) { switch (record.getRecordType()) { case exercise: validateExerciseData(record.getData()); // 校验 JSON 结构 break; case blood_sugar: validateBloodSugarData(record.getData()); // 校验血糖值范围 break; } healthRecordMapper.insert(record); // MyBatis XML 中 data 字段用 #{data} 直接插入 }3.3 PDF 上传Vue 前端用wx.uploadFileSSM 后端用MultipartFile接收并转存小程序端调用wx.uploadFile({ url: https://localhost:8080/api/file/upload-pdf, filePath: tempFilePath, name: pdfFile, formData: { userId: 1001 }, success: (res) { console.log(res.data) } })后端 ControllerPostMapping(/upload-pdf) ResponseBody public Result uploadPdf(RequestParam(pdfFile) MultipartFile file, RequestParam(userId) Long userId) { // 1. 校验文件类型必须为 application/pdf if (!application/pdf.equals(file.getContentType())) { return Result.fail(仅支持PDF格式上传); } // 2. 生成唯一文件名避免中文乱码 String fileName userId _ System.currentTimeMillis() .pdf; // 3. 存入磁盘非数据库BLOB File dest new File(D:/health-pdfs/ fileName); file.transferTo(dest); // 4. 记录路径到 database PdfRecord pdf new PdfRecord(); pdf.setUserId(userId); pdf.setFilePath(D:/health-pdfs/ fileName); pdf.setUploadTime(new Date()); pdfRecordService.insert(pdf); return Result.success(); }玄学经验Windows 下file.transferTo(dest)若报Access is denied不是权限问题而是D:/health-pdfs/目录不存在——SSM 不会自动创建父目录必须手动建好。4. 避坑指南SSM Vue 微信小程序联调的五个血泪现场4.1 现象小程序调用/api/user/info返回 401但 Postman 调用正常原因微信小程序request默认不携带 Cookie而 SSM 的Interceptor依赖HttpServletRequest.getSession().getAttribute(userId)做鉴权。Postman 会自动保存 JSESSIONID小程序不会。解决废弃 Session 鉴权改用请求头Authorization: Bearer token。在WebMvcConfigurer中添加拦截器Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .excludePathPatterns(/api/user/wx-login) // 登录接口放行 .excludePathPatterns(/static/**); // 静态资源放行 }AuthInterceptor.java中从request.getHeader(Authorization)提取 token查user_token表验证有效性。4.2 现象Vue 页面显示「undefined」控制台报TypeError: Cannot read property name of undefined原因IndexMain.vue.bak是备份文件真正入口是IndexMain.vue但main.js中new Vue({ render: h h(App) })的App.vue里router-view未包裹在div中导致 Vue 2.6 的 Fragment 渲染失败。解决打开App.vue将router-view/router-view改为template div idapp router-view/ /div /template4.3 现象MySQL 插入health_record报错Incorrect string value: \xF0\x9F\x91\xA4... for column data原因data字段为 TEXT 类型但 MySQL 5.7 默认字符集utf8不支持 emoji需utf8mb4。虽然数据库建表时用了utf8mb4但 JDBC 连接字符串缺参数。解决修改jdbc.propertiesjdbc.urljdbc:mysql://localhost:3306/health_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneGMT%2B8并确保 MySQL 配置文件my.cnf中[mysqld]段落含[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci4.4 现象2-run.bat启动后 Vue 页面空白Network 面板显示GET http://localhost:8080/static/css/app.css net::ERR_ABORTED 404原因Webpack 输出路径与 Tomcat 静态资源路径不匹配。build/webpack.base.conf.js中output.publicPath设为/static/但2-run.bat启动的是独立 webpack-dev-server端口 8080而static目录实际在 Tomcat 的webapps/health-web/static/下。解决修改build/webpack.dev.conf.jsdevServer: { port: 8080, proxy: { /api: { target: http://localhost:8080, // 指向 Tomcat changeOrigin: true } }, // 关键让 webpack-dev-server 也托管 static 目录 contentBase: path.join(__dirname, ../static), watchContentBase: true }4.5 现象微信开发者工具真机调试时wx.getLocation()返回errCode: 1位置服务未开启原因小程序app.json中permission字段缺失iOS 真机需显式声明。解决在app.json根节点添加permission: { scope.userLocation: { desc: 用于获取您的实时位置以便为您推荐附近健康服务 } }且wx.getLocation()调用前必须先wx.authorize({scope: scope.userLocation})否则 iOS 直接拒绝。5. 进阶实战把血糖趋势图从 ECharts 换成 Canvas 原生渲染解决低端安卓机白屏问题5.1 为什么必须换ECharts 在 Android 4.4 WebView 上的三大翻车点项目原blood-sugar-trend.vue用 ECharts 5.2 渲染折线图但在华为 P8Android 4.4.2 WebView 39上必现白屏。抓包发现echarts.min.js加载成功但init()时document.getElementById(chart)返回 null控制台报TypeError: Cannot read property getContext of nullcanvas元素被渲染但尺寸为0x0根本原因是Android 4.4 WebView 对getBoundingClientRect()支持不全ECharts 依赖此 API 计算容器尺寸。而 Canvas 原生绘图只需canvas.width/height完全可控。5.2 Canvas 渲染血糖趋势图四步实现零依赖我们用blood-sugar-trend-canvas.vue替代原组件核心逻辑如下步骤1HTML 结构精简到极致template div classchart-container canvas refchartCanvas width375 height250/canvas /div /template注意width/height必须写死非 CSS 设置否则 Canvas 会缩放失真。步骤2计算坐标系与数据映射mounted() { this.drawChart() }, methods: { drawChart() { const canvas this.$refs.chartCanvas const ctx canvas.getContext(2d) const data this.sugarData // [{date: 2023-05-01, value: 6.2}, ...] // 1. 定义画布边界留白 const margin { top: 20, right: 10, bottom: 30, left: 40 } const width canvas.width - margin.left - margin.right const height canvas.height - margin.top - margin.bottom // 2. 计算 X/Y 轴范围 const xScale d3.scaleBand() .domain(data.map(d d.date)) .range([0, width]) .padding(0.2) const yScale d3.scaleLinear() .domain([3.0, 12.0]) // 血糖安全范围 .range([height, 0]) // 3. 绘制网格线Y轴 ctx.strokeStyle #eee ctx.lineWidth 1 for (let i 0; i 5; i) { const y height - i * height / 5 ctx.beginPath() ctx.moveTo(margin.left, y margin.top) ctx.lineTo(canvas.width - margin.right, y margin.top) ctx.stroke() } } }步骤3绘制折线与数据点// 接续 drawChart() 函数 // 4. 绘制折线 ctx.strokeStyle #409EFF ctx.lineWidth 2 ctx.beginPath() data.forEach((d, i) { const x xScale(d.date) xScale.bandwidth() / 2 margin.left const y yScale(d.value) margin.top if (i 0) ctx.moveTo(x, y) else ctx.lineTo(x, y) }) ctx.stroke() // 5. 绘制数据点圆圈 ctx.fillStyle #409EFF data.forEach(d { const x xScale(d.date) xScale.bandwidth() / 2 margin.left const y yScale(d.value) margin.top ctx.beginPath() ctx.arc(x, y, 4, 0, Math.PI * 2) ctx.fill() })步骤4添加交互提示hover 效果// 在 mounted() 中添加事件监听 this.$refs.chartCanvas.addEventListener(mousemove, (e) { const rect canvas.getBoundingClientRect() const x e.clientX - rect.left const y e.clientY - rect.top // 简单距离判断非精确但够用 let nearest null let minDist 999 data.forEach((d, i) { const px xScale(d.date) xScale.bandwidth() / 2 margin.left const py yScale(d.value) margin.top const dist Math.sqrt(Math.pow(x - px, 2) Math.pow(y - py, 2)) if (dist 20 dist minDist) { minDist dist nearest d } }) if (nearest) { this.tooltip ${nearest.date}${nearest.value} mmol/L } else { this.tooltip } })5.3 性能对比与上线 checklist指标ECharts 方案Canvas 方案首屏渲染耗时Android 4.43200ms白屏480ms立即出图内存占用12MB3.2MB包体积增加180KB0KB原生 API上线前必做三件事在vue.config.js中关闭productionSourceMap: false减少 1.2MB 包体积将canvas绘制逻辑抽离为chart-utils.js通过import { drawBloodSugarChart } from /utils/chart-utils复用在App.vue的beforeDestroy钩子中清除canvas事件监听防止内存泄漏从那以后我每次做微信小程序图表需求都强制走一遍 Canvas 原生方案验证——哪怕只是画条直线也要确认getContext(2d)是否可用。低端机不是测试边缘而是真实用户的第一道门槛。希望帮到你。本文还有配套的精品资源点击获取