资讯动态

健康管理平台PRD落地:从角色权限到数据库与接口实战

发布时间:2026/10/7 3:44:37 来源:尧图企业网站定制
简介传智健康PRD文档1是一份针对健康管理系统的产品需求说明书面向产品经理、系统设计人员及健康服务平台开发团队用于梳理核心业务模块与操作流程。文档重点涵盖数据结构类型管理包括指标序号、名称、参考值、单位及适用性别等字段规则同时定义了用户、医护人员、管理员三类角色职责并详细展开会员管理、预约管理、健康评估、健康干预等功能的流程与特性可作为同类健康系统设计或PRD写作参考。资源包为单份docx文档大小890KB内容结构完整包含修订记录、目录及功能详情。目前已有599人学习下载适合正在规划健康管理类产品、需要参考业务逻辑与功能划分的产品或研发人员。1. 传智健康PRD文档1它不是需求清单而是技术合同拿到传智健康PRD文档1很多人第一反应是赶紧数功能点——注册、预约、报告、档案然后直接进数据库建表。但这类健康管理平台的PRD真正值钱的不是功能清单而是躲在“会员提交预约”“医生查看报告”这些句子后面的角色边界、状态流转、数据校验规则。传智健康PRD文档1本身就是一份技术合同前端能看到交互后端能拆出接口测试能写出边界用例。这篇笔记就按一套健康服务平台的常见落地路径把这个PRD翻译成架构、数据库、接口、验收方案让新手照着做让熟手看到参数与坑。2. 先拆业务流程再谈建表从传智健康PRD到系统架构2.1 从PRD里捞出“谁在用”角色与权限矩阵传智健康PRD文档1里最容易被忽略的是“人”。我习惯先打开文档把所有主语录一遍会员提交预约、医生查看健康档案、管理员维护体检项目。每出现一个主语就记一个角色。这不是写阅读笔记而是为后续的权限模型打底子。健康项目里角色常分三类会员、医生、管理员。会员管自己的预约和报告医生管客户档案和报告审核管理员管套餐、时段和订单。角色背后是权限集合不是数据库里的一个user_type字段。我给它的权限点拆到业务动作而不是页面路径。角色预约取消/退款上传报告查看报告维护体检项目会员YYNYN医生NNYYN管理员NYYYY这张表里最容易漏的是“退款”。PRD里写了“会员可取消预约”但没写取消后谁审核退款。如果只把订单状态改成“cancelled”退款链路就是空的。我一般把“取消”和“退款”拆成两个动作取消由会员触发退款由管理员确认两个动作分别配权限。不要小看这一步它决定了后端是写一个接口还是两个。有了角色矩阵再做菜单或接口列表就顺了。会员端的功能列表、医生端的数据范围、管理员的运维入口都能从矩阵里推导出来。矩阵还有一个好处做安全测试时直接对着矩阵造异常账号一个越权用例都跑不掉。2.2 把“体检预约”改成一张状态机图PRD里最典型的句子是“会员选择日期和时间段进行预约”。很多开发看这句话觉得简单但预约在真实系统里是一条状态链待支付、已支付、已预约、到检、报告生成、已取消、已退款。如果不提前定义状态机后面所有“能不能取消”“能不能上传报告”的判断都会散落成一堆if-else。我从传智健康PRD文档1的动词里提状态支付、取消、到检、生成报告。然后写一个不可变的状态转移表。这一步早期做后面所有Controller都只调一个入口。# state_config.py # 键是当前状态值是允许转移到的下一个状态 TRANSITIONS { PENDING_PAYMENT: [PAID, CANCELLED], PAID: [BOOKED, CANCELLED, REFUNDING], BOOKED: [CHECKED_IN, CANCELLED], CHECKED_IN: [REPORT_GENERATED, NO_SHOW], REPORT_GENERATED: [REPORT_VIEWED, REPORT_REJECTED], }这段代码的逻辑是任何状态变更都先过这张表源状态不在键里或目标状态不在值里直接抛异常。这样PRD里的业务规则被收敛到一个模块而不是散落在各个Service里。参数说明我把“REFUNDING”列在PAID的下一个状态但它不是终态后面还有“REFUNDED”这里没写全是因为退款走的是支付网关回调属于独立流程。状态机里最忌讳把网关状态混进业务状态比如把“支付中”放到订单状态里会让查询和统计都变复杂。写完状态机还要把它画成图吗我一般不用画图工具直接把状态和事件列成表格发给前端。前端根据状态机控制按钮显隐后端根据状态机做校验两边各守一道门。传智健康PRD里“支付完成后锁定时段”这句话落到状态机里就是预约记录从PAID进入BOOKED时才去扣减时段名额。这样避免了“先锁定再支付但没支付成功”的脏数据。2.3 从用例到服务划分六个模块就够了拆完角色和流程下一步是聚合功能。常见的健康管理平台做微服务的话六个模块足够会员服务、订单服务、档案服务、报告服务、排期服务、支付网关。如果是单体应用这些模块至少也要在代码层面分清楚包名和表前缀。关键原则是一个PRD功能点只能落在一个服务里。比如“预约”归订单服务“上传报告”归报告服务“健康指标展示”归档案服务。传智健康PRD文档1里有个“健康评估”功能是典型容易放错位置的看着像报告实际依赖历史档案和医生结论我倾向于放档案服务这样报告服务保持纯存储和查询将来对接第三方LIS系统时不用动评估逻辑。服务拆分之后还要定义服务之间怎么通信。常见的做法是同步HTTP调用但遇到“支付成功后需要生成预约记录”这种跨服务流程我建议用事件异步。支付网关回调订单服务订单服务发出OrderPaidEvent排期服务监听后扣减名额。如果把扣减名额写在支付回调的同步流程里一回数据库慢了支付网关会超时重复回调处理起来很恶心。到这里架构层的角色、状态、服务边界都有了。我不建议直接画几百行的架构图先把角色矩阵和状态机贴到文档里评审一次就够。接下来才是硬碰硬的数据库设计。3. 从PRD到数据库设计健康档案与预约订单的表结构3.1 用户主数据与健康档案主表细分拆开传智健康PRD文档1里“个人健康档案”通常包含基本资料、既往病史、过敏史、生活习惯。如果一股脑全塞进users表每加一个字段就要改表结构而且业务边界混乱。常见做法是拆成两张表users存登录、身份、手机号health_profile存每次体检后的结构化指标。下面是一份可直接改用的建表语句。CREATE TABLE users ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, mobile VARCHAR(20) NOT NULL, user_type TINYINT NOT NULL DEFAULT 1 COMMENT 1-会员 2-医生 3-管理员, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-正常 0-禁用, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, UNIQUE KEY uk_mobile (mobile) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE health_profile ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL, blood_pressure_systolic SMALLINT UNSIGNED COMMENT 收缩压单位mmHg, blood_pressure_diastolic SMALLINT UNSIGNED COMMENT 舒张压单位mmHg, fasting_glucose DECIMAL(4,2) COMMENT 空腹血糖单位mmol/L, height_cm DECIMAL(5,2) COMMENT 身高单位cm, weight_kg DECIMAL(5,2) COMMENT 体重单位kg, profile_date DATE NOT NULL COMMENT 档案记录日期, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, CONSTRAINT fk_profile_user FOREIGN KEY (user_id) REFERENCES users(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这两条SQL的逻辑是users表不存身高体重因为指标会按日期产生多份记录应该放在health_profile里并按profile_date排序。新来一个会员可能每年体检一次档案表就会有多条记录。如果塞在users表报表统计“最近一次血压”就要写复杂的窗口函数不如直接按日期查。参数说明血压收缩压和舒张压拆成两个字段为后续报表排序、趋势画图省掉分隔步骤。fasting_glucose用DECIMAL(4,2)能存最大99.99足够血糖值使用。最重要的是height_cm和weight_kg后面都带单位注释这是从PRD里“身高170”“体重60”这种话里逼出来的约定——后面避坑章节还会展开。外键在这里有存在价值因为健康档案必须挂在一个有效会员下分库分表之前先保留强约束。3.2 预约订单与报告表用状态字段和版本号挡并发预约和报告是健康平台里最核心的两张表。PRD里“预约成功后生成待支付订单支付完成后锁定体检时段”这句话落地后至少要两张表appointment_order和report通过order_id关联。CREATE TABLE appointment_order ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL COMMENT 业务订单号, user_id BIGINT UNSIGNED NOT NULL, appointment_date DATE NOT NULL, time_slot VARCHAR(10) NOT NULL COMMENT 如09:00-10:00, package_id INT NOT NULL COMMENT 体检套餐ID, amount_cents INT UNSIGNED NOT NULL COMMENT 金额单位分, status VARCHAR(30) NOT NULL DEFAULT PENDING_PAYMENT, version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE report ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, order_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL, report_no VARCHAR(32) NOT NULL, pdf_path VARCHAR(255) NULL COMMENT 对象存储相对路径, structured_data JSON NULL COMMENT 结构化指标结果, status VARCHAR(30) NOT NULL DEFAULT GENERATING, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, CONSTRAINT fk_report_order FOREIGN KEY (order_id) REFERENCES appointment_order(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里要重点说amount_cents。PRD里写“套餐价格500元”如果直接用DECIMAL(10,2)存订单表可能出现浮点误差支付网关回调对账时对不上。费用字段一律用整数分存储前端展示时再除以100。这是一个成熟系统的血泪经验。version字段是留给乐观锁的。更新订单状态时SQL里会带WHERE id ? AND version 旧版本号更新成功version1。这个字段在并发取消、并发回调场景下能挡住脏写。report表里的structured_data用JSON存结构化指标适合“报告生成后前端直接展示数值”的需求而不是每次都要下载PDF去解析。pdf_path存的是相对路径不是绝对地址这样换存储环境不用改数据库。3.3 不要把体检报告当附件存储这是传智健康PRD同类项目里最常见的坑开发看到“上传报告”就建一个File表把PDF存到本地磁盘数据库存file_path。小并发下没问题但一旦要部署多个节点报告文件散在各台机器上用户访问时经常404。我建议把PDF放到对象存储数据库里存相对路径。例如pdf_path存“2024/12/order_12345.pdf”真正的完整URL交给配置项去拼。这样换存储桶或迁移时只改配置不动表。PDF文件本身只保留一份结构化指标也存一份读取体验完全走接口避免前端直接打开慢速文件流。注意PRD里如果写“报告只能下载一次”很多人会直接去文件系统删文件。这是错的删完以后用户再次点击就404还会留下“为什么我下载失败了”的投诉。正确做法是在report表增加一个downloaded_at字段查询时判断是否为空。结合前面状态机的REPORT_VIEWED状态下载一次后状态变更后续请求在接口层就被拦截。这里不需要存储引擎层面的特殊操作数据库的一个字段就能解决。4. 接口设计把PRD里的动作变成REST API4.1 从“我要预约”到POST /api/appointmentsPRD里的业务动作落到接口层要明确路径、方法、参数、幂等性。传智健康PRD文档1里“会员提交预约”对应创建一个订单。我一般设计成POST /api/appointments而不是POST /api/order因为接口语义面向业务名词不是数据表名。先给一个FastAPI的入口函数示例重点是校验交给入口事务交给服务。from fastapi import APIRouter, HTTPException router APIRouter(prefix/api/appointments, tags[预约]) def create_appointment(user_id: int, appointment_date: str, time_slot: str, package_id: int): # 接口层只做格式校验不直接写库 if len(time_slot) ! 11 or - not in time_slot: raise HTTPException(status_code422, detailtime_slot 格式错误) return create_appointment_service( user_id, appointment_date, time_slot, package_id )这段代码演示了入口层的关键思路不要在接口里拼INSERT语句而是把参数原样传给服务层。真正的原子操作在create_appointment_service里用数据库事务包裹订单创建和时段扣减。参数说明time_slot我用“09:00-10:00”这种固定长度字符串接口层先做格式校验避免脏数据进库。重复提交是预约系统的高频问题。用户双击提交表单可能创建两笔订单。常见做法是前端生成一个幂等键放在请求头Idempotency-Key里后端收到后先查Redis有就直接返回之前的结果没有才处理。这个过程在PRD里不会写但确实关系到钱我建议提前加进去。4.2 报告查询接口缓存键必须带上用户维度报告查询是健康类平台最频繁的读接口。PRD里规定“医生可查看会员报告”是一回事缓存策略又是另一回事。如果直接拿report_id做缓存键医生A首次访问后缓存了一个包含个人敏感信息的对象医生B再次访问时会直接命中同一份缓存这就是数据泄露事故。常见做法是把缓存键设计成“资源ID访问者ID”甚至带上数据权限版本号。下面演示读取路径。def get_report(report_id: int, viewer_id: int): # 先校验数据权限再做缓存顺序不能反 if not check_data_permission(viewer_id, report_id): raise HTTPException(status_code403, detail无权查看该报告) cache_key fcache:report:{viewer_id}:{report_id} cached redis.get(cache_key) if cached is not None: return json.loads(cached) result query_report_from_db(report_id) redis.set(cache_key, json.dumps(result), ex600) return result这段代码的关键是“先权限后缓存”。哪怕缓存已经命中也要先过一遍权限判断。否则缓存就成了攻击入口。参数说明ex600表示缓存10分钟具体数值看PRD是否要求“报告更新后立即可见”。如果要求实时生效就不能简单依赖过期时间而要在报告状态变更时主动删除该报告相关缓存。我一般用一个版本号字段cache:report:{report_id}:{viewer_id}:{report_version}report表中每更新一次structural_dataversion加一缓存键自然失效。这种方式比删除所有缓存再回源更可控也不会因为通配符删除造成瞬时穿透。4.3 权限接口用角色和数据范围做两道闸传智健康PRD文档1里常见一句“医生只能查看自己客户的报告”。这句话拆开是两个权限功能权限是“医生能查看报告”数据权限是“只能查看与自己绑定的会员”。只做后面一条是错的只做前面一条更是事故。我通常用装饰器来实现两道闸。Python示例def require_scope(scope: str): def decorator(func): def wrapper(viewer_id, target_user_id): # 第一道闸角色功能权限 check_role_permission(viewer_id, scope) # 第二道闸数据范围权限 if not is_bound_to(viewer_id, target_user_id): raise HTTPException(status_code403, detail数据越权) return func(viewer_id, target_user_id) return wrapper return decorator这里scope是PRD里的功能点名称例如“查看报告”。check_role_permission查的是role_permission表is_bound_to查的是医生与会员的绑定关系表。很多项目把两道闸写成一句SQL结果管理员或测试账号直接拿到全量数据。拆开以后功能权限测试用例可以测“非医生不能看”数据权限测试用例可以测“医生不能看别人的客户”互不干扰。接口层设计到这里有了预约创建、报告查询、权限控制。下一步必须解决那些从PRD字面看不出、上线后才会爆炸的坑。5. 传智健康PRD落地避坑4个实际翻车场景和排查方法5.1 预约时段“看起来有位置提交就失败”现象会员在时段列表看到“09:00-10:00”可约提交订单后却被提示“该时段已满”。原因PRD只写“时段可约”没定义可约数量的扣减方式。开发最初是页面加载时查一次总量预约时再做一次SELECT两次之间多个用户同时提交名额就超卖了。这是典型的读改写并发问题。解决把时段剩余名额做成原子扣减。常见做法是用一条UPDATE语句把剩余数作为条件更新。示例UPDATE schedule SET remaining remaining - 1 WHERE schedule_id #{scheduleId} AND remaining 0;如果受影响行数为0说明名额已用完直接返回“时段已满”。这里的schedule表要提前把每个时段的初始名额算好并在每天早上定时重置。注意不要在扣减后再去SELECT验证否则又回到非原子路径。我还会在代码里加日志把schedule_id和请求时间打印出来到时发现“名额衰减但订单没创建”时能定位到是哪一步回滚。5.2 报告状态在多端显示不一致现象会员端显示“报告生成中”医生端已经显示“已生成”刷新后两边状态又不一样。原因报告状态被分散在多个服务里更新。上传报告的回调写一次状态审核接口又写一次两个服务都有权限改report.status互相不协调。解决把所有状态更新收口到状态机模块禁止其他服务直接UPDATE report.status。统一入口后每次状态变更都要带当前事件。示例report_service.transition(report_id, REPORT_GENERATED, operator_id)transition内部会先查当前状态再用乐观锁执行UPDATE report SET status REPORT_GENERATED, version version 1 WHERE id ? AND version 旧版本。如果更新失败说明有其他并发操作抢先直接触发重试或告警。另外建一张report_status_log表记录report_id、from_state、to_state、operator_id、operated_at这样出现争议时能回放每一次状态变化。排查时先看日志表再看业务代码千万不要直接在线上改状态。5.3 健康指标单位不一致现象一张报告里血糖值是“5.5”另一张是“99”前端列表排序时数值完全错乱。原因PRD里只写了“血糖”没写单位。医生录入时有人按mmol/L有人按mg/dL系统没有存储原始单位显示时也不知道该除以18还是乘以18。解决在系统内定义一个基准单位所有传入的指标先转换成基准值再存库。血糖统一以mmol/L为基准接收数值后先校验范围。如果值大于30判定单位为mg/dL自动除以18换算为mmol/L再写入。同时health_profile表加一个unit_note VARCHAR(20)记录原始单位方便核查。前端展示时可以再按用户偏好转成mg/dL但数据库不存展示值。这个单位问题如果不在建表阶段解决后面做健康趋势分析时会直接翻车。5.4 医生能看所有会员报告现象安全测试人员用普通医生账号直接调用报告详情接口能返回任意会员的报告。原因接口层只判断了“医生角色可以查看报告”没有校验当前医生与目标用户的关系。这是最典型的越权漏洞而且越权路径往往藏在后端接口里前端菜单根本没有这个入口。解决在数据访问层增加数据范围过滤器。如果用的MyBatis可以在拦截器里自动拼接AND doctor_id #{currentUserId}如果REST风格就在服务层调用ensureBound。关键是功能权限和数据权限分开两个判断都通过才放行。我还会专门写一个越权测试用例登录A医生账号传入B用户的user_id断言HTTP 403。这个用例应该放进自动化测试套件里每次发版前跑一遍防止回归。6. 从PRD到验收用需求追踪矩阵守住底线传智健康PRD文档1里每个需求编号都应该有一行对应的测试用例。所有人都不想在临近上线时才发现“预约取消”这个需求根本没人测。我习惯在评审通过后立刻建立需求追踪矩阵RTM表格不复杂四列够了。PRD编号需求描述测试用例状态R-101会员可预约未来7天体检TC-预约-001 测试7天边界断言第8天不可选通过R-102支付成功后锁定时段TC-预约-002 并发提交同一时段断言仅1单成功通过R-103医生只能查看本客户报告TC-权限-003 A医生访问B客户报告断言403通过这个矩阵的价值是让“需求—用例—代码”逐行对应。我前期经常漏掉状态机的异常分支比如支付成功但取消窗口已过后来就规定每个状态转移必须至少一个成功用例和一个失败用例。PRD里“取消后不能重复退款”这条不变量我用数据库唯一约束加幂等键挡住测试用例只需要重复提交两次取消接口第二次必须返回失败。我的个人习惯是上线前不只是看所有用例通过还要求后端开发把接口里所有“非PRD字段”列出来逐个说明来源。传智健康PRD文档1里写的是“报告下载”但接口请求参数里如果多了一个is_forced_download就必须有人解释这是干什么用的。有一次就是因为多了一个参数医生端越权查询被实锤从那以后我给自己定下规矩需求追踪矩阵必须和接口定义一起评审。这个习惯可能有点繁琐但值得希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑