45 KiB
丹阳公益婚恋服务平台
V1.0 数据库详细设计与核心数据模型
版本:V1.0
数据库建议:PostgreSQL
适用范围:微信小程序用户端、公益红娘工作台、运营管理后台
设计目标:结构清晰、敏感数据分层、状态可追溯、支持事务、便于后续扩展
第一章 数据库设计原则
V1.0数据库设计必须坚持:
- 账号数据与婚恋资料分离
- 公共资料与敏感资料分离
- 用户状态与关系状态分离
- 审核数据可追溯
- 风控数据独立
- 聊天数据独立
- 活动数据独立
- 红娘服务独立
- 权限与组织独立
- 重要业务状态不通过删除实现
- 核心业务使用数据库约束兜底
- PostgreSQL作为业务真实数据源
不建议:
- 一个users表塞所有字段
- 使用大量JSON替代清晰结构
- 核心关系依赖Redis
- 不做唯一约束
- 只依赖前端校验
第二章 核心数据域
V1.0数据库建议划分为:
01 用户账号域
02 实名认证域
03 婚恋资料域
04 人工审核域
05 推荐与关系域
06 聊天消息域
07 公益活动域
08 公益红娘域
09 举报风控域
10 系统通知域
11 组织权限域
12 审计日志域
13 配置与统计域
第三章 核心表总览
建议V1.0核心表:
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
第四章 通用字段规范
大部分业务表建议包含:
id
created_at
updated_at
需要软删除的表:
deleted_at
业务状态不要用:
is_deleted = 0/1
优先采用:
deleted_at IS NULL
第五章 ID设计
推荐:
UUID / UUIDv7
或:
应用生成的分布式唯一ID。
不要让外部接口直接暴露连续自增ID。
例如:
user_id = 018f...
或者:
usr_xxxxxxxxx
如果数据库内部使用BIGINT,也建议API层映射公共ID。
第六章 users 用户账号表
职责:
只保存账号和平台级状态。
建议结构:
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约束
唯一约束:
CREATE UNIQUE INDEX uk_users_openid
ON users(wechat_openid)
WHERE deleted_at IS NULL;
手机号:
CREATE UNIQUE INDEX uk_users_phone
ON users(phone)
WHERE phone IS NOT NULL
AND deleted_at IS NULL;
unionid:
如业务使用:
CREATE UNIQUE INDEX uk_users_unionid
ON users(wechat_unionid)
WHERE wechat_unionid IS NOT NULL
AND deleted_at IS NULL;
第八章 users状态约束
account_status:
normal
reviewing_risk
limited
suspended
banned
closed
relationship_status:
searching
getting_to_know
paused
matched_successfully
service_exited
建议使用:
VARCHAR + CHECK
而不是PostgreSQL ENUM。
原因:
后续状态扩展和数据库迁移更方便。
第九章 user_identities 实名认证表
高度敏感。
与users严格分离。
字段建议:
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)
);
第十章 实名敏感字段
real_name_enc
id_number_enc
必须:
加密后存储。
同时建议存:
id_number_hash
用于:
- 防重复身份证注册
- 查询重复身份
但不能通过Hash还原身份证。
第十一章 身份证重复约束
如果产品规则限制:
一个身份证只能一个有效账号
可建立:
CREATE UNIQUE INDEX uk_identity_hash
ON user_identities(id_number_hash)
WHERE verification_status = 'verified';
具体注销后是否允许重新注册,应由后续业务规则决定。
第十二章 user_profiles 婚恋资料表
职责:
用户可以参与匹配的主要资料。
建议:
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作为业务真相。
展示时:
age = 当前日期 - birth_date
如为了搜索性能需要,可建立表达式索引或预计算字段,但不要让前端修改age。
第十四章 profile_status
建议:
in_progress
completed
pending_review
approved
need_edit
rejected
但要注意:
Profile状态主要用于快速业务查询。
完整审核历史仍以review_tasks为准。
第十五章 user_preferences 择偶条件表
一用户一条主配置。
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:
region_scope
marital_statuses
因为本质是小型多选集合。
不建议把整个择偶条件全部放一个JSONB中。
核心匹配字段仍应独立列。
第十七章 Priority约束
required
preferred
所有支持硬条件/软条件的字段统一使用。
第十八章 user_interests 兴趣关系表
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 兴趣字典
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
);
例如:
travel
reading
sports
fitness
photography
movie
music
food
cooking
outdoor
pets
games
culture
charity
第二十章 user_photos 用户照片表
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:
avatar
lifestyle
第二十一章 主头像约束
建议应用层保证:
一个用户只有一个有效avatar。
数据库可建立部分唯一索引:
CREATE UNIQUE INDEX uk_user_avatar
ON user_photos(user_id)
WHERE photo_type = 'avatar'
AND deleted_at IS NULL;
第二十二章 user_privacy_settings 隐私设置
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 审核任务
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)
);
第二十四章 审核任务索引
CREATE INDEX idx_review_tasks_status_time
ON review_tasks(status, submitted_at);
审核员:
CREATE INDEX idx_review_tasks_operator
ON review_tasks(assigned_operator_id, status);
第二十五章 review_items 审核明细
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:
avatar
lifestyle_photos
basic_profile
self_intro
relationship_profile
第二十六章 review_reasons 审核原因字典
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
);
例如:
AVATAR_NOT_CLEAR
PHOTO_NOT_SELF
CONTACT_INFO_INCLUDED
AD_CONTENT
PROFILE_CONFLICT
INAPPROPRIATE_CONTENT
第二十七章 recommendation_records 推荐记录
用于:
- 避免重复推荐
- 计算冷却期
- 统计曝光
- 分析推荐效果
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)
);
第二十八章 推荐索引
CREATE INDEX idx_recommend_user_time
ON recommendation_records(user_id, shown_at DESC);
双方关系:
CREATE INDEX idx_recommend_pair
ON recommendation_records(user_id, target_user_id);
第二十九章 user_relations 用户单向关系
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唯一约束
同方向同一关系类型:
CREATE UNIQUE INDEX uk_user_relation
ON user_relations(user_id, target_user_id, relation_type);
relation_type:
liked
passed
blocked
第三十一章 blocked优先级
业务逻辑中:
blocked > liked / passed
一旦拉黑:
- 推荐层排除
- 聊天层禁止
- 红娘层排除
是否物理删除旧liked,可不删除。
保持行为历史更利于审计。
第三十二章 matches 双向匹配表
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标准化顺序
应用层必须保证:
user_a_id < user_b_id
或者基于UUID字符串排序。
并建立:
CREATE UNIQUE INDEX uk_match_pair
ON matches(user_a_id, user_b_id);
如果未来允许同一双方历史多次重新匹配,可以改为:
活跃关系部分唯一索引。
V1建议一个pair一条match记录,通过状态保存历史。
第三十四章 match status
active
getting_to_know
unmatched
successful
第三十五章 relationship_success_requests
用于:
成功牵手必须双方确认。
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 会话表
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
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 消息表
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)
);
第三十九章 消息幂等唯一索引
CREATE UNIQUE INDEX uk_message_client_id
ON messages(sender_id, client_message_id)
WHERE client_message_id IS NOT NULL;
第四十章 消息查询索引
CREATE INDEX idx_messages_conversation_time
ON messages(conversation_id, created_at DESC);
第四十一章 message_type
text
image
system
risk_notice
第四十二章 聊天内容是否加密
数据库层面建议至少:
磁盘/云数据库加密 + 严格访问权限。
如果后续提高安全等级,可对聊天正文做应用层加密。
但要注意:
- 风控检测
- 举报取证
- 消息搜索
会增加实现复杂度。
V1先以严格访问控制和审计为主。
第四十三章 activities 活动表
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
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
pending
approved
rejected
cancelled
checkin_status:
not_checked_in
checked_in
第四十六章 activity_feedback
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 公益红娘服务
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)
);
第四十九章 红娘服务索引
CREATE INDEX idx_matchmaker_status
ON matchmaker_services(status, created_at);
按红娘:
CREATE INDEX idx_matchmaker_operator
ON matchmaker_services(assigned_operator_id, status);
第五十章 matchmaker_recommendations
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
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 举报表
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)
);
第五十三章 举报索引
CREATE INDEX idx_reports_status_risk
ON reports(status, risk_level, created_at);
按被举报用户:
CREATE INDEX idx_reports_target
ON reports(target_user_id, created_at DESC);
第五十四章 report_evidences
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
一用户一条风险摘要。
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
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 风控处置
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:
warning
feature_limit
temporary_suspend
ban
manual_investigation
第五十八章 appeals 申诉表
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 系统通知
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)
);
第六十章 通知索引
CREATE INDEX idx_notifications_user
ON notifications(user_id, read_status, created_at DESC);
第六十一章 organizations 组织表
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:
women_federation
trade_union
charity_center
volunteer_team
civil_affairs_partner
other
第六十二章 operators 工作人员表
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)
);
第六十三章 工作人员唯一索引
CREATE UNIQUE INDEX uk_operator_username
ON operators(username);
手机号如果作为登录凭证,可对phone_hash建立唯一索引。
第六十四章 roles 角色表
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()
);
第六十五章 默认角色
super_admin
organization_admin
reviewer
matchmaker
activity_manager
risk_operator
volunteer
第六十六章 permissions 权限表
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
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
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 操作审计
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
专门记录敏感资料查看。
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
轻量系统配置。
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()
);
第七十三章 推荐配置示例
daily_recommendation_limit
max_profile_photos
max_interest_tags
recommendation_cooldown_days
activity_enabled
matchmaker_enabled
chat_image_enabled
第七十四章 match_rule_configs
推荐独立结构:
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
);
第七十五章 匹配规则版本化
不要直接覆盖原权重。
建议:
version 1
version 2
version 3
只有一条:
status = active
这样以后可以追溯:
某次推荐使用哪套规则。
recommendation_records可以增加:
rule_version
第七十六章 ER核心关系概览
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
User A
│
├── liked ──────> User B
│
User B
│
└── liked ──────> User A
│
↓
Match
│
↓
Conversation
│
↓
Messages
第七十八章 红娘关系ER
User
↓
MatchmakerService
↓
Operator / Matchmaker
↓
MatchmakerRecommendation
↓
TargetUser
红娘推荐不会直接写入:
user_relations.liked
必须由用户本人操作。
第七十九章 活动关系ER
Activity
│
├── ActivityRegistration ── User
│
└── ActivityFeedback ───── User
│
└── interested_user_id
第八十章 审核关系ER
User
↓
ReviewTask
↓
ReviewItem
一次资料修改:
新建新的ReviewTask。
不要覆盖历史审核任务。
第八十一章 数据保留原则
以下数据不能因为状态结束就删除:
- 匹配记录
- 红娘服务记录
- 审核记录
- 举报记录
- 风控处置
- 活动报名
- 操作日志
原因:
服务质量、纠纷处理、统计、审计都需要历史。
第八十二章 软删除建议
适合soft delete:
- 用户照片
- 活动
- 某些运营配置
不建议简单soft delete:
- messages
- audit_logs
- reports
- review_tasks
这些属于业务记录。
第八十三章 用户注销后的数据策略
用户注销后:
users.account_status = closed
同时:
- recommendation_enabled = false
- search_visibility = false
- 停止匹配
- 停止新聊天
根据数据生命周期策略:
对公开资料匿名化或进入待清理流程。
具体删除周期需在隐私合规设计章节明确。
第八十四章 数据脱敏查询层
数据库Entity不直接返回前端。
必须经过DTO转换。
例如:
PublicProfileDTO
不会包含:
real_name_enc
id_number_enc
phone
第八十五章 公共用户资料DTO
建议包含:
userId
displayName
age
height
region
education
occupation
maritalStatus
childrenStatus
interests
photos
relationshipGoal
marriagePlan
verified
reviewed
recommendReasons
第八十六章 自己查看自己DTO
SelfProfileDTO可增加:
phoneMasked
reviewStatus
profileCompletion
privacySettings
仍然不要默认返回完整身份证。
第八十七章 红娘DTO
MatchmakerUserDTO:
可查看:
- 公开资料
- 择偶条件
- 服务状态
- 推荐记录
- 跟进记录
不可包含:
- 身份证
- 完整聊天正文
- 风控证据
第八十八章 管理员DTO
AdminUserDTO根据权限动态裁剪。
敏感数据接口应独立:
GET /admin/users/{id}/sensitive-identity
而不是:
用户详情接口默认带完整实名信息。
第八十九章 数据库索引重点
重点索引建议:
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不要做全量暴力评分。
数据库先筛:
性别
审核通过
账号正常
婚恋状态正常
隐私允许
年龄粗范围
地区
然后应用层继续:
双方required条件
preferred条件
权重评分
第九十一章 年龄查询建议
使用birth_date转换条件。
例如用户要求:
28~35岁。
换算成:
birth_date BETWEEN 某两个日期
这样比数据库逐条计算年龄更利于索引。
第九十二章 大字段控制
以下字段不要放主列表查询:
- self_intro
- 活动description
- 举报description
- 红娘followup全文
- 消息content
列表页只查询必要字段。
第九十三章 JSONB使用边界
适合:
- 推荐理由快照
- 风险事件metadata
- 审计before/after
- 系统配置
- 小型多选条件
不适合:
核心业务实体全部JSON化。
第九十四章 数据库事务场景一:双向喜欢
事务建议:
BEGIN
↓
插入A→B liked
↓
检查B→A liked
↓
如果存在:
创建match
创建conversation
创建2条conversation_member
COMMIT
↓
事务后发送通知
第九十五章 并发双向喜欢
A和B同时点击喜欢。
必须通过:
- unique relation约束
- unique match pair约束
- 捕获duplicate key后重新查询
保证最终:
只有一个Match。
第九十六章 数据库事务场景二:活动名额
审核通过报名时:
BEGIN
↓
锁定活动/名额状态
↓
统计当前已通过人数
↓
判断性别名额
↓
更新registration
COMMIT
避免超额。
第九十七章 更高效的活动名额方案
如果未来活动量增大:
可在activities增加:
approved_male_count
approved_female_count
approved_total_count
使用原子更新。
V1可以先用事务统计。
第九十八章 数据库事务场景三:审核领取
UPDATE review_tasks
SET status = 'processing',
assigned_operator_id = :operatorId
WHERE id = :id
AND status = 'pending';
检查影响行数:
1 → 领取成功
0 → 已被其他审核员领取
不一定需要Redis锁。
数据库原子更新更简单。
第九十九章 数据库事务场景四:签到
唯一报名记录上更新:
not_checked_in → checked_in
重复签到:
返回已签到,而不是插入第二条记录。
第一百章 数据归档策略
V1无需复杂冷热分层。
但应预留:
messages和audit_logs未来可能体量最大。
后续可按:
- 时间分区
- 月度分区
- 历史归档
扩展。
第一百零一章 messages未来分区建议
如果消息量明显增长:
可按:
created_at
按月分区。
V1阶段无需提前复杂化。
第一百零二章 audit_logs未来分区
同样可按:
月份。
因为操作日志通常只追加。
第一百零三章 数据库迁移工具
必须使用迁移工具。
Java:
Flyway / Liquibase
Node:
Prisma migrations / TypeORM migrations
不能生产环境:
手工改表。
第一百零四章 migration命名
例如:
V001__init_users.sql
V002__add_profiles.sql
V003__add_review_tables.sql
V004__add_match_tables.sql
每次变更可追溯。
第一百零五章 初始化字典数据
建议migration或seed初始化:
- 兴趣字典
- 教育类型
- 职业分类
- 审核原因
- 权限
- 默认角色
- 默认匹配规则
第一百零六章 测试数据策略
开发环境可提供:
seed脚本。
生成:
- 男用户
- 女用户
- 不同年龄
- 不同婚姻状态
- 不同择偶条件
- 匹配/不匹配数据
方便测试匹配算法。
第一百零七章 生产数据严禁复制到开发环境
尤其不得直接复制:
- 实名信息
- 聊天记录
- 用户照片
- 举报证据
如确需问题复现:
使用脱敏数据。
第一百零八章 备份对象
至少备份:
PostgreSQL
对象存储关键Bucket
系统配置
数据库迁移文件
Redis:
原则上不需要作为业务恢复唯一依赖。
第一百零九章 数据恢复优先级
第一:
用户与业务数据库。
第二:
用户照片与证据。
第三:
配置。
Redis可以:
重建。
第一百一十章 敏感数据访问原则
实名数据库字段不能被:
- 推荐模块
- 活动普通列表
- 红娘候选列表
- 统计模块
直接查询。
建议代码层也分Repository。
例如:
IdentityRepository
只允许Verification/SensitiveAdmin服务使用。
第一百一十一章 数据库账号权限
生产环境至少拆:
app_rw
migration_admin
backup_user
readonly_reporting
后台运营人员绝不能直接拥有数据库账号。
第一百一十二章 数据最小化
没有业务必要就不采集。
例如V1不采集:
- 房产证
- 车辆信息
- 详细收入
- 银行信息
- 精确住宅地址
数据库也不要“先预留几十个字段以后可能用”。
第一百一十三章 业务唯一约束汇总
必须有:
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
因此:
账号关闭优先状态化,而不是删除。
第一百一十六章 统计数据来源
早期后台统计从业务表聚合。
例如:
成功牵手:
matches.status = successful
审核通过:
user_profiles.profile_status = approved
不要前端自己累加。
第一百一十七章 后续可增加每日汇总表
例如:
daily_platform_metrics
字段:
date
new_users
verified_users
approved_users
likes
mutual_matches
chat_started
activity_registrations
successful_matches
reports
当实时统计性能不足时再启用。
第一百一十八章 推荐记录的数据价值
recommendation_records后续可以分析:
曝光 → 查看
查看 → 喜欢
喜欢 → 双向
双向 → 聊天
这比一开始建设复杂AI推荐系统更重要。
第一百一十九章 未来AI匹配的数据基础
如果未来引入AI,现有表已经提供:
- 基础条件
- 兴趣
- 婚恋计划
- 推荐曝光
- 喜欢行为
- 双向匹配
- 解除匹配
- 成功牵手
可以作为后续推荐优化基础。
因此V1不需要提前做向量数据库。
第一百二十章 数据库V1明确不建设
暂不需要:
- ElasticSearch
- Neo4j
- Milvus
- ClickHouse
- Kafka存储
- 数据湖
- 特征库
- AI向量表
- 推荐模型训练表
- 支付订单表
- VIP表
- 礼物表
- 直播表
- 短视频表
第一百二十一章 核心表开发优先级
P0
第一批:
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
第二批:
activities
activity_registrations
activity_feedback
matchmaker_services
matchmaker_recommendations
matchmaker_followups
notifications
P2
第三批:
reports
report_evidences
user_risk_profiles
risk_events
risk_actions
appeals
system_configs
match_rule_configs
sensitive_access_logs
风控功能本身仍是V1必做,这里的P2只是数据库实施排期顺序,不代表可以不上线。
第一百二十二章 数据库验收重点
必须验证:
用户
一个微信账号不能生成多个正常用户。
身份
一个认证身份证不能无限重复注册。
关系
不能自己喜欢自己。
Match
一对用户不能生成重复Match。
消息
重复clientMessageId不能生成两条消息。
活动
同一用户不能重复报名同一活动。
审核
一个审核任务不能被多人同时完成。
权限
敏感信息不进入普通查询结果。
第一百二十三章 数据一致性验收
必须重点测试:
喜欢成功但Match失败
Match成功但Conversation失败
活动报名成功但人数超限
举报处理后User状态未同步
用户拉黑后Conversation还能发送
成功牵手后仍进入推荐池
这些都必须通过事务和业务状态保证一致。
第一百二十四章 数据库ER文字总图
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查询顺手返回。
每个高风险业务都依赖数据库约束兜底,而不是只相信前端。
最终数据库不是为了“表越多越专业”,而是为了保证:
用户、匹配、聊天、活动、红娘、审核和安全这几条核心业务链在任何异常、并发和后续扩展场景下,都能保持数据清晰、状态一致、责任可追溯。