elder 项目功能笔记(分模块)
技术栈:Spring Boot 3.4.5 + MyBatis-Plus + MySQL + JWT + 阿里云 OSS + Spring AI(DeepSeek) + EasyExcel
前端:ui-admin 管理后台(Vue3 + Element Plus)、ui-app 手机端(Vue3 + Vant)
一、登录模块(全项目的参照模板)
流程:
- 前端输入姓名和密码,
POST /admin/users/login提交{name, password} - 后端
getOne(new QueryWrapper<User>().eq("name", user.getName()))判断是否有数据,没有返回”用户名不存在” - 有就对比密码,再依次校验注销(deleted)、禁用(status)状态
- 校验通过后,map 存储 id 和 name,通过
String token = JwtUtil.createToken(map)创建 token(HMAC256 签名,1 小时过期) - token 传到前端,
tokenStore.setToken(result.data)存入 Pinia(并持久化到 localStorage,key 为elder-admin-token),然后router.push('/')跳转首页 - 首页路由守卫拦截 → 请求
userInfo接口 → 后端从 token 解析出 id 查库,返回用户信息 + 菜单树 + 按钮权限 - 之后每次请求,axios 请求拦截器自动在
Authorization头带上 token;后端LoginInterceptor验签,失败返回 401,前端收到 401 清 token 踢回登录页
token 校验链路:
登录成功拿 token → Pinia + localStorage 持久化(刷新不丢失)
↓
axios 请求拦截器:headers.Authorization = token
↓
后端 LoginInterceptor:JwtUtil.parseToken 验签验过期
↓ ↓
通过→放行 Controller 失败→401→前端踢回 /login
手机端登录(/app/elders/login)流程完全相同,只是查的是 Elder 表(老人姓名即账号),登录成功当场调 userInfo() 拉老人信息再跳转。两端 token store 用不同 localStorage key,防止同域名下互相覆盖。
注册/找回密码:注册校验用户名、手机号唯一后创建账号;忘记密码按手机号找到用户直接改密(TODO:后期改邮箱验证)。
二、权限模块(RBAC)
功能:角色管理、权限(菜单/按钮)管理、给角色分配权限、给用户分配角色。
五张表:user → user_role → role → role_permission → permission(permission 分三类:0 目录 / 1 菜单带路由 / 2 按钮带权限值如 user:add)。
实现流程:
- 登录后
userInfo接口按 userId 三表关联查出全部权限 - type=2 的权限值进
btnList→ 前端hasBtnPermission('user:add')控制按钮显隐 - type=0/1 递归组装成菜单树
routerList→ 前端动态生成左侧菜单,并做路径级鉴权(手输 URL 无权限会被拦回首页) - 分配权限/角色的接口统一”先删旧关联再插新关联“全量覆盖
三、用户管理(后台)
功能:用户增删改查、条件分页、批量删除、Excel 导入导出、分配角色、重置密码。
实现流程:
- 列表:
?page=&limit=+ Query 对象(name/email 模糊、时间区间)走 MyBatis-Plus 分页插件 - Excel 导出:EasyExcel 写响应流,前端
blob接收下载;导入:监听器逐行读逐行入库 - 重置密码:从 token 解析本人 id → 校验旧密码 → 更新新密码
- 分配护工下拉:
listByRole?roleCode=HUGONG按角色编码查该角色下的用户
四、老人管理
功能:老人档案增删改查(照片、富文本备注、Excel 导入导出)、标签管理、给老人打标签。老人同时是手机端登录账号。
实现流程:
- 状态:0禁用/1启用/2请假/3退住中/4入住中/5已退住
- 标签分配:先删后插全量覆盖;列表支持多标签 AND 筛选(老人必须同时拥有所选全部标签)
- 查询结果批量回填每条老人的 tagList,避免循环查库(N+1 问题)
- 标签编码唯一,冲突捕获
DuplicateKeyException返回友好提示
五、客户管理(客户/合同/附件)
业务主线:录入客户(意向) → 关联老人 → 签订合同 → 合同生效(入住)。
功能:客户列表(来源、状态字典)、客户分配老人、合同管理、合同附件上传预览。
实现流程:
- 绑定方向是老人挂客户:分配老人 = 事务里先清掉未勾选老人的 customer_id,再给勾选的老人设置,全量替换
- 合同自动回填客户:保存合同时强制
customerId = elder.customerId,客户由老人唯一确定,不信任前端传值 - 级联保护:客户名下还有老人或合同时拒绝删除,防孤儿数据
- 合同状态:1草稿/2待签署/3生效中/4到期/5终止/6作废
- 附件传 OSS 存 URL;预览走后端代理(见公共功能)
六、护理管理(等级/项目/计划/任务)
功能:护理等级、护理项目、护理计划(给老人定周期性护理方案)、护理任务(护工执行打卡)。
实现流程(计划→任务引擎):
- 计划明细指定每个项目的时间、周期(0每天/1每周/2每月)、频次
- 保存计划时一次性物化生成任务:遍历起止日期,按周期判定执行日(每周=距开始日%7==0,每月=同日号),每个执行日按频次生成 N 条任务;不是定时任务
- 任务快照项目名、护工、时间,状态 0待执行/1已完成/2已跳过
- 删除计划级联删明细和任务
- 护工在任务页打卡执行:填实际时间、结果(如”血压120/80″)、传打卡照片
护工数据隔离:任务列表接口解析 token,判断角色是护工(HUGONG)就服务端强制覆盖查询条件为只看自己的任务,前端传什么都无效。
七、预约管理(项目/计划/任务)
功能:预约项目维护、预约计划(管理员创建 + 公开页免登录自助提交)、分配护工、任务执行打卡。
实现流程:
- 状态机:自助提交强制
status=0待确认, source=1自助→ 待确认不生成任务 → 管理员assign/{id}分配护工,0→1 进行中时才生成任务 → 取消则删任务 - 管理员直接创建计划时若选了护工则直接确认并生成任务
- 修改计划:删旧明细和旧任务后重建
- 公开接口
submit、public-list在拦截器白名单里,免登录可访问 - 其余与护理模块的任务引擎完全同构
八、体检管理(项目/套餐/记录)
功能:体检项目维护、体检套餐(穿梭框分配项目)、手机端预约套餐、后台到检签到、逐项录结果、完成出报告。
实现流程:
- 套餐价格 = 所含项目价格之和,分配项目时自动重算
- 状态机:手机端提交 0待体检 → 到检签到 1体检中 → 完成体检 2已完成;取消 3已取消
- 提交时做价格快照(套餐后改价不影响历史订单);明细做 itemName 快照(项目改名不污染历史)
- 录结果时数值型项目按参考范围自动判定异常,覆盖前端选择;完成时未录的明细统一标”未完成”
- 手机端报告:本人 + 已完成双重校验后才返回
九、手机端功能(ui-app,老人使用)
功能:登录、首页健康数据、浏览体检套餐并预约、上门服务目录与预约、我的预约(体检+服务合并列表,可取消)、我的合同及附件预览、体检报告、AI 助手、个人中心(资料/改密/头像)。
实现流程:
- Controller 层独立(
/app/),Service 层完全复用后台的** - 所有数据隔离靠”从 token 解析 elderId + 只操作本人数据”:查合同先验合同属于本人,查报告验本人+已完成
- 我的预约是三源合并:体检记录 + 已确认服务任务 + 待确认服务计划(用 5 表示待确认),按日期倒序展示
- 取消预约按 type 区分:体检取消体检记录,服务仅可取消本人待确认计划
- 预约提交/取消后置 store 里的 dirty 脏标记,列表页据此重新拉数据
十、AI 助手(康养小智)
功能:手机端聊天窗口,健康咨询,可查老人自己的体检报告。
实现流程:
- DeepSeek ChatClient + 系统提示词(紧急情况提示就医、不替代医生等规则)
- 会话记忆以 token 里的老人 id 作为 conversationId,按老人隔离
- 挂了三个工具(Function Calling):查本人档案、最近报告、历史报告——工具底层复用”本人+已完成”校验过的接口,AI 也越不了权
- 回复走 SSE 流式:后端返回 Flux 流,前端用原生 fetch 逐块读取渲染打字机效果,
[END]为结束标记,Markdown 渲染 + DOMPurify 防注入 - 离开页面自动中断未完成的请求
十一、公共功能
文件上传:统一 POST /upload,UUID 重命名后传阿里云 OSS,返回 URL 由前端存库。admin 用 el-upload(手动带 Authorization 头),app 用 van-uploader。
OSS 读取代理:GET /oss/get?url= 后端读取 OSS 对象流式回写(inline 内联预览),绕过匿名直链强制下载的问题,也不暴露 OSS 地址。admin 和 app 各有一个代理 Controller。
首页图表(后台):4 张统计卡片(老人/计划/待执行/已完成)+ 近 7 日新增面积图、完成柱状图、状态环形图,数据通过多次分页查询取 total 聚合。
Excel 导入导出:用户、老人两个模块支持,导出 blob 下载,导入逐行读逐行入库。
十二、横切机制速查
| 机制 | 做法 |
|---|---|
| 统一响应 | Result:code(1成功/0失败) + msg + data,前端拦截器统一判 |
| 全局异常 | 业务异常 ServiceException 抛出 → 全局处理器转 Result.error(msg) |
| 登录校验 | LoginInterceptor 拦 /**,白名单放行:两个 login、register、forgetPassword、公开预约 2 个、公开体检套餐 2 个 |
| 分页 | ?page=&limit= 统一入参,MyBatis-Plus 分页插件;复杂 join 写 XML |
| 逻辑删除 | deleted 字段 @TableLogic,查询自动过滤 |
| 自动填充 | createTime/updateTime 由 MetaObjectHandler 自动写 |
| Long 精度 | 全局 Long→String 序列化,防前端 JS 丢精度 |
十三、通用设计套路(各模块反复出现)
- 不信任前端:自助提交强制覆盖 status/source/elderId,更新只接受白名单字段
- 关联分配先删后插全量覆盖(角色/权限/标签/老人/套餐项目五处同款)
- 快照设计:价格快照、项目名快照,历史数据不受主数据修改影响
- 主子表级联:删计划级联删明细+任务;客户反向禁止删除
- 登录定义的三种模式贯穿全项目:先查后比、不信任前端、token 里的 id 是唯一身份凭证
十四、已知问题
ui-admin/src/utils/request.js:35:401 分支用了未 import 的router,token 过期时会报错而非跳转(ui-app 版本正常)- Authorization 头为裸 token,无
Bearer前缀 - 两端登录页预填了测试账号密码,上线前需删除
- JWT 密钥硬编码
"dxk",密码明文存储,生产环境需改为环境变量 + 哈希 - 后端接口层无 RBAC 校验,权限只在前端菜单/按钮层面