Files
lehuo/docs/04_DATA_MODEL.md
T

488 lines
11 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 乐观锁。