Files
lehuo/docs/04_DATA_MODEL.md
T

522 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04 数据模型
## 1. 通用字段规范
核心表统一包含:
- id: bigint/uuid(二选一后固定;建议 bigint 内部主键 + public_id 随机ID)
- public_id: varchar,外部暴露,禁止连续可枚举
- created_at
- updated_at
- created_by(后台业务表适用)
- updated_by
- deleted_at(需要软删除的表)
时间统一数据库 UTC 存储,展示按 Asia/Shanghai。
---
## 2. 核心实体
### users
微信/后台统一用户主体,避免重复身份系统。
- id
- public_id
- status
- user_type
- mobile(加密/脱敏策略)
- nickname
- avatar_url
- last_login_at
### wechat_identities
- user_id
- openid_ciphertext(AES-256-GCM)
- openid_hash(HMAC-SHA-256,仅用于等值查询)
- unionid_ciphertext nullable
- unionid_hash nullable
- appid
- created_at
唯一索引 `(appid, openid_hash)`;日志和 API 均不得返回 openid/unionid。
### staff_profiles
- user_id
- employee_no nullable
- display_name
- department
- status
工作人员显示名不是服务对象敏感档案字段;严禁把其扩展为手机号、证件号或详细住址。服务对象称呼仍按下文要求加密。
### roles / permissions / user_roles / role_permissions
标准 RBAC。Phase 1 预置 `VOLUNTEER`、`STAFF`、`PROJECT_MANAGER`、`ADMIN`、
`SUPER_ADMIN`,权限码由 shared contracts 统一定义。角色替换必须在事务中写审计,并禁止移除最后一名有效超级管理员。
### admin_credentials
- user_id(唯一)
- username(唯一)
- password_hash(Argon2id)
- failed_login_count
- locked_until nullable
- password_changed_at
### auth_sessions
- public_id(随机 UUID)
- user_id
- channel(WECHAT/ADMIN)
- token_hash(仅保存带 pepper 的 HMAC,不保存明文 token)
- expires_at
- last_used_at nullable
- revoked_at nullable
同一用户、同一渠道重新登录时撤销旧会话;过期、撤销或非 ACTIVE 用户的会话均无效。
---
## 3. beneficiary_profiles
服务对象档案:
- id
- public_id
- display_name_encrypted / display_name_hash
- gender nullable
- birth_year nullable
- mobile_encrypted / mobile_hash nullable
- region_code
- address_detail_encrypted nullable
- primary_category
- key_care_flag
- is_minor
- status
- notes_encrypted nullable
- created_by
- timestamps
不要默认收集完整身份证。
---
## 4. beneficiary_contacts
- beneficiary_id
- public_id
- name_encrypted
- relation
- mobile_encrypted / mobile_hash
- is_guardian
- is_emergency_contact
- consent_record_id nullable
---
## 5. beneficiary_tags / tags
标签:
- 独居
- 高龄
- 行动不便
- 定期探访
- 困境儿童
等。
标签只能帮助服务,不应成为歧视性画像。
---
## 6. service_applications
- id
- public_id(随机 UUID,仅此字段对外暴露)
- application_no(随机业务编号)
- applicant_user_id
- beneficiary_id nullable
- consent_id
- source_follow_up_task_id nullable(继续帮扶来源,唯一)
- parent_application_id nullable(原申请)
- client_request_id
- request_fingerprint(域隔离 HMAC-SHA-256)
- apply_mode
- service_category
- beneficiary_name_encrypted(AES-256-GCM)
- contact_mobile_encrypted(AES-256-GCM)
- contact_mobile_hash(域隔离 HMAC-SHA-256,仅用于等值查询)
- region_code
- description_encrypted(AES-256-GCM)
- preferred_contact_time nullable
- needs_home_visit nullable
- mobility_difficulty nullable
- status
- source
- submitted_at
- accepted_at nullable
- closed_at nullable
- timestamps
首次申请在 Phase 2 创建时不自动建立 beneficiary 档案;Phase 3 经人工核验后关联。Phase 4 的继续帮扶申请保留 `parent_application_id` 和 `source_follow_up_task_id`,并创建新的独立 consent,不修改或复用原申请授权。
索引:
- application_no unique
- `(applicant_user_id, client_request_id)` unique,保证数据库级幂等
- applicant_user_id + submitted_at
- status + submitted_at
- service_category + submitted_at
- contact_mobile_hash
---
## 7. service_application_status_history
- application_id
- from_status
- to_status
- operator_user_id
- reason_code
- created_at
不可更新,只追加。
---
## 8. service_cases
- id
- public_id
- case_no
- application_id
- accept_request_id / accept_request_fingerprint(接单幂等)
- beneficiary_id
- owner_staff_id
- category
- priority
- status
- planned_start_at nullable
- planned_end_at nullable
- service_location_encrypted nullable
- summary_encrypted
- result_summary_encrypted nullable
- follow_up_plan_encrypted nullable
- completed_at nullable
- complete_request_id / complete_request_fingerprint nullable(完成服务幂等)
- timestamps
---
## 9. case_assignees
支持多人协作:
- case_id
- user_id
- assignee_type STAFF/VOLUNTEER
- role
- assigned_at
- removed_at nullable
---
## 10. service_records
- id
- public_id
- case_id
- beneficiary_id
- service_start_at
- service_end_at nullable
- service_type
- client_request_id / request_fingerprint(同一创建者范围内幂等)
- content_encrypted
- result_encrypted
- follow_up_suggestion_encrypted nullable
- beneficiary_confirm_status nullable
- created_by
- timestamps
`service_record_revisions` 在修改前保存加密快照、原 revision 和修改人;更新使用 `expectedRevision` 乐观锁,防止覆盖并支持审计追溯。
---
## 11. follow_up_tasks
- id
- public_id / follow_up_no(均不可预测)
- beneficiary_id
- case_id nullable
- owner_staff_id
- due_at
- follow_up_type(SERVICE_COMPLETION / KEY_CARE / MANUAL)
- status(PENDING / COMPLETED / CANCELLED)
- source(AUTO_SERVICE_COMPLETION / STAFF_MANUAL / KEY_CARE_PLAN)
- reason_encrypted
- result_code nullable
- result_note_encrypted nullable
- create_request_id / create_request_fingerprint
- complete_request_id / complete_request_fingerprint nullable
- completed_at nullable
- created_by / updated_by / timestamps
自动回访与服务完成在同一事务创建。人工回访以 `(created_by, create_request_id)` 唯一;完成请求保存指纹并安全重放。
## 12. follow_up_status_history
- follow_up_task_id
- from_status nullable
- to_status
- operator_user_id
- reason_code
- created_at
只追加,不更新。继续帮扶申请通过 `service_applications.source_follow_up_task_id` 回指来源,原申请历史不覆盖。
---
## 13. volunteers / volunteer_skills
volunteers:
- user_id
- real_name
- mobile
- region_code
- availability_note
- status
- approved_by nullable
- approved_at nullable
skills:
- volunteer_id
- skill_code
---
## 14. projects
- id
- public_id
- project_no
- client_request_id / request_fingerprint
- name
- category
- summary
- content
- cover_file_id
- manager_user_id
- start_date
- end_date
- target_people nullable
- status
- is_public
- sort_order
- timestamps
`(created_by_id, client_request_id)` 唯一;`project_status_history` 只追加记录创建、发布和完成。`service_cases.project_id` 可空,用于把既有工单归集到项目但不改变工单事实。
---
## 15. activities
- id
- public_id
- activity_no
- client_request_id / request_fingerprint
- project_id nullable
- title
- summary
- content
- cover_file_id
- start_at
- end_at
- registration_deadline
- location_text
- capacity nullable
- status
- published_at nullable
- timestamps
`(created_by_id, client_request_id)` 唯一;`activity_status_history` 只追加记录活动状态变化。
---
## 16. activity_registrations
- activity_id
- user_id
- volunteer_id
- public_id / client_request_id / request_fingerprint
- status
- check_in_status
- registered_at
- reviewed_by / reviewed_at / review_note_encrypted nullable
- checked_in_at nullable
- checked_in_by nullable
- service_minutes nullable
- service_minutes_recorded_at / service_minutes_recorded_by nullable
唯一 `(activity_id,user_id)` 防重复报名。
`activity_registration_history` 只追加保存报名审核、取消、签到和服务时长变更前后事实,并以全局请求 UUID 唯一约束支持安全重放。
志愿者资料的姓名、手机号、可参与时间和审核备注加密;手机号与姓名仅保存域隔离 HMAC 查询值。`volunteer_status_history` 只追加,并与志愿者角色授予/撤销事务一致。
---
## 17. content_posts
- id
- public_id
- type
- title
- summary
- content
- cover_file_id nullable
- status DRAFT/REVIEW/PUBLISHED/ARCHIVED
- author_user_id
- reviewer_user_id nullable
- published_at nullable
- timestamps
---
## 18. files
只保存元数据:
- id
- public_id
- storage_provider
- bucket
- object_key
- original_filename_encrypted
- mime_type
- size
- sha256
- business_type
- uploader_user_id
- status
- created_at
`service_record_files` 关联服务记录与已确认文件。对象 key 使用随机值,上传确认时复核大小、MIME 和可选 SHA-256;不匹配文件进入隔离状态。
Phase 5 允许 `files.case_id` 为空,并通过 `business_type=PROJECT_COVER/ACTIVITY_COVER` 关联公开封面;封面仍经同一私有对象存储 Adapter 生成短时读取地址,不保存永久公网 URL。
前端不得直接拼接永久公网 URL。
---
## 19. consents
- id
- public_id
- user_id nullable
- beneficiary_id nullable
- consent_type
- version
- granted_by
- granted_at
- revoked_at nullable
- evidence_json(仅保存必要证明)
- ip_hash nullable
- created_at
Phase 2 每次申请显式要求 `privacyConsent=true`,记录隐私同意版本、授权主体、授权时间、最小证据和 IP HMAC;不得在证据中重复保存申请敏感明文。
---
## 20. audit_logs
只追加:
- id
- request_id
- actor_user_id
- actor_role
- action
- resource_type
- resource_public_id
- result
- metadata_json(不得含敏感明文)
- ip_hash
- user_agent_summary
- created_at
---
## 21. notifications / notification_deliveries
通知实体与发送记录分开,支持重试和追踪。
Phase 6 已落地 `content_posts`、只追加的 `content_post_status_history`、`notifications`、`notification_preferences` 和 `notification_deliveries`。消息以 `(user_id,event_key)` 唯一防重,微信投递以 `(notification_id,channel)` 唯一,失败最多重试五次后进入 `DEAD`。
Phase 7 不建立可人工修改的统计快照;九项 V1 指标直接从申请、工单、服务记录、服务对象、志愿者、活动和报名服务分钟事实计算。
---
## 22. 数据库规则
1. 必须使用 FK 或由 ORM/服务保证等价完整性;禁止无约束“凭感觉关联”。
2. 唯一业务编号必须 unique。
3. 常查字段建组合索引。
4. 禁止对大文本字段滥建索引。
5. 金额如未来出现用 decimal,禁止 float。
6. 所有枚举在 contracts 中有单一事实来源。
7. migration 不允许 destructive change 直接上线。
8. 删除列采用“停用 → 迁移 → 验证 → 后续版本删除”。
---
## 23. financial_disclosures
- id / public_id(随机 UUID)
- client_request_id / request_fingerprint(创建幂等)
- title / summary nullable
- rich_text(后端白名单清洗后的富文本)
- status:DRAFT / PUBLISHED / ARCHIVED
- is_pinned / sort_order / revision
- published_at nullable
- created_by / updated_by / timestamps
`financial_disclosure_status_history` 只追加保存创建、发布和归档事实,请求 UUID 全局唯一。已发布文档禁止覆盖编辑,只能归档;草稿编辑使用 revision 乐观锁。
---
## 24. 旧物善用 Phase 1
- `old_item_donations`:随机 `public_id`、非连续公益编号、捐赠人、幂等键、品类、新旧程度、数量、方式、状态、revision、定向需求、投递点、人工分配人员和真实帮扶人数。
- `old_item_donation_status_history`:只追加保存初始提交和每次状态变化,`request_id` 全局唯一。
- `old_item_donation_files`:关联最多六张已确认的 `OLD_ITEM_DONATION_IMAGE` 私有文件。
- `old_item_needs`:随机公开 ID、品类、区域、受助群体、需求量、已满足量、优先级、排序、发布状态和 revision。
- `old_item_need_status_history`:只追加保存需求发布与下架事实。
- `old_item_drop_off_points`:后台维护的公开投递名称、区域、地址、开放时间、启停和排序。
联系人、手机号、上门取件地址、物品描述、驳回原因和内部轨迹备注加密保存;手机号另存域隔离 HMAC。列表不公开敏感明文,管理端详情读取写审计。全部外键使用 `RESTRICT`,公开资源不暴露自增主键。
---
## 25. 旧物善用 Phase 2 物资台账
- `old_item_inventory_imports`:记录随机公开 ID、创建人幂等键、请求指纹、加密原文件名和导入行数;不保存原 Excel 文件。
- `old_item_inventory_items`:随机公开 ID、非连续台账编号、可选来源捐赠/导入批次、品类、名称、数量、单位、仓位、状态、定向需求、可选受助对象、帮扶区域、加密备注、落地时间和 revision。每个来源捐赠最多绑定一个物资批次,避免重复入库。
- `old_item_inventory_events`:只追加保存入库、分拣、消毒、出库、落地节点,请求 UUID 全局唯一,记录前后状态、时间、位置、加密备注和操作人。
台账状态严格为 `IN_STOCK → SORTED → DISINFECTED → OUTBOUND → DELIVERED`,不允许跳级、回退或覆盖事件。Excel 批量入库在一个事务中创建导入批次、所有物资行、入库事件和审计,任一行无效时整批回滚。受助对象只保存既有内部外键;小程序仅返回流转状态、时间和出库/落地的粗粒度地区。
---
## 26. 旧物善用 Phase 3 荣誉证书与积分
- `old_item_reward_configs`:不可变规则版本,保存证书标题/正文/颁发方/法律声明、四类物资每件积分、单笔积分上限和当前激活标识;新版本不覆盖历史版本。
- `old_item_certificates`:随机公开 ID、非连续证书编号、来源捐赠、规则版本、加密收件人称呼、完整模板快照、签发/撤销幂等指纹、签发与撤销操作者及时间。每笔捐赠最多一张当前有效证书,补发会撤销旧证书但不删除历史。
- `old_item_point_accounts`:每个用户一个荣誉积分账户,保存余额和 revision;余额不得小于零。
- `old_item_point_entries`:只追加积分流水,记录获得/人工校正、变更值、变更后余额、来源捐赠/规则版本、幂等指纹、加密原因和操作人;来源捐赠唯一,防止重复计分。
捐赠完成、证书签发、积分账户更新、积分流水和审计在同一事务提交。积分没有货币价值,不建立兑换订单、商品、转让、提现、到期或排行榜数据结构。