简介:《传智健康PRD文档1》是一份面向产品经理、需求分析人员及健康管理系统开发者的产品需求说明书,围绕健康评估指标的数据结构规范、用户角色划分、会员管理与预约管理流程展开,可用于理解医疗健康类项目的需求建模与功能设计。压缩包内仅含1个docx文档,约890KB,篇幅紧凑但目录覆盖简介、用户角色描述、产品概述、功能详情、健康评估与健康干预等章节,并配有产品结构图、流程图和特性描述,便于快速梳理业务链路与字段约束。目前已有600人学习,适合用作需求文档写作参考、课程案例拆解或同类健康管理平台的原型设计底稿,也能帮助读者对照指标名称、参考值、单位及适用性别等定义,理解健康评估模块的标准化思路。
1. 一份健康管理系统的 PRD,最容易让人低估的地方
一份健康管理系统的产品需求文档,最容易让人低估的地方是它看起来太规整了——七个模块、三张角色表、二十多张字段说明,目录排得整整齐齐,像一份可以直接照着写代码的说明书。有位做后端的同行拿到这份 PRD 的那个下午,直接开 IDE 建表,结果在第一张会员档案表上卡了半小时:出生日期这一栏写着「字符型、可修改、可为空、根据患者身份证号自动计算」,四个描述凑在一起,到底该由谁写入、能不能手改,文档没说。他后来换了读法,把这份文档当成一份数据契约,先抽字段、再定状态、最后反推接口,链路才顺过来。这份 PRD 适合谁:想找一个完整业务系统练手的后端、需要给课程项目找真实需求蓝本的人、接手健康管理类外包要快速摸清模块边界的开发者。它不给源码,给的是需求边界和字段口径,而这恰恰是二次开发里最容易翻车的地方。
2. 角色与总体流程:三种身份怎么穿过同一条业务主线
2.1 管理员、健康管理师、患者:权限边界决定接口要拆几套
文档里的角色表只有三行,但它对代码结构的影响远大于三行。管理员对平台所有信息有增删改查权限,健康管理师围绕患者做评估、干预、随访,患者在微信端预约、查报告、填评估、记健康日记。表面看是三个端,实际是两套数据可见性:管理员看全量,健康管理师只看分配给自己的患者。会员档案表里那个「健康管理师」字段不是装饰,它是数据权限的过滤列。
这意味着不能用一个/api/patient/list打天下。常见做法是按入口拆开:PC 管理端走/api/admin/**,健康管理师工作台走/api/manager/**,微信端走/api/wx/**,三套路由背后共用 Service,但查询条件在拦截层就被注入了manager_id。这样即便前端漏了参数,后端也不会把别人的患者抖出来。
权限点本身要落到「资源:动作」的粒度,而不是只存一个角色名。下面这份结构可以直接塞进 Redis 做登录态缓存:
{ "userId": 1024, "role": "HEALTH_MANAGER", "permissions": [ "member:archive:view", "member:archive:edit", "assess:report:create", "intervene:plan:create", "knowledge:diet:view" ], "dataScope": "SELF" }permissions控制的是按钮和接口能不能进,dataScope控制的是进了之后能看到几行数据,取值SELF表示仅本人名下患者,ALL表示全机构。这两个维度必须分开存,否则后面加「科室主管看本科室」这类需求时,就得把权限表推倒重来。注意知识库那几个模块,文档写得很清楚:管理员可增删改查,健康管理师仅可查看。这条规则落到代码里就是知识库的写接口上加@PreAuthorize("hasAuthority('knowledge:diet:edit')"),读接口不限。
2.2 总体流程:一条从预约到干预结束的状态链
文档的总体流程图画得不复杂,但把它摊平成状态就长了:患者预约体检、健康管理师确认是否到诊、到诊后建档、上传体检报告、做健康评估、系统按评估结果预分类、健康管理师确认人群分类、制定干预方案、执行干预、定期回访、有效则结束、无效则调整方案再走一轮。这条链上每一个箭头,在后端都是一次状态变更,而状态字段用什么类型存,直接决定了后面统计能不能做。
血泪经验是别用中文文本存状态。文档里预约状态写的是「电话回访患者确认是否按时到诊」,这种描述落到数据库要是存成"已到诊"三个字,后面做「本月到诊率」就得写where status = '已到诊',一旦有人手改成「已到检」,统计立刻归零。合格做法是tinyint加字典表,枚举值写进常量类,前端拿字典接口渲染。
预约状态建议这样切:0 待确认 / 1 已确认 / 2 已到诊 / 3 已取消 / 4 爽约。档案状态只有1 启用 / 0 停用两个值。干预状态更细一点:0 待制定 / 1 执行中 / 2 已结束 / 3 已中止,其中「有效则结束、无效则调整方案」对应的是从执行中回到待制定、并把旧方案标记为历史版本,而不是原地改方案内容——原地改会把回访记录挂到一个已经变形的方案上,复盘时完全对不上账。
2.3 功能摘要表:把「任务描述」翻译成接口清单
文档第三章那张功能摘要表信息密度最高,也最容易被当成目录跳过。它实际是一张「模块-子模块-任务描述」的三列表,把每一行动词拎出来,接口形态基本就定了。查询类落成分页列表,统计类落成聚合接口,文件类落成上传下载,增删改查落成标准 REST。
| 任务描述 | 接口形态 | 关键参数 |
|---|---|---|
| 会员查询 | GET /api/manager/member/page | name、phone、status、pageNum、pageSize |
| 本月预约量 | GET /api/manager/stat/reservation/count | month |
| 上传体检报告 | POST /api/manager/report/upload | archiveNo、checkDate、file |
| 日历预约 | GET /api/manager/schedule/calendar | startDate、endDate |
| 批量导入开诊时间 | POST /api/admin/schedule/import | file、month |
| 干预方案查询 | GET /api/manager/plan/page | archiveNo、status、startTime、endTime |
这张表里有两行值得单独说。「批量导入开诊时间」是管理员按月考勤式排班,导入的是「某天某套餐可约多少人」,不是一条条预约记录,所以它的落点是排班表而不是预约表,很多人第一次做会搞混,把排班写进预约表,结果日历上全是幽灵预约。「日历预约」的返回结构要按天聚合,前端才能画格子,如果接口直接返回明细列表,前端得自己 groupBy,数据量一上来就卡。
3. 数据结构设计:会员档案、评估指标、预约列表的字段落地
3.1 会员档案:把「字符型/可修改/可为空」翻译成 DDL
会员档案那张字段表有三十多行,是整份文档里最值得逐行读的部分。它给了四个信息:名称、类型、可修改、可为空。这三列的组合含义是——类型决定列类型,可为空决定NOT NULL,可修改决定这个字段进不进 update 的白名单。文档里「档案号」是字符型、不可修改、不可为空,那它就是业务主键,建唯一索引;「出生日期」是可修改、可为空、由身份证推算,那它就是一个冗余列,写入时算一次,之后允许人工纠正。
CREATE TABLE member_archive ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', archive_no VARCHAR(32) NOT NULL COMMENT '档案号,体检档案唯一标识,不可修改', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT NOT NULL COMMENT '性别 1男 2女', id_card VARCHAR(18) NOT NULL COMMENT '身份证号,全局唯一', phone VARCHAR(11) NOT NULL COMMENT '手机号,同时作为微信端登录账号', password VARCHAR(64) NOT NULL COMMENT '登录密码,BCrypt 哈希后存储', birth_date DATE NULL COMMENT '出生日期,建档时由身份证号推算,允许人工修正', manager_id BIGINT NOT NULL COMMENT '所属健康管理师,数据权限过滤列', status TINYINT NOT NULL DEFAULT 1 COMMENT '档案状态 1启用 0停用', height DECIMAL(5,1) NULL COMMENT '身高 cm', weight DECIMAL(5,1) NULL COMMENT '体重 kg', sbp SMALLINT NULL COMMENT '收缩压 mmHg', dbp SMALLINT NULL COMMENT '舒张压 mmHg', bmi DECIMAL(4,1) NULL COMMENT 'BMI,由身高体重计算后落库', past_history TEXT NULL COMMENT '既往史', family_history TEXT NULL COMMENT '家族史', allergy TEXT NULL COMMENT '过敏史', chronic TEXT NULL COMMENT '慢性病', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_archive_no (archive_no), UNIQUE KEY uk_id_card (id_card), UNIQUE KEY uk_phone (phone), KEY idx_manager (manager_id) ) COMMENT '会员档案';这张表的两个取舍点值得展开。第一,文档把身高、体重、血压、体温、腰围、臀围、BMI、血氧全部标成「字符型」,如果照抄成varchar,后面算 BMI、做趋势图、按血压区间筛人群时每一处都要强转,而且"145"和"145 "会被当成两个值。落库统一用数值类型,字符型只在导出报表时再格式化。第二,bmi是算出来的,但仍要落库——不是为了省计算,是为了让「按 BMI 筛超重人群」这种查询能走索引,实时算的话每次都得全表扫。
manager_id NOT NULL这一条是我特意加的。文档里「健康管理师」字段可为空,但一个没有归属管理师的档案在数据权限体系里是个黑洞:健康管理师看不到,管理员默认列表里也不该出现,最后只能靠人工排查。常见做法是建档时强制指派,确实要留空的场景(比如微信端自助注册还没分配),就先挂到一个虚拟的「未分配」账号上。
3.2 评估指标:参考值和临界值不是一回事
评估指标表只有六行字段,但它是整份文档里概念最容易混的地方。文档写得很明确:「左右临界值为实际评估对照值,正常值参考为显示值」,还有一条业务规则「同一指标针对性别不同,正常参考值会有不同」。这两句话合起来意味着,同一个「血红蛋白」指标,男性和女性要各存一行,判定逻辑读临界值,报告展示读参考值。
CREATE TABLE assess_indicator ( id BIGINT NOT NULL AUTO_INCREMENT, seq_no VARCHAR(32) NOT NULL COMMENT '指标序号', name VARCHAR(100) NOT NULL COMMENT '指标名称,如收缩压', ref_value VARCHAR(100) NULL COMMENT '正常参考值,仅用于报告展示,可能是 90-140 这类区间文本', unit VARCHAR(20) NULL COMMENT '单位,mmHg / mmol/L', gender TINYINT NOT NULL DEFAULT 0 COMMENT '0通用 1男 2女', left_value DECIMAL(10,2) NULL COMMENT '左临界值,判定下界', right_value DECIMAL(10,2) NULL COMMENT '右临界值,判定上界', PRIMARY KEY (id), UNIQUE KEY uk_name_gender (name, gender) ) COMMENT '评估指标';ref_value用varchar而不是数值,是因为参考值经常是区间文本或带比较符的描述,它是给人看的;left_value和right_value才是给程序做三分判定用的,必须能比较大小。唯一键带上gender是关键,只按name建唯一索引的话,录入女性血红蛋白那一行时会直接报主键冲突,这个坑我在两个项目里都见过。
判定逻辑本身不复杂,取到指标值v后跟左右临界值比:v < left_value为偏低,v > right_value为偏高,落在闭区间内为正常。真正的麻烦在于边界处理——临床参考值一般是左闭右开,而文档没写清楚。我的做法是统一用「左含右不含」,并在指标表加一个boundary_type字段留出扩展位,后面遇到必须闭区间的情况不用改代码。
3.3 预约列表与报告上传:排序方向决定业务对错
预约列表的字段表里有档案号、预约日期、姓名、手机号、预约类型、预约状态,还有一个容易被忽略的字段叫「预约类型」,取值是电话预约或微信预约。这个字段的价值不在展示,在于区分数据来源——微信端的预约是患者自己填的,电话预约是健康管理师代填的,两者的校验严格程度不一样,微信端可以强制填身份证,电话预约可能只有手机号。
报告上传模块的字段表更短:档案号、体检时间、姓名、手机号、检测套餐、报告状态,其中报告状态是个文件类型字段。业务规则里有一句「报告状态根据体检时间按顺序排列,最先检查的在最前面」,这句话翻译成 SQL 就是ORDER BY check_date ASC,不是默认的倒序。做列表页时默认按创建时间倒序是肌肉记忆,但这里业务明确要求正序,照默认写出来,最先做的体检反而排在最后,健康管理师按顺序随访时天天翻页。
CREATE TABLE exam_report ( id BIGINT NOT NULL AUTO_INCREMENT, archive_no VARCHAR(32) NOT NULL COMMENT '档案号', check_date DATE NOT NULL COMMENT '体检时间,列表按此列升序', setmeal_id BIGINT NULL COMMENT '检测套餐', file_url VARCHAR(255) NULL COMMENT '报告文件地址,不存二进制', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待上传 1已上传 2已作废', PRIMARY KEY (id), KEY idx_archive_check (archive_no, check_date) ) COMMENT '体检报告';文件本身不要进数据库。文档说「体检报告为手动上传」,一份 PDF 少则几百 K 多则几十 M,塞进 BLOB 列之后备份和迁移都会变成灾难,正确姿势是对象存储加file_url,数据库只留地址和状态。status里那个「已作废」不是文档写的,是我加的——报告传错了要能撤,直接物理删除会让干预方案里引用的报告突然 404。
4. 核心链路:预约、评估、干预、知识库的模块拆解
4.1 预约管理:检查项、组合、套餐的三层结构
预约模块的骨架是三层:检查项(单个检验项目)、检查组(若干检查项的组合)、套餐(若干检查组打包售卖)。文档的流程图里写了「维护体检项、组合、套餐」三个动作,对应三张主表和两张关联表。检查项和检查组是多对多,检查组和套餐也是多对多,中间表都要带排序字段,否则打印体检单时项目顺序每次都不一样。
预约设置是这条链上最容易做歪的地方。文档要求「日历预约」和「批量导入开诊时间」并存,本质是两种排班录入方式:一种按天点选,一种按 Excel 批量灌。排班表的结构应该是「日期 + 套餐 + 可约上限 + 已约数量」,预约时对已约数量做原子自增,而不是先查再改。
CREATE TABLE reservation ( id BIGINT NOT NULL AUTO_INCREMENT, archive_no VARCHAR(32) NOT NULL COMMENT '档案号', setmeal_id BIGINT NOT NULL COMMENT '套餐id', reserve_date DATE NOT NULL COMMENT '预约就诊日期', reserve_type TINYINT NOT NULL COMMENT '1电话预约 2微信预约', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待确认 1已确认 2已到诊 3已取消 4爽约', manager_id BIGINT NOT NULL COMMENT '跟进的健康管理师', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_date_status (reserve_date, status), KEY idx_archive (archive_no) ) COMMENT '体检预约';确认到诊这个动作要顺手建档。文档在会员管理里写「到诊注册用户为健康管理师直接添加患者信息;预约到诊用户为健康管理师完善信息」,两条路径最后都落到同一张会员档案表,区别只是档案号是系统生成还是沿用预约时给的。取消预约不要物理删除,状态改成 3 并记录取消时间,后面统计爽约率要用。
4.2 健康评估:问卷作答到报告生成的计算链
评估是这份 PRD 里少见的双入口模块:患者自己在微信端填,健康管理师在 PC 端给到诊患者填,两边产出的报告格式必须一致。文档的流程图里有个判断分支「是否做过健康评估」,做过就直接查结果,没做过才新建,所以评估记录表要有archive_no + questionnaire_id的联合索引,查最近一次评估时按时间倒序取一条。
报告生成的核心是把问卷作答映射到指标,再按 3.2 节那套临界值做三分判定。返回给前端的结构建议直接定型,别让前端二次拼装:
{ "archiveNo": "A20240415001", "questionnaireId": 12, "assessTime": "2024-04-15 10:32:00", "indicators": [ {"name": "收缩压", "value": 145, "unit": "mmHg", "result": "HIGH", "refValue": "90-140", "suggestId": 301}, {"name": "BMI", "value": 26.5, "unit": "kg/m²", "result": "HIGH", "refValue": "18.5-24", "suggestId": 305} ], "summary": "血压偏高、体重超重,建议控制钠摄入并增加有氧运动" }result只允许HIGH / NORMAL / LOW三个值,前端据此上色,不要直接返回中文。suggestId指向知识库里的评估建议,这样报告页点「查看建议」时有据可查,也避免了把建议文本硬编码在评估逻辑里——文档里评估建议是独立可维护的,硬编码等于把知识库白做了。summary是多个指标异常的合并话术,可以由模板拼出来,也可以让健康管理师手改后存快照。
4.3 健康干预:人群预分类到方案执行的两段式设计
干预模块的关键词是「预分类」和「模板」。患者做完评估,系统按结果给出一个人群分类建议,健康管理师确认或修改,这个字段在文档里叫「人群分类」,前面还有个「系统预分类」的标记。这两个字段要分开存:pre_category存系统算出来的,category存人工确认后的,否则明天改了评估算法,历史记录的「系统当时判断错了」这件事就再也追不回来了。
干预模板是提前维护的复用单元,字段包括模板标题、关键词、适用人群、方案目标、运动方案、膳食方案、健康方案、是否启用。它本质是三段内容加一个适用条件,制定方案时按人群分类匹配模板,一键带入后允许逐项调整。方案和模板之间是多对一,模板改了不影响已经生成的方案——这点必须靠生成时就拷贝内容来实现,如果是引用关系,模板一改,所有历史方案跟着变形,回访记录全部失效。
执行和回访是方案下的子记录。回访要有「下次回访时间」和「本次结论」,结论里区分有效、无效、失访,无效的走「调整方案」分支,把旧方案状态置为已中止并新建一版。整个干预模块的表不多,但状态流转的严谨度直接决定统计分析能不能出数。
4.4 知识库与统计分析:内容沉淀和运营看板
知识库包含评估建议、健康方案、运动项目库、膳食库、疾病库、宣教内容六类,文档里给了膳食库和运动项目库的字段。膳食库记编码、食品名称、所属类型、100g 含能量、蛋白质、脂肪、建议人群;运动项目库记编码、项目名称、运动强度、千步当量、运动时间、年龄区间、运动方法、注意事项。这两个库的结构高度相似,都是「编码 + 名称 + 若干属性 + 适用人群」,完全可以抽成一张主表加 JSON 扩展列,或者干脆做成两个独立表——前者省事,后者查询快,看数据量定,几千条以内两种都行。
统计分析目前只定义了工作量统计:按健康管理师统计干预方案总量、回访量、完成量,支持选时间区间,并根据权限限定查看人员。这里的口径问题比技术问题大:方案总量按创建时间算还是按开始时间算,回访量按计划次数还是实际完成次数算,完成量里含不含中止的。文档没写,就得在需求评审时定死并写进字段注释,否则三个人的报表能出三个数。
5. 避坑排查:字段约束、状态流转和权限口径的五个坑
5.1 坑一:出生日期既自动算又允许手改
现象是同一个患者,档案页显示 35 岁,评估报告里算出来 34 岁,客户来问到底几岁。原因是birth_date建档时由身份证推算写入,但文档标了「可修改」,某次人工纠错时只改了birth_date没同步身份证,或者改了身份证没重算日期,两处数据就分叉了。解决方式是只保留一个真相源:以id_card为准,birth_date仅作为缓存列,在身份证变更钩子里强制重算;如果业务确实需要覆盖(比如身份证登记有误),就加一个birth_date_overridden标记,报表口径统一优先读身份证推算值。
5.2 坑二:参考值和临界值混用导致判定翻车
现象是某位患者血压 138/88,报告标成「偏高」,健康管理师手动改成正常后,下次评估又变回偏高。原因是判定逻辑读了ref_value里的字符串"90-140"去做解析,而人工判断用的是left_value / right_value,两套标准打架。解决方式是判定只认临界值两列,ref_value全链路只出现在展示层,并且在指标维护页面加校验:ref_value的文本必须落在左右临界值区间内,录错时当场拦下。
5.3 坑三:预约状态存成中文文本
现象是上线三个月后做「本季度到诊率」,数字只有 30%,实际到诊率应该在 70% 以上。原因是status字段用varchar存中文,早期录入了「已到诊」「已到检」「到诊」三种写法,统计 SQL 只匹配了其中一种。解决方式是改tinyint加字典表,历史数据做一次映射清洗,并且在数据字典表里把预约状态锁定为五个枚举值,前端下拉只从这个字典取,从源头掐死自由输入。
5.4 坑四:知识库只在前端藏了按钮
现象是健康管理师账号登录后确实看不到膳食库的「新增」按钮,但用抓包工具重放请求,能直接删掉一条膳食记录。原因是权限只在模板层用th:if或v-if隐藏了按钮,接口层没做校验。解决方式是在知识库的写接口统一加权限注解,按钮隐藏只作为体验优化,不作为安全边界。这条在文档里其实写清楚了——「系统管理员可以对膳食库进行增删改查,健康管理师仅可以查看」——是权限矩阵,不是 UI 描述。
5.5 坑五:报告文件存本地路径
现象是服务迁到第二台机器后,历史体检报告全部打不开。原因是文件落在应用服务器的本地磁盘,file_url存的是/data/upload/2024/xxx.pdf这样的绝对路径。解决方式是接对象存储,数据库存相对 key,读取时由服务端签发临时访问地址;迁移前的老文件写个脚本批量上传并回写 key。文件这块还有个小坑:上传接口要限大小和 MIME 类型,PDF 和图片放行,其他一律拒绝,不然总有人传 zip 上来。
6. 二次开发:把 PRD 变成可运行系统的三个改造方向
拿到这份文档想二次开发,最省力的路径不是从头搭,而是先按第 3 章把六张核心表建出来——会员档案、评估指标、预约、报告、干预方案、知识库,再围着这六张表补 CRUD,能跑通一条「建档→预约→上传报告→评估→出方案」的最小闭环,剩下的都是加法。
第一个改造方向是把评估问卷从硬编码改成配置化。文档里风险评估、体质测评、心理测评各有一套问卷,如果每套写一个 Service,加第四套就得改代码。做法是抽questionnaire、question、option三张表,题目带类型(单选、多选、数值填空)和对应的指标 id,作答结果存 JSON,判定时按题目关联的指标去查assess_indicator的临界值。这样新增一套问卷只需要录数据,不动一行 Java。
第二个方向是把微信端和 PC 端收敛到同一套 API。两边做的事高度重合——预约、查报告、填评估——差别只在身份和可见范围。用同一个/api/**前缀,登录时签发带role和dataScope的 token,网关层按 scope 注入查询条件。这样改成小程序时,只需要换登录换取 openid 的那一段,业务接口原样复用。
第三个方向是统计口径落成汇总表。工作量统计如果每次打开页面都去group by干预方案表,数据量过十万就会明显卡顿。加一张stat_workload_daily,每天凌晨用定时任务按管理师维度汇总当天的方案数、回访数、完成数,页面查汇总表加时间区间过滤,秒开。下面是这张表的骨架,加了一个stat_date做分区键,方便后面按月归档:
CREATE TABLE stat_workload_daily ( id BIGINT NOT NULL AUTO_INCREMENT, stat_date DATE NOT NULL COMMENT '统计日期', manager_id BIGINT NOT NULL COMMENT '健康管理师', plan_count INT NOT NULL DEFAULT 0 COMMENT '当日新增干预方案数', visit_count INT NOT NULL DEFAULT 0 COMMENT '当日完成回访数', finish_count INT NOT NULL DEFAULT 0 COMMENT '当日结束方案数', PRIMARY KEY (id), UNIQUE KEY uk_date_manager (stat_date, manager_id) ) COMMENT '工作量日汇总';这张表的坑在于补数。定时任务挂了三天,第四天跑起来只补当天的,前三天就是空的。我一般会把它写成支持传日期参数的幂等任务,INSERT ... ON DUPLICATE KEY UPDATE,挂了自己手动补哪一天都行,不用改代码。
从那以后我每次接这类只有 PRD 没有源码的项目,都强制先把字段表逐行翻成 DDL 跑一遍建表,字段类型、唯一键、空值约束全部落地,再回头对需求——十次里有三次能在这个阶段发现文档自相矛盾的地方,比写到一半再返工便宜太多。希望帮到你。
本文还有配套的精品资源,点击获取