# 丹阳公益婚恋服务平台 # V1.0 数据库详细设计与核心数据模型 版本:V1.0 数据库建议:PostgreSQL 适用范围:微信小程序用户端、公益红娘工作台、运营管理后台 设计目标:结构清晰、敏感数据分层、状态可追溯、支持事务、便于后续扩展 --- # 第一章 数据库设计原则 V1.0数据库设计必须坚持: - 账号数据与婚恋资料分离 - 公共资料与敏感资料分离 - 用户状态与关系状态分离 - 审核数据可追溯 - 风控数据独立 - 聊天数据独立 - 活动数据独立 - 红娘服务独立 - 权限与组织独立 - 重要业务状态不通过删除实现 - 核心业务使用数据库约束兜底 - PostgreSQL作为业务真实数据源 不建议: - 一个users表塞所有字段 - 使用大量JSON替代清晰结构 - 核心关系依赖Redis - 不做唯一约束 - 只依赖前端校验 --- # 第二章 核心数据域 V1.0数据库建议划分为: ```text 01 用户账号域 02 实名认证域 03 婚恋资料域 04 人工审核域 05 推荐与关系域 06 聊天消息域 07 公益活动域 08 公益红娘域 09 举报风控域 10 系统通知域 11 组织权限域 12 审计日志域 13 配置与统计域 ``` --- # 第三章 核心表总览 建议V1.0核心表: ```text users user_identities user_profiles user_preferences user_interests interest_dictionary user_photos user_privacy_settings review_tasks review_items review_reasons recommendation_records user_relations matches relationship_success_requests conversations conversation_members messages activities activity_registrations activity_feedback matchmaker_services matchmaker_recommendations matchmaker_followups reports report_evidences user_risk_profiles risk_events risk_actions appeals notifications organizations operators roles permissions role_permissions operator_roles audit_logs sensitive_access_logs system_configs match_rule_configs ``` --- # 第四章 通用字段规范 大部分业务表建议包含: ```text id created_at updated_at ``` 需要软删除的表: ```text deleted_at ``` 业务状态不要用: ```text is_deleted = 0/1 ``` 优先采用: ```text deleted_at IS NULL ``` --- # 第五章 ID设计 推荐: > UUID / UUIDv7 或: > 应用生成的分布式唯一ID。 不要让外部接口直接暴露连续自增ID。 例如: ```text user_id = 018f... ``` 或者: ```text usr_xxxxxxxxx ``` 如果数据库内部使用BIGINT,也建议API层映射公共ID。 --- # 第六章 users 用户账号表 职责: > 只保存账号和平台级状态。 建议结构: ```sql CREATE TABLE users ( id UUID PRIMARY KEY, wechat_openid VARCHAR(128) NOT NULL, wechat_unionid VARCHAR(128), phone VARCHAR(32), account_status VARCHAR(32) NOT NULL DEFAULT 'normal', relationship_status VARCHAR(32) NOT NULL DEFAULT 'searching', recommendation_enabled BOOLEAN NOT NULL DEFAULT TRUE, last_login_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); ``` --- # 第七章 users约束 唯一约束: ```sql CREATE UNIQUE INDEX uk_users_openid ON users(wechat_openid) WHERE deleted_at IS NULL; ``` 手机号: ```sql CREATE UNIQUE INDEX uk_users_phone ON users(phone) WHERE phone IS NOT NULL AND deleted_at IS NULL; ``` unionid: 如业务使用: ```sql CREATE UNIQUE INDEX uk_users_unionid ON users(wechat_unionid) WHERE wechat_unionid IS NOT NULL AND deleted_at IS NULL; ``` --- # 第八章 users状态约束 account_status: ```text normal reviewing_risk limited suspended banned closed ``` relationship_status: ```text searching getting_to_know paused matched_successfully service_exited ``` 建议使用: > VARCHAR + CHECK 而不是PostgreSQL ENUM。 原因: 后续状态扩展和数据库迁移更方便。 --- # 第九章 user_identities 实名认证表 高度敏感。 与users严格分离。 字段建议: ```sql CREATE TABLE user_identities ( id UUID PRIMARY KEY, user_id UUID NOT NULL UNIQUE, real_name_enc TEXT NOT NULL, id_number_enc TEXT NOT NULL, id_number_hash VARCHAR(128) NOT NULL, gender VARCHAR(16), birth_date DATE, verification_status VARCHAR(32) NOT NULL, provider VARCHAR(64), provider_request_id VARCHAR(128), verified_at TIMESTAMPTZ, failed_reason_code VARCHAR(64), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT fk_identity_user FOREIGN KEY (user_id) REFERENCES users(id) ); ``` --- # 第十章 实名敏感字段 ```text real_name_enc id_number_enc ``` 必须: > 加密后存储。 同时建议存: ```text id_number_hash ``` 用于: - 防重复身份证注册 - 查询重复身份 但不能通过Hash还原身份证。 --- # 第十一章 身份证重复约束 如果产品规则限制: > 一个身份证只能一个有效账号 可建立: ```sql CREATE UNIQUE INDEX uk_identity_hash ON user_identities(id_number_hash) WHERE verification_status = 'verified'; ``` 具体注销后是否允许重新注册,应由后续业务规则决定。 --- # 第十二章 user_profiles 婚恋资料表 职责: > 用户可以参与匹配的主要资料。 建议: ```sql CREATE TABLE user_profiles ( user_id UUID PRIMARY KEY, display_name VARCHAR(64), gender VARCHAR(16) NOT NULL, birth_date DATE NOT NULL, height_cm SMALLINT, region_code VARCHAR(32), region_name VARCHAR(128), marital_status VARCHAR(32), children_status VARCHAR(32), education_code VARCHAR(32), occupation_code VARCHAR(32), work_region_code VARCHAR(32), smoking_status VARCHAR(32), drinking_status VARCHAR(32), long_term_danyang VARCHAR(32), future_danyang VARCHAR(32), relationship_goal VARCHAR(64), marriage_plan VARCHAR(32), self_intro TEXT, profile_completion SMALLINT NOT NULL DEFAULT 0, profile_status VARCHAR(32) NOT NULL DEFAULT 'in_progress', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT fk_profile_user FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第十三章 用户年龄不要单独存 年龄随时间变化。 因此: > 存birth_date,不存age作为业务真相。 展示时: ```text age = 当前日期 - birth_date ``` 如为了搜索性能需要,可建立表达式索引或预计算字段,但不要让前端修改age。 --- # 第十四章 profile_status 建议: ```text in_progress completed pending_review approved need_edit rejected ``` 但要注意: > Profile状态主要用于快速业务查询。 完整审核历史仍以review_tasks为准。 --- # 第十五章 user_preferences 择偶条件表 一用户一条主配置。 ```sql CREATE TABLE user_preferences ( user_id UUID PRIMARY KEY, min_age SMALLINT, max_age SMALLINT, age_priority VARCHAR(16), min_height_cm SMALLINT, max_height_cm SMALLINT, height_priority VARCHAR(16), region_scope JSONB, region_priority VARCHAR(16), marital_statuses JSONB, marital_priority VARCHAR(16), min_education_code VARCHAR(32), education_priority VARCHAR(16), children_acceptance VARCHAR(32), children_priority VARCHAR(16), smoking_preference VARCHAR(32), drinking_preference VARCHAR(32), settlement_preference VARCHAR(32), marriage_plan_preference VARCHAR(32), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT fk_preference_user FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第十六章 为什么部分条件可以JSONB 适合JSONB: ```text region_scope marital_statuses ``` 因为本质是小型多选集合。 不建议把整个择偶条件全部放一个JSONB中。 核心匹配字段仍应独立列。 --- # 第十七章 Priority约束 ```text required preferred ``` 所有支持硬条件/软条件的字段统一使用。 --- # 第十八章 user_interests 兴趣关系表 ```sql CREATE TABLE user_interests ( user_id UUID NOT NULL, interest_code VARCHAR(32) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY(user_id, interest_code), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第十九章 interest_dictionary 兴趣字典 ```sql CREATE TABLE interest_dictionary ( code VARCHAR(32) PRIMARY KEY, name VARCHAR(64) NOT NULL, sort_order INT NOT NULL DEFAULT 0, enabled BOOLEAN NOT NULL DEFAULT TRUE ); ``` 例如: ```text travel reading sports fitness photography movie music food cooking outdoor pets games culture charity ``` --- # 第二十章 user_photos 用户照片表 ```sql CREATE TABLE user_photos ( id UUID PRIMARY KEY, user_id UUID NOT NULL, file_key VARCHAR(512) NOT NULL, photo_type VARCHAR(32) NOT NULL, sort_order SMALLINT NOT NULL DEFAULT 0, review_status VARCHAR(32) NOT NULL DEFAULT 'pending', review_reason VARCHAR(256), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ, FOREIGN KEY(user_id) REFERENCES users(id) ); ``` photo_type: ```text avatar lifestyle ``` --- # 第二十一章 主头像约束 建议应用层保证: > 一个用户只有一个有效avatar。 数据库可建立部分唯一索引: ```sql CREATE UNIQUE INDEX uk_user_avatar ON user_photos(user_id) WHERE photo_type = 'avatar' AND deleted_at IS NULL; ``` --- # 第二十二章 user_privacy_settings 隐私设置 ```sql CREATE TABLE user_privacy_settings ( user_id UUID PRIMARY KEY, visible_in_recommendations BOOLEAN NOT NULL DEFAULT TRUE, visible_in_search BOOLEAN NOT NULL DEFAULT TRUE, show_occupation BOOLEAN NOT NULL DEFAULT TRUE, show_lifestyle_photos BOOLEAN NOT NULL DEFAULT TRUE, receive_activity_recommendations BOOLEAN NOT NULL DEFAULT TRUE, accept_matchmaker_recommendations BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第二十三章 review_tasks 审核任务 ```sql CREATE TABLE review_tasks ( id UUID PRIMARY KEY, user_id UUID NOT NULL, review_type VARCHAR(32) NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'pending', assigned_operator_id UUID, submitted_at TIMESTAMPTZ NOT NULL, started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, final_comment VARCHAR(1000), version INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第二十四章 审核任务索引 ```sql CREATE INDEX idx_review_tasks_status_time ON review_tasks(status, submitted_at); ``` 审核员: ```sql CREATE INDEX idx_review_tasks_operator ON review_tasks(assigned_operator_id, status); ``` --- # 第二十五章 review_items 审核明细 ```sql CREATE TABLE review_items ( id UUID PRIMARY KEY, review_task_id UUID NOT NULL, item_type VARCHAR(32) NOT NULL, status VARCHAR(32) NOT NULL, reason_code VARCHAR(64), comment VARCHAR(500), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(review_task_id) REFERENCES review_tasks(id) ); ``` item_type: ```text avatar lifestyle_photos basic_profile self_intro relationship_profile ``` --- # 第二十六章 review_reasons 审核原因字典 ```sql CREATE TABLE review_reasons ( code VARCHAR(64) PRIMARY KEY, name VARCHAR(128) NOT NULL, category VARCHAR(32), enabled BOOLEAN NOT NULL DEFAULT TRUE, sort_order INT NOT NULL DEFAULT 0 ); ``` 例如: ```text AVATAR_NOT_CLEAR PHOTO_NOT_SELF CONTACT_INFO_INCLUDED AD_CONTENT PROFILE_CONFLICT INAPPROPRIATE_CONTENT ``` --- # 第二十七章 recommendation_records 推荐记录 用于: - 避免重复推荐 - 计算冷却期 - 统计曝光 - 分析推荐效果 ```sql CREATE TABLE recommendation_records ( id UUID PRIMARY KEY, user_id UUID NOT NULL, target_user_id UUID NOT NULL, source VARCHAR(32) NOT NULL, score NUMERIC(5,2), level VARCHAR(16), reasons JSONB, shown_at TIMESTAMPTZ NOT NULL, action VARCHAR(32), action_at TIMESTAMPTZ, FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(target_user_id) REFERENCES users(id) ); ``` --- # 第二十八章 推荐索引 ```sql CREATE INDEX idx_recommend_user_time ON recommendation_records(user_id, shown_at DESC); ``` 双方关系: ```sql CREATE INDEX idx_recommend_pair ON recommendation_records(user_id, target_user_id); ``` --- # 第二十九章 user_relations 用户单向关系 ```sql CREATE TABLE user_relations ( id UUID PRIMARY KEY, user_id UUID NOT NULL, target_user_id UUID NOT NULL, relation_type VARCHAR(32) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(target_user_id) REFERENCES users(id), CONSTRAINT chk_not_self_relation CHECK (user_id <> target_user_id) ); ``` --- # 第三十章 user_relations唯一约束 同方向同一关系类型: ```sql CREATE UNIQUE INDEX uk_user_relation ON user_relations(user_id, target_user_id, relation_type); ``` relation_type: ```text liked passed blocked ``` --- # 第三十一章 blocked优先级 业务逻辑中: ```text blocked > liked / passed ``` 一旦拉黑: - 推荐层排除 - 聊天层禁止 - 红娘层排除 是否物理删除旧liked,可不删除。 保持行为历史更利于审计。 --- # 第三十二章 matches 双向匹配表 ```sql CREATE TABLE matches ( id UUID PRIMARY KEY, user_a_id UUID NOT NULL, user_b_id UUID NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'active', source VARCHAR(32) NOT NULL DEFAULT 'user_like', matched_at TIMESTAMPTZ NOT NULL, unmatched_at TIMESTAMPTZ, successful_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(user_a_id) REFERENCES users(id), FOREIGN KEY(user_b_id) REFERENCES users(id), CONSTRAINT chk_match_users CHECK (user_a_id <> user_b_id) ); ``` --- # 第三十三章 matches标准化顺序 应用层必须保证: ```text user_a_id < user_b_id ``` 或者基于UUID字符串排序。 并建立: ```sql CREATE UNIQUE INDEX uk_match_pair ON matches(user_a_id, user_b_id); ``` 如果未来允许同一双方历史多次重新匹配,可以改为: > 活跃关系部分唯一索引。 V1建议一个pair一条match记录,通过状态保存历史。 --- # 第三十四章 match status ```text active getting_to_know unmatched successful ``` --- # 第三十五章 relationship_success_requests 用于: > 成功牵手必须双方确认。 ```sql CREATE TABLE relationship_success_requests ( id UUID PRIMARY KEY, match_id UUID NOT NULL, initiated_by UUID NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'pending', confirmed_by UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), resolved_at TIMESTAMPTZ, FOREIGN KEY(match_id) REFERENCES matches(id), FOREIGN KEY(initiated_by) REFERENCES users(id), FOREIGN KEY(confirmed_by) REFERENCES users(id) ); ``` --- # 第三十六章 conversations 会话表 ```sql CREATE TABLE conversations ( id UUID PRIMARY KEY, match_id UUID NOT NULL UNIQUE, status VARCHAR(32) NOT NULL DEFAULT 'active', last_message_id UUID, last_message_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(match_id) REFERENCES matches(id) ); ``` --- # 第三十七章 conversation_members ```sql CREATE TABLE conversation_members ( conversation_id UUID NOT NULL, user_id UUID NOT NULL, last_read_message_id UUID, unread_count INT NOT NULL DEFAULT 0, can_send BOOLEAN NOT NULL DEFAULT TRUE, joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY(conversation_id, user_id), FOREIGN KEY(conversation_id) REFERENCES conversations(id), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第三十八章 messages 消息表 ```sql CREATE TABLE messages ( id UUID PRIMARY KEY, conversation_id UUID NOT NULL, sender_id UUID, client_message_id VARCHAR(128), message_type VARCHAR(32) NOT NULL, content TEXT, file_id UUID, status VARCHAR(32) NOT NULL DEFAULT 'sent', risk_level VARCHAR(16), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(conversation_id) REFERENCES conversations(id), FOREIGN KEY(sender_id) REFERENCES users(id) ); ``` --- # 第三十九章 消息幂等唯一索引 ```sql CREATE UNIQUE INDEX uk_message_client_id ON messages(sender_id, client_message_id) WHERE client_message_id IS NOT NULL; ``` --- # 第四十章 消息查询索引 ```sql CREATE INDEX idx_messages_conversation_time ON messages(conversation_id, created_at DESC); ``` --- # 第四十一章 message_type ```text text image system risk_notice ``` --- # 第四十二章 聊天内容是否加密 数据库层面建议至少: > 磁盘/云数据库加密 + 严格访问权限。 如果后续提高安全等级,可对聊天正文做应用层加密。 但要注意: - 风控检测 - 举报取证 - 消息搜索 会增加实现复杂度。 V1先以严格访问控制和审计为主。 --- # 第四十三章 activities 活动表 ```sql CREATE TABLE activities ( id UUID PRIMARY KEY, title VARCHAR(200) NOT NULL, cover_file_id UUID, description TEXT, organizer_org_id UUID NOT NULL, coorganizer_text VARCHAR(500), start_at TIMESTAMPTZ NOT NULL, end_at TIMESTAMPTZ NOT NULL, location_name VARCHAR(200), location_address VARCHAR(500), male_limit INT, female_limit INT, total_limit INT, min_age SMALLINT, max_age SMALLINT, registration_start_at TIMESTAMPTZ, registration_end_at TIMESTAMPTZ, need_review BOOLEAN NOT NULL DEFAULT TRUE, status VARCHAR(32) NOT NULL DEFAULT 'draft', created_by UUID NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ, FOREIGN KEY(organizer_org_id) REFERENCES organizations(id) ); ``` --- # 第四十四章 activity_registrations ```sql CREATE TABLE activity_registrations ( id UUID PRIMARY KEY, activity_id UUID NOT NULL, user_id UUID NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'pending', registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), reviewed_by UUID, reviewed_at TIMESTAMPTZ, reject_reason VARCHAR(500), checkin_status VARCHAR(32) NOT NULL DEFAULT 'not_checked_in', checkin_at TIMESTAMPTZ, emergency_contact_name VARCHAR(100), emergency_contact_phone_enc TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(activity_id) REFERENCES activities(id), FOREIGN KEY(user_id) REFERENCES users(id), UNIQUE(activity_id, user_id) ); ``` --- # 第四十五章 registration status ```text pending approved rejected cancelled ``` checkin_status: ```text not_checked_in checked_in ``` --- # 第四十六章 activity_feedback ```sql CREATE TABLE activity_feedback ( id UUID PRIMARY KEY, activity_id UUID NOT NULL, user_id UUID NOT NULL, satisfaction SMALLINT, join_again VARCHAR(32), interested_user_id UUID, comment VARCHAR(1000), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(activity_id) REFERENCES activities(id), FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(interested_user_id) REFERENCES users(id), UNIQUE(activity_id, user_id) ); ``` --- # 第四十七章 活动意向隐私 interested_user_id: > 不能直接向对方公开。 只有双方互选后: > 触发匹配逻辑。 --- # 第四十八章 matchmaker_services 公益红娘服务 ```sql CREATE TABLE matchmaker_services ( id UUID PRIMARY KEY, user_id UUID NOT NULL, service_type VARCHAR(32) NOT NULL, description VARCHAR(1000), status VARCHAR(32) NOT NULL DEFAULT 'submitted', organization_id UUID, assigned_operator_id UUID, assigned_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(organization_id) REFERENCES organizations(id), FOREIGN KEY(assigned_operator_id) REFERENCES operators(id) ); ``` --- # 第四十九章 红娘服务索引 ```sql CREATE INDEX idx_matchmaker_status ON matchmaker_services(status, created_at); ``` 按红娘: ```sql CREATE INDEX idx_matchmaker_operator ON matchmaker_services(assigned_operator_id, status); ``` --- # 第五十章 matchmaker_recommendations ```sql CREATE TABLE matchmaker_recommendations ( id UUID PRIMARY KEY, service_id UUID NOT NULL, user_id UUID NOT NULL, target_user_id UUID NOT NULL, operator_id UUID NOT NULL, reason VARCHAR(1000), status VARCHAR(32) NOT NULL DEFAULT 'pending_view', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), viewed_at TIMESTAMPTZ, responded_at TIMESTAMPTZ, FOREIGN KEY(service_id) REFERENCES matchmaker_services(id), FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(target_user_id) REFERENCES users(id), FOREIGN KEY(operator_id) REFERENCES operators(id) ); ``` --- # 第五十一章 matchmaker_followups ```sql CREATE TABLE matchmaker_followups ( id UUID PRIMARY KEY, service_id UUID NOT NULL, operator_id UUID NOT NULL, followup_type VARCHAR(32), content VARCHAR(2000) NOT NULL, result_code VARCHAR(64), next_followup_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(service_id) REFERENCES matchmaker_services(id), FOREIGN KEY(operator_id) REFERENCES operators(id) ); ``` --- # 第五十二章 reports 举报表 ```sql CREATE TABLE reports ( id UUID PRIMARY KEY, reporter_user_id UUID NOT NULL, target_user_id UUID NOT NULL, report_type VARCHAR(64) NOT NULL, description VARCHAR(2000), status VARCHAR(32) NOT NULL DEFAULT 'pending', risk_level VARCHAR(16) NOT NULL DEFAULT 'normal', processed_by UUID, processed_at TIMESTAMPTZ, result_code VARCHAR(64), result_comment VARCHAR(2000), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(reporter_user_id) REFERENCES users(id), FOREIGN KEY(target_user_id) REFERENCES users(id) ); ``` --- # 第五十三章 举报索引 ```sql CREATE INDEX idx_reports_status_risk ON reports(status, risk_level, created_at); ``` 按被举报用户: ```sql CREATE INDEX idx_reports_target ON reports(target_user_id, created_at DESC); ``` --- # 第五十四章 report_evidences ```sql CREATE TABLE report_evidences ( id UUID PRIMARY KEY, report_id UUID NOT NULL, file_key VARCHAR(512) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(report_id) REFERENCES reports(id) ); ``` --- # 第五十五章 user_risk_profiles 一用户一条风险摘要。 ```sql CREATE TABLE user_risk_profiles ( user_id UUID PRIMARY KEY, score INT NOT NULL DEFAULT 0, level VARCHAR(16) NOT NULL DEFAULT 'normal', report_count INT NOT NULL DEFAULT 0, block_count INT NOT NULL DEFAULT 0, last_event_at TIMESTAMPTZ, last_calculated_at TIMESTAMPTZ, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第五十六章 risk_events ```sql CREATE TABLE risk_events ( id UUID PRIMARY KEY, user_id UUID NOT NULL, event_type VARCHAR(64) NOT NULL, score_delta INT NOT NULL DEFAULT 0, source_type VARCHAR(32), source_id UUID, metadata JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第五十七章 risk_actions 风控处置 ```sql CREATE TABLE risk_actions ( id UUID PRIMARY KEY, user_id UUID NOT NULL, action_type VARCHAR(32) NOT NULL, reason VARCHAR(1000), operator_id UUID, started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ, revoked_at TIMESTAMPTZ, FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(operator_id) REFERENCES operators(id) ); ``` action_type: ```text warning feature_limit temporary_suspend ban manual_investigation ``` --- # 第五十八章 appeals 申诉表 ```sql CREATE TABLE appeals ( id UUID PRIMARY KEY, user_id UUID NOT NULL, related_action_id UUID, content VARCHAR(2000) NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'pending', processed_by UUID, processed_at TIMESTAMPTZ, result VARCHAR(1000), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第五十九章 notifications 系统通知 ```sql CREATE TABLE notifications ( id UUID PRIMARY KEY, user_id UUID NOT NULL, type VARCHAR(32) NOT NULL, title VARCHAR(200) NOT NULL, content VARCHAR(2000), business_type VARCHAR(32), business_id UUID, read_status VARCHAR(16) NOT NULL DEFAULT 'unread', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), read_at TIMESTAMPTZ, FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第六十章 通知索引 ```sql CREATE INDEX idx_notifications_user ON notifications(user_id, read_status, created_at DESC); ``` --- # 第六十一章 organizations 组织表 ```sql CREATE TABLE organizations ( id UUID PRIMARY KEY, name VARCHAR(200) NOT NULL, type VARCHAR(32) NOT NULL, contact_name VARCHAR(100), contact_phone_enc TEXT, status VARCHAR(32) NOT NULL DEFAULT 'active', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` type: ```text women_federation trade_union charity_center volunteer_team civil_affairs_partner other ``` --- # 第六十二章 operators 工作人员表 ```sql CREATE TABLE operators ( id UUID PRIMARY KEY, organization_id UUID, name VARCHAR(100) NOT NULL, phone_enc TEXT, phone_hash VARCHAR(128), username VARCHAR(100) NOT NULL, password_hash VARCHAR(255) NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'active', last_login_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(organization_id) REFERENCES organizations(id) ); ``` --- # 第六十三章 工作人员唯一索引 ```sql CREATE UNIQUE INDEX uk_operator_username ON operators(username); ``` 手机号如果作为登录凭证,可对phone_hash建立唯一索引。 --- # 第六十四章 roles 角色表 ```sql CREATE TABLE roles ( id UUID PRIMARY KEY, code VARCHAR(64) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, description VARCHAR(500), system_role BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` --- # 第六十五章 默认角色 ```text super_admin organization_admin reviewer matchmaker activity_manager risk_operator volunteer ``` --- # 第六十六章 permissions 权限表 ```sql CREATE TABLE permissions ( id UUID PRIMARY KEY, code VARCHAR(128) NOT NULL UNIQUE, name VARCHAR(200) NOT NULL, module VARCHAR(64) NOT NULL, description VARCHAR(500) ); ``` --- # 第六十七章 role_permissions ```sql CREATE TABLE role_permissions ( role_id UUID NOT NULL, permission_id UUID NOT NULL, PRIMARY KEY(role_id, permission_id), FOREIGN KEY(role_id) REFERENCES roles(id), FOREIGN KEY(permission_id) REFERENCES permissions(id) ); ``` --- # 第六十八章 operator_roles ```sql CREATE TABLE operator_roles ( operator_id UUID NOT NULL, role_id UUID NOT NULL, PRIMARY KEY(operator_id, role_id), FOREIGN KEY(operator_id) REFERENCES operators(id), FOREIGN KEY(role_id) REFERENCES roles(id) ); ``` --- # 第六十九章 audit_logs 操作审计 ```sql CREATE TABLE audit_logs ( id UUID PRIMARY KEY, operator_id UUID, organization_id UUID, action VARCHAR(128) NOT NULL, resource_type VARCHAR(64) NOT NULL, resource_id VARCHAR(128), before_data JSONB, after_data JSONB, ip VARCHAR(64), user_agent VARCHAR(500), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` --- # 第七十章 审计数据要求 before_data / after_data: 必须脱敏。 禁止写入: - 完整身份证 - 完整手机号 - 密码 - Token - 聊天正文 --- # 第七十一章 sensitive_access_logs 专门记录敏感资料查看。 ```sql CREATE TABLE sensitive_access_logs ( id UUID PRIMARY KEY, operator_id UUID NOT NULL, user_id UUID NOT NULL, data_type VARCHAR(64) NOT NULL, reason VARCHAR(500), ip VARCHAR(64), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), FOREIGN KEY(operator_id) REFERENCES operators(id), FOREIGN KEY(user_id) REFERENCES users(id) ); ``` --- # 第七十二章 system_configs 轻量系统配置。 ```sql CREATE TABLE system_configs ( config_key VARCHAR(128) PRIMARY KEY, config_value JSONB NOT NULL, description VARCHAR(500), updated_by UUID, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` --- # 第七十三章 推荐配置示例 ```text daily_recommendation_limit max_profile_photos max_interest_tags recommendation_cooldown_days activity_enabled matchmaker_enabled chat_image_enabled ``` --- # 第七十四章 match_rule_configs 推荐独立结构: ```sql CREATE TABLE match_rule_configs ( id UUID PRIMARY KEY, version INT NOT NULL, status VARCHAR(16) NOT NULL, age_weight INT NOT NULL, region_weight INT NOT NULL, marital_weight INT NOT NULL, children_weight INT NOT NULL, education_weight INT NOT NULL, height_weight INT NOT NULL, marriage_plan_weight INT NOT NULL, settlement_weight INT NOT NULL, lifestyle_weight INT NOT NULL, interest_weight INT NOT NULL, minimum_score INT NOT NULL DEFAULT 60, created_by UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), activated_at TIMESTAMPTZ ); ``` --- # 第七十五章 匹配规则版本化 不要直接覆盖原权重。 建议: ```text version 1 version 2 version 3 ``` 只有一条: ```text status = active ``` 这样以后可以追溯: > 某次推荐使用哪套规则。 recommendation_records可以增加: ```text rule_version ``` --- # 第七十六章 ER核心关系概览 ```text users │ ├──1:1── user_identities ├──1:1── user_profiles ├──1:1── user_preferences ├──1:1── user_privacy_settings │ ├──1:N── user_photos ├──1:N── user_interests ├──1:N── review_tasks ├──1:N── recommendation_records ├──1:N── user_relations ├──N:N── matches ├──N:N── conversations ├──1:N── activity_registrations ├──1:N── matchmaker_services ├──1:N── reports ├──1:1── user_risk_profiles └──1:N── notifications ``` --- # 第七十七章 匹配关系ER ```text User A │ ├── liked ──────> User B │ User B │ └── liked ──────> User A │ ↓ Match │ ↓ Conversation │ ↓ Messages ``` --- # 第七十八章 红娘关系ER ```text User ↓ MatchmakerService ↓ Operator / Matchmaker ↓ MatchmakerRecommendation ↓ TargetUser ``` 红娘推荐不会直接写入: ```text user_relations.liked ``` 必须由用户本人操作。 --- # 第七十九章 活动关系ER ```text Activity │ ├── ActivityRegistration ── User │ └── ActivityFeedback ───── User │ └── interested_user_id ``` --- # 第八十章 审核关系ER ```text User ↓ ReviewTask ↓ ReviewItem ``` 一次资料修改: > 新建新的ReviewTask。 不要覆盖历史审核任务。 --- # 第八十一章 数据保留原则 以下数据不能因为状态结束就删除: - 匹配记录 - 红娘服务记录 - 审核记录 - 举报记录 - 风控处置 - 活动报名 - 操作日志 原因: > 服务质量、纠纷处理、统计、审计都需要历史。 --- # 第八十二章 软删除建议 适合soft delete: - 用户照片 - 活动 - 某些运营配置 不建议简单soft delete: - messages - audit_logs - reports - review_tasks 这些属于业务记录。 --- # 第八十三章 用户注销后的数据策略 用户注销后: ```text users.account_status = closed ``` 同时: - recommendation_enabled = false - search_visibility = false - 停止匹配 - 停止新聊天 根据数据生命周期策略: > 对公开资料匿名化或进入待清理流程。 具体删除周期需在隐私合规设计章节明确。 --- # 第八十四章 数据脱敏查询层 数据库Entity不直接返回前端。 必须经过DTO转换。 例如: ```text PublicProfileDTO ``` 不会包含: ```text real_name_enc id_number_enc phone ``` --- # 第八十五章 公共用户资料DTO 建议包含: ```text userId displayName age height region education occupation maritalStatus childrenStatus interests photos relationshipGoal marriagePlan verified reviewed recommendReasons ``` --- # 第八十六章 自己查看自己DTO SelfProfileDTO可增加: ```text phoneMasked reviewStatus profileCompletion privacySettings ``` 仍然不要默认返回完整身份证。 --- # 第八十七章 红娘DTO MatchmakerUserDTO: 可查看: - 公开资料 - 择偶条件 - 服务状态 - 推荐记录 - 跟进记录 不可包含: - 身份证 - 完整聊天正文 - 风控证据 --- # 第八十八章 管理员DTO AdminUserDTO根据权限动态裁剪。 敏感数据接口应独立: ```text GET /admin/users/{id}/sensitive-identity ``` 而不是: > 用户详情接口默认带完整实名信息。 --- # 第八十九章 数据库索引重点 重点索引建议: ```text users(account_status) users(relationship_status) user_profiles(gender, region_code) user_profiles(birth_date) user_profiles(profile_status) review_tasks(status, submitted_at) user_relations(user_id, target_user_id) user_relations(target_user_id, relation_type) matches(user_a_id, user_b_id) messages(conversation_id, created_at) activities(status, start_at) activity_registrations(activity_id, status) matchmaker_services(status, assigned_operator_id) reports(status, risk_level) reports(target_user_id) notifications(user_id, read_status, created_at) ``` --- # 第九十章 匹配初筛查询建议 V1不要做全量暴力评分。 数据库先筛: ```text 性别 审核通过 账号正常 婚恋状态正常 隐私允许 年龄粗范围 地区 ``` 然后应用层继续: ```text 双方required条件 preferred条件 权重评分 ``` --- # 第九十一章 年龄查询建议 使用birth_date转换条件。 例如用户要求: > 28~35岁。 换算成: ```text birth_date BETWEEN 某两个日期 ``` 这样比数据库逐条计算年龄更利于索引。 --- # 第九十二章 大字段控制 以下字段不要放主列表查询: - self_intro - 活动description - 举报description - 红娘followup全文 - 消息content 列表页只查询必要字段。 --- # 第九十三章 JSONB使用边界 适合: - 推荐理由快照 - 风险事件metadata - 审计before/after - 系统配置 - 小型多选条件 不适合: > 核心业务实体全部JSON化。 --- # 第九十四章 数据库事务场景一:双向喜欢 事务建议: ```text BEGIN ↓ 插入A→B liked ↓ 检查B→A liked ↓ 如果存在: 创建match 创建conversation 创建2条conversation_member COMMIT ↓ 事务后发送通知 ``` --- # 第九十五章 并发双向喜欢 A和B同时点击喜欢。 必须通过: - unique relation约束 - unique match pair约束 - 捕获duplicate key后重新查询 保证最终: > 只有一个Match。 --- # 第九十六章 数据库事务场景二:活动名额 审核通过报名时: ```text BEGIN ↓ 锁定活动/名额状态 ↓ 统计当前已通过人数 ↓ 判断性别名额 ↓ 更新registration COMMIT ``` 避免超额。 --- # 第九十七章 更高效的活动名额方案 如果未来活动量增大: 可在activities增加: ```text approved_male_count approved_female_count approved_total_count ``` 使用原子更新。 V1可以先用事务统计。 --- # 第九十八章 数据库事务场景三:审核领取 ```sql UPDATE review_tasks SET status = 'processing', assigned_operator_id = :operatorId WHERE id = :id AND status = 'pending'; ``` 检查影响行数: ```text 1 → 领取成功 0 → 已被其他审核员领取 ``` 不一定需要Redis锁。 数据库原子更新更简单。 --- # 第九十九章 数据库事务场景四:签到 唯一报名记录上更新: ```text not_checked_in → checked_in ``` 重复签到: > 返回已签到,而不是插入第二条记录。 --- # 第一百章 数据归档策略 V1无需复杂冷热分层。 但应预留: messages和audit_logs未来可能体量最大。 后续可按: - 时间分区 - 月度分区 - 历史归档 扩展。 --- # 第一百零一章 messages未来分区建议 如果消息量明显增长: 可按: ```text created_at ``` 按月分区。 V1阶段无需提前复杂化。 --- # 第一百零二章 audit_logs未来分区 同样可按: > 月份。 因为操作日志通常只追加。 --- # 第一百零三章 数据库迁移工具 必须使用迁移工具。 Java: ```text Flyway / Liquibase ``` Node: ```text Prisma migrations / TypeORM migrations ``` 不能生产环境: > 手工改表。 --- # 第一百零四章 migration命名 例如: ```text V001__init_users.sql V002__add_profiles.sql V003__add_review_tables.sql V004__add_match_tables.sql ``` 每次变更可追溯。 --- # 第一百零五章 初始化字典数据 建议migration或seed初始化: - 兴趣字典 - 教育类型 - 职业分类 - 审核原因 - 权限 - 默认角色 - 默认匹配规则 --- # 第一百零六章 测试数据策略 开发环境可提供: > seed脚本。 生成: - 男用户 - 女用户 - 不同年龄 - 不同婚姻状态 - 不同择偶条件 - 匹配/不匹配数据 方便测试匹配算法。 --- # 第一百零七章 生产数据严禁复制到开发环境 尤其不得直接复制: - 实名信息 - 聊天记录 - 用户照片 - 举报证据 如确需问题复现: > 使用脱敏数据。 --- # 第一百零八章 备份对象 至少备份: ```text PostgreSQL 对象存储关键Bucket 系统配置 数据库迁移文件 ``` Redis: 原则上不需要作为业务恢复唯一依赖。 --- # 第一百零九章 数据恢复优先级 第一: > 用户与业务数据库。 第二: > 用户照片与证据。 第三: > 配置。 Redis可以: > 重建。 --- # 第一百一十章 敏感数据访问原则 实名数据库字段不能被: - 推荐模块 - 活动普通列表 - 红娘候选列表 - 统计模块 直接查询。 建议代码层也分Repository。 例如: ```text IdentityRepository ``` 只允许Verification/SensitiveAdmin服务使用。 --- # 第一百一十一章 数据库账号权限 生产环境至少拆: ```text app_rw migration_admin backup_user readonly_reporting ``` 后台运营人员绝不能直接拥有数据库账号。 --- # 第一百一十二章 数据最小化 没有业务必要就不采集。 例如V1不采集: - 房产证 - 车辆信息 - 详细收入 - 银行信息 - 精确住宅地址 数据库也不要“先预留几十个字段以后可能用”。 --- # 第一百一十三章 业务唯一约束汇总 必须有: ```text users.wechat_openid UNIQUE users.phone UNIQUE(有效账号) user_identities.user_id UNIQUE user_profiles.user_id UNIQUE user_preferences.user_id UNIQUE user_privacy_settings.user_id UNIQUE user_interests(user_id, interest_code) UNIQUE user_relations(user_id,target_user_id,relation_type) UNIQUE matches(user_a_id,user_b_id) UNIQUE conversations.match_id UNIQUE conversation_members(conversation_id,user_id) UNIQUE messages(sender_id,client_message_id) UNIQUE activity_registrations(activity_id,user_id) UNIQUE activity_feedback(activity_id,user_id) UNIQUE ``` --- # 第一百一十四章 外键策略 核心业务表建议使用外键。 好处: > 防止孤儿数据。 但生产删除必须谨慎。 多数情况下: > 不使用ON DELETE CASCADE。 因为婚恋业务需要历史审计。 --- # 第一百一十五章 不建议Cascade删除 例如删除User时绝不能自动删除: - messages - reports - matches - review_tasks 因此: > 账号关闭优先状态化,而不是删除。 --- # 第一百一十六章 统计数据来源 早期后台统计从业务表聚合。 例如: 成功牵手: ```text matches.status = successful ``` 审核通过: ```text user_profiles.profile_status = approved ``` 不要前端自己累加。 --- # 第一百一十七章 后续可增加每日汇总表 例如: ```text daily_platform_metrics ``` 字段: ```text date new_users verified_users approved_users likes mutual_matches chat_started activity_registrations successful_matches reports ``` 当实时统计性能不足时再启用。 --- # 第一百一十八章 推荐记录的数据价值 recommendation_records后续可以分析: ```text 曝光 → 查看 查看 → 喜欢 喜欢 → 双向 双向 → 聊天 ``` 这比一开始建设复杂AI推荐系统更重要。 --- # 第一百一十九章 未来AI匹配的数据基础 如果未来引入AI,现有表已经提供: - 基础条件 - 兴趣 - 婚恋计划 - 推荐曝光 - 喜欢行为 - 双向匹配 - 解除匹配 - 成功牵手 可以作为后续推荐优化基础。 因此V1不需要提前做向量数据库。 --- # 第一百二十章 数据库V1明确不建设 暂不需要: - ElasticSearch - Neo4j - Milvus - ClickHouse - Kafka存储 - 数据湖 - 特征库 - AI向量表 - 推荐模型训练表 - 支付订单表 - VIP表 - 礼物表 - 直播表 - 短视频表 --- # 第一百二十一章 核心表开发优先级 ## P0 第一批: ```text users user_identities user_profiles user_preferences user_interests user_photos user_privacy_settings review_tasks review_items recommendation_records user_relations matches conversations conversation_members messages organizations operators roles permissions role_permissions operator_roles audit_logs ``` --- ## P1 第二批: ```text activities activity_registrations activity_feedback matchmaker_services matchmaker_recommendations matchmaker_followups notifications ``` --- ## P2 第三批: ```text reports report_evidences user_risk_profiles risk_events risk_actions appeals system_configs match_rule_configs sensitive_access_logs ``` 风控功能本身仍是V1必做,这里的P2只是数据库实施排期顺序,不代表可以不上线。 --- # 第一百二十二章 数据库验收重点 必须验证: ### 用户 一个微信账号不能生成多个正常用户。 ### 身份 一个认证身份证不能无限重复注册。 ### 关系 不能自己喜欢自己。 ### Match 一对用户不能生成重复Match。 ### 消息 重复clientMessageId不能生成两条消息。 ### 活动 同一用户不能重复报名同一活动。 ### 审核 一个审核任务不能被多人同时完成。 ### 权限 敏感信息不进入普通查询结果。 --- # 第一百二十三章 数据一致性验收 必须重点测试: ```text 喜欢成功但Match失败 Match成功但Conversation失败 活动报名成功但人数超限 举报处理后User状态未同步 用户拉黑后Conversation还能发送 成功牵手后仍进入推荐池 ``` 这些都必须通过事务和业务状态保证一致。 --- # 第一百二十四章 数据库ER文字总图 ```text User │ ├─ Identity ├─ Profile │ ├─ Photos │ ├─ Interests │ ├─ Preference │ └─ Privacy │ ├─ Reviews │ └─ ReviewItems │ ├─ Relations │ ↓ │ Match │ ↓ │ Conversation │ ↓ │ Messages │ ├─ ActivityRegistrations │ ↓ │ Activities │ ├─ MatchmakerServices │ ├─ Recommendations │ └─ Followups │ ├─ Reports │ └─ Evidences │ ├─ RiskProfile │ ├─ RiskEvents │ └─ RiskActions │ └─ Notifications Organization │ └─ Operators ├─ Roles │ └─ Permissions └─ AuditLogs ``` --- # 第一百二十五章 数据库设计最终原则 数据库层必须做到: > **真实身份和公开婚恋资料物理分层。** > **所有双向关系都有数据库唯一约束。** > **所有重要状态变化都有历史可追溯。** > **聊天、举报、审核、风控不能因为用户状态变化被随意删除。** > **匹配数据能够支持未来持续优化,但V1不提前复杂化。** > **敏感数据永远不通过普通User查询顺手返回。** > **每个高风险业务都依赖数据库约束兜底,而不是只相信前端。** 最终数据库不是为了“表越多越专业”,而是为了保证: > **用户、匹配、聊天、活动、红娘、审核和安全这几条核心业务链在任何异常、并发和后续扩展场景下,都能保持数据清晰、状态一致、责任可追溯。**