作者:974294341-bit | 技术栈:Spring Boot 3 / Vue3 / MySQL·H2 | 标准:MZ/T 039-2013
- 为什么写这篇
起因很朴素:动手做这个项目前,在 B 站刷到两个"老年人能力评估系统"的视频,一个是微信小程序版、一个是机构 Web 版。看演示挺像回事,但点进去只有解说,没有源码、没有题库、没有打分逻辑。
作为一个想真正搞懂"它到底怎么算出来'失能等级'"的人,光看演示没用。于是我做了三件事:
-
逆向这两个系统的设计与架构(设计级,非反编译);
-
扒标准——找到民政部《老年人能力评估》MZ/T 039-2013,把"22 题怎么评、等级怎么定"搞透;
-
自己落地一套能跑的系统:电脑端管理后台 + 登录鉴权 + 后端评估引擎(手机端原生 App 下轮做)。
这篇博客就是这条链路的完整记录,重点放在评估算法怎么工程化、登录与权限怎么零依赖实现,以及本机没有 Maven 也把 Spring Boot 跑起来的实战,而不是堆界面截图。 -
逆向对象:两个系统的真实面貌
两个 B 站链接都是搜索聚合落地页,拿不到源码,所以做的是设计级逆向——基于公开描述 + 国标框架还原架构。结论如下:
系统 A(教学演示向)
系统 B(机构专业向)
形态
微信小程序(C 端)+ Web 后台
Web 系统(机构/医养场景)
角色
老人 / 自评师 / 管理员(3 类)
评估员 / 管理员
技术栈
Spring + Vue3 + Bootstrap + 小程序
同类 Web(框架未明示)
评估类型
单一(国标能力评估)
多量表(能力/认知/营养…)
特色
自评 + 评估师建议 + 统计看板
AI 处方 + 详细/简易双报告
定位
轻量/教学演示
专业评估/干预闭环
关键启发来自系统 B:它"支持多种评估类型"的本质,是把量表配置化——一套表结构能吃下所有量表,加新量表 = 插几条数据,不碰代码。这一点我直接吸收进了自己的设计。 -
标准深挖:评估到底怎么算
拿到 MZ/T 039-2013 标准后,核心模型非常清晰,是一个四层结构:
22 个二级指标(逐项 0~4 评分)
↓ 求和
4 个一级指标
↓ 按得分区间划级
一级指标等级(0 能力完好 ~ 3 重度受损)
↓ 组合判定(ADL 权重最高)
综合能力等级(0 能力完好 ~ 3 重度失能)
2.1 4 个一级指标 + 22 个二级指标
一级指标
满分
二级指标(数量)
日常生活活动(ADL)
40
进食、洗澡、修饰、穿衣、大便控制、小便控制、如厕、床椅转移、平地行走、上下楼梯(10)
精神状态
8
认知功能、攻击行为、抑郁症状(3)
感知觉与沟通
10
意识水平、视力、听力、沟通交流(4)
社会参与
20
生活能力、工作能力、时间/空间定向、人物定向、社会交往能力(5)
多数项目 04 分(4=完好,0=完全丧失);部分损伤类项目(攻击行为、抑郁、视力、听力、意识)只用 02 档。
2.2 综合等级判定(2013 版核心)
不是简单取平均分,日常生活活动(ADL)权重最高。判定规则:
- 0 级 能力完好:4 个维度全为 0 级
- 1 级 轻度失能:出现 1 级且无更高
- 2 级 中度失能:出现 2 级(或 ADL=1 且其他有 ≥1 项为 2 级)
- 3 级 重度失能:任一维度=3 级(或 ADL=2 且其他 ≥2 项为 2/3 级;或 ADL=3)
2.3 一个易踩的版本坑
MZ/T 039-2013(2013 版)和 GB/T 42195-2022(2022 版)不是一回事:
- 2013 版 = 22 题、能力分 4 级(0~3)
- 2022 版 = 26 题、能力分 5 级(新增"完全失能"4 级)
拿源码后第一件事,就是确认它吃的是哪一版,决定题库是 22 还是 26、等级是 4 还是 5 档。我这里先按 2013 版实现。
-
关键决策:不要小程序,要原生 App
项目硬性要求:电脑端 + 手机端 App,不要微信小程序。
手机端技术选型当时对比了三种:
方案
优点
缺点
结论
Flutter
一套代码出 Android+iOS 真机,最像"正经 App"
多学一门 Dart
最省事但非首选
Android Kotlin 原生
与 Java 后端生态近、项目演示有说服力
只有 Android,无 iOS
选定(下轮做)
React Native
前端 React 复用
与现成 Vue 后台不共用
排除
最终落地节奏:后端 Spring Boot + 电脑端 Vue 管理后台 + 登录鉴权先跑通,手机端 Android(Kotlin + Retrofit + Compose)复用同一套 REST API,不改后端。 -
工程落地(核心)
项目路径:elder-assessment-engine/,结构如下:
elder-assessment-engine/
├── pom.xml # Spring Boot 3 / Java 17
├── resolve_cp.mjs # 免 Maven 依赖解析器(见 §6)
├── sql/elder_assessment_mzt039_2013.sql # 生产建表 + 22 题选项 JSON
├── src/main/java/com/example/elder/
│ ├── assessment/AssessmentEngine.java # ★ 评估算法核心
│ ├── entity/ # Elder, Scale, ScaleItem, ScaleResult, ScaleAnswer, User...
│ ├── repo/ # Spring Data JPA
│ ├── service/ # 评估业务 + 统计
│ ├── controller/ # /api/auth, /api/scale, /api/elders, /api/assessments, /api/stats
│ ├── config/ # CORS + 拦截器 + 启动种子数据
│ └── dto/
└── frontend/ # Vue3 + Vite + Element Plus + ECharts
└── src/views/ # Login / ElderList(建档) / Assessment(22题表单) / Report(双报告) / Statistics
4.1 评估引擎(最重要的一块)
把"四层模型"直接写成纯 Java 方法,不依赖框架,方便独立测试。维度满分硬编码为 DIM_MAX = {40, 8, 10, 20},22 题按题号映射到 4 个维度:
// 题号(1~22) → 维度下标:1-10→ADL, 11-13→精神, 14-17→感知, 18-22→社会
private static int[] buildItemDim() {
int[] a = new int[23];
for (int i = 1; i <= 10; i++) a[i] = 0; // 日常生活活动
for (int i = 11; i <= 13; i++) a[i] = 1; // 精神状态
for (int i = 14; i <= 17; i++) a[i] = 2; // 感知觉与沟通
for (int i = 18; i <= 22; i++) a[i] = 3; // 社会参与
return a;
}
综合定级就是标准里那段组合规则的直接翻译,ADL 权重最高:
public static int comprehensiveLevel(int[] dimLevels) {
if (Arrays.stream(dimLevels).allMatch(l -> l == 0)) return 0; // 全 0 → 完好
int adl = dimLevels[0];
if (adl == 3) return 3; // ADL 重度 → 综合重度
if (Arrays.stream(dimLevels).anyMatch(l -> l == 3)) return 3; // 任一维度重度
int othersGE2 = 0;
for (int i = 1; i < 4; i++) if (dimLevels[i] >= 2) othersGE2++;
if (adl == 2 && othersGE2 >= 2) return 3; // ADL 中度 + 其他≥2项中度
if (Arrays.stream(dimLevels).anyMatch(l -> l == 2)) return 2; // 任一中度
if (adl == 1 && othersGE2 >= 1) return 2;
if (Arrays.stream(dimLevels).anyMatch(l -> l == 1)) return 1; // 任一轻度
return 0;
}
主入口 assess(answers, thresholds):题号→分值 + 各维度等级阈值 → 返回四个维度原始分、维度等级、综合等级与名称。
4.2 后端 Spring Boot
- 表设计沿用"多量表配置化"思路:scale / scale_dimension / scale_item / scale_result / scale_answer / scale_dimension_level / user。当前只挂了 MZ/T 039-2013 一张量表,但结构能无缝扩展认知、营养等量表。
- REST API(前端直接消费):
- POST /api/auth/login —— 登录换 token
- GET /api/scale —— 拉取评估表单(维度+题目+选项)
- GET/POST/PUT /api/elders —— 老人建档管理
- POST /api/assessments —— 提交 22 题答案,返回定级结果
- GET /api/assessments/{id}/report —— 详细/简易双报告
- GET /api/stats/overview —— 统计概览
- dev 用 H2 内存库 + 启动自动种子(4 维度 / 22 题 / 等级阈值 / 演示老人 / 登录账号),开箱即跑;application-prod.yml 切 MySQL。
- 登录与权限:零依赖实现
参考演示系统的"欢迎页 → 主界面"流程,我把登录鉴权也做进去了。但不引 Spring Security、不引 jjwt——全部用 JDK 内置能力实现,既减少依赖又能在"无 Maven"环境里编译通过。
5.1 密码:PBKDF2 + 每用户独立加盐
// PasswordUtil:JDK 内置 PBKDF2WithHmacSHA256,65536 次迭代,256 位密钥
private static final int ITERATIONS = 65536;
private static final int KEY_LENGTH = 256;
private static final String ALGO = "PBKDF2WithHmacSHA256";
public static String generateSalt() { // 16 字节随机盐
byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);
return Base64.getEncoder().encodeToString(salt);
}
public static String hash(String password, String salt) { /* PBEKeySpec + 哈希 */ }
public static boolean matches(String password, String salt, String storedHash) {
return storedHash != null && storedHash.equals(hash(password, salt));
}
数据库只存 salt + hash,即使泄露也无法反推明文。
5.2 令牌:HMAC-SHA256 自签 JWT
不引 jjwt,自己拼 header.payload.signature:
// JwtUtil:HMAC-SHA256 自签,payload 仅含 sub/role/exp
private static final long EXPIRE_MS = 1000L * 60 * 60 * 8; // 8 小时
public static String generate(String username, String role) {
String header = b64url("{"alg":"HS256","typ":"JWT"}"…);
String payload = b64url("{"sub":""+username+"","role":""+role+"","exp":"+exp+"}"…);
String sig = hmacSha256(header + "." + payload, SECRET); // JDK Mac
return header + "." + payload + "." + sig;
}
public static Map<String,Object> parse(String token) { /* 验签 + 解 payload */ }
注意:SECRET 目前是代码里的演示默认值,上线应改环境变量或配置项;token 里只放用户名/角色/过期时间,无敏感信息。
5.3 两角色 + 拦截器
AuthInterceptor 拦截 /api/**(放行 /api/auth/login),从 Authorization: Bearer
角色
用户名
密码
说明
管理员
admin
admin123
系统管理员
评估员
evaluator
eval123
评估员小张
实测坑提醒:评估员密码是 eval123,不是管理员的 admin123——填错就会出现"用户名或密码错误"(401)。
- 踩坑与校准(干货)
6.1 定级阈值不能拍脑袋 ⚠️
维度原始分 → 维度等级,需要阈值(如 ADL ≥36 为 0 级)。我代码里留了注入点,种子数据用的默认近似:
ADL: 0级≥36 1级≥30 2级≥20 3级≥0
精神: 0级≥7 1级≥6 2级≥4 3级≥0
感知: 0级≥9 1级≥8 2级≥5 3级≥0
社会: 0级≥18 1级≥15 2级≥10 3级≥0
上线前必须做:用标准附录《一级指标等级划分》的精确分值覆盖 scale_dimension_level 表,否则定级结果可能与官方不一致。这是整个系统最容易"看起来对、实际错"的地方。
6.2 本机没有 Maven,照样把 Spring Boot 跑起来 🔧
这是本次最有价值的一段。本机没有 Maven 可执行文件,且代理拦截装不上;更糟的是本地 .m2 被两个 Spring Boot 版本(3.2.5 和 4.1.0)污染,全量收集 jar 必然版本冲突。
我的解法是写了一个依赖解析器 resolve_cp.mjs:
- 以 spring-boot-dependencies 3.2.5 的 BOM 为权威版本表,递归解析传递依赖;
- 处理 BOM 的 import 嵌套(spring-framework-bom / jackson-bom 等)、链式属性 ${a.b}、省略 groupId 的继承;
- 从 .m2 缓存里收集精确版本的 jar,复制进 lib/ 目录;
- 用 javac -cp 'lib/' 编译、java -cp 'out;lib/' 主类 直接运行,完全绕过 Maven 打包。
途中还补齐了 .m2 缺失/选错的 byte-buddy(Hibernate 字节码增强必需,锁定 1.14.13)、lombok、h2、logback。
最终验证:POST /api/auth/login 用 admin/admin123、evaluator/eval123 均 200 返回 token,真实后端在 8080 端口稳定运行。
6.3 "AI 处方"是医疗敏感词
系统 B 的卖点"AI 处方",在医养语境下本质是干预/照护方案,不是开药。若要做:
- 干预项来自人工维护的目录(可审计),LLM 只做自然语言润色,不自主诊断;
- 输出需人工审核留痕(auditor_id),无资质场景不做诊断。否则合规风险很大。
6.4 界面美化:欢迎页 + 2.5D 插画
登录页做了视觉升级:左侧 2.5D 等距插画背景 + 蓝紫渐变,登录卡片居中浮于图层之上(毛玻璃质感);主界面去掉了左侧深灰导航栏,改为顶部导航。这些不影响逻辑,但让演示更有说服力。
-
验证结果
验证项
结果
评估引擎独立编译(JDK 17, UTF-8)
✅ 通过
前端 npm install + vite build(1666 模块)
✅ 通过
真实 Spring Boot 后端运行 + 登录鉴权
✅ 通过(免 Maven 方式,8080 端口实测)
22 题评估 → 综合定级
✅ 通过
多条件查询 / 统计 / 导入导出
✅ 通过 -
本地运行
方式一:标准(需要 Maven + JDK 17)
后端(默认 H2 内存库,:8080)
cd elder-assessment-engine
mvn spring-boot:run
电脑端(:5173,已配 /api 代理到 8080)
cd elder-assessment-engine/frontend
npm install && npm run dev
方式二:本机无 Maven(已验证)
依赖已解析进 lib/、编译产物在 out/,直接运行主类即可:
cd elder-assessment-engine
java -cp 'out;lib/*' com.example.elder.ElderAssessmentApplication --server.port=8080
注:application.yml 里的 server.port 在本环境没被 Spring 读到,用 --server.port=8080 命令行固定即可,功能不受影响。
体验路径:打开 http://localhost:5173/login → 用 admin/admin123(或 evaluator/eval123)登录 → 老人管理 → 新建 → 去评估 → 答 22 题 → 提交 → 看详细/简易报告。
-
按课堂任务补齐的功能(WEB 端)
项目实际是依据一份课堂任务实现的,要求"开发 WEB + 手机端两套评估系统",并明确了 5 项功能。WEB 端已补齐:
任务要求
实现方式
① 老人注册/信息导入
单条建档 + CSV 批量导入(含模板下载)
② 能力定期评估
22 题表单,同一老人可多次评估,历史记录可查、可再次评估
③ 多条件查询
按姓名/性别/年龄/最近评估等级/评估时间筛选
④ 能力数据统计
统计页:等级分布饼图、各等级人数、评估次数趋势(ECharts)
⑤ 能力数据导出
老人数据、评估记录一键导出 CSV(Excel 可直接打开)
导入/导出统一用零依赖 CSV(UTF-8 BOM),不引入 POI,规避本机构建风险;手机端按任务要求另做原生 App(下轮)。 -
后续规划
-
手机端 Android 原生 App(Kotlin + Retrofit + Compose):复用现有 /api/*,无需改后端,下一轮做。
-
扩展多量表 + AI 处方引擎:接系统 B 的设计——规则引擎出干预项、LLM 做文案润色、人工审核留痕。
-
升级 GB/T 42195-2022:若目标系统实为 2022 版,扩题库到 26 题、等级到 5 档。
-
阈值校准:用标准附录精确分值替换默认近似阈值。
-
小结
这次从"看两个视频"到"自己写出能跑的评估引擎 + 登录鉴权",最大的体会是:
项目里的"智能评估"剥掉包装,核心就是"一套标准 + 一张题库表 + 一段定级函数"。 把国标吃透、把算法写成可单测的纯函数,比堆界面有价值得多;而"没有 Maven 也能把 Spring Boot 跑起来",逼着我真正理解了 classpath、BOM 版本仲裁和字节码增强这些被构建工具藏起来的东西。
逆向的价值不在"抄",在于逼自己把别人的演示翻译成可验证的工程实现——然后你会发现,很多"高大上"的系统,骨架也就这么大。
浙公网安备 33010602011771号