507 lines
30 KiB
SQL
507 lines
30 KiB
SQL
-- ============================================================
|
||
-- EchoChat 数据库初始化脚本
|
||
-- 包含模块:auth(用户认证)、contact(联系人)、im(即时通讯)、group(群聊)
|
||
-- ============================================================
|
||
|
||
-- ============================================================
|
||
-- auth_users: 用户主表
|
||
-- 存储系统所有用户(包括普通用户和管理员),通过角色表区分权限
|
||
-- ============================================================
|
||
CREATE TABLE auth_users (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
username VARCHAR(50) UNIQUE NOT NULL,
|
||
email VARCHAR(100) UNIQUE NOT NULL,
|
||
password_hash VARCHAR(255) NOT NULL,
|
||
nickname VARCHAR(50) NOT NULL DEFAULT '',
|
||
avatar VARCHAR(500) NOT NULL DEFAULT '',
|
||
gender SMALLINT NOT NULL DEFAULT 0,
|
||
phone VARCHAR(20) DEFAULT NULL,
|
||
status SMALLINT NOT NULL DEFAULT 1,
|
||
last_login_at TIMESTAMP(0) DEFAULT NULL,
|
||
last_login_ip VARCHAR(50) DEFAULT NULL,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE auth_users IS '用户主表,存储所有用户信息(普通用户与管理员共用)';
|
||
COMMENT ON COLUMN auth_users.id IS '用户唯一标识,自增主键';
|
||
COMMENT ON COLUMN auth_users.username IS '用户名,全局唯一,用于登录';
|
||
COMMENT ON COLUMN auth_users.email IS '邮箱地址,全局唯一,用于登录和通知';
|
||
COMMENT ON COLUMN auth_users.password_hash IS '密码哈希值,使用 bcrypt 加密存储';
|
||
COMMENT ON COLUMN auth_users.nickname IS '用户昵称,用于前端显示';
|
||
COMMENT ON COLUMN auth_users.avatar IS '头像 URL 地址';
|
||
COMMENT ON COLUMN auth_users.gender IS '性别:0=未知,1=男,2=女';
|
||
COMMENT ON COLUMN auth_users.phone IS '手机号码,可选';
|
||
COMMENT ON COLUMN auth_users.status IS '账号状态:1=正常,2=禁用(管理员封禁),3=注销(用户主动注销)';
|
||
COMMENT ON COLUMN auth_users.last_login_at IS '最后一次登录时间';
|
||
COMMENT ON COLUMN auth_users.last_login_ip IS '最后一次登录 IP 地址';
|
||
COMMENT ON COLUMN auth_users.created_at IS '账号创建时间';
|
||
COMMENT ON COLUMN auth_users.updated_at IS '信息最后更新时间';
|
||
|
||
-- ============================================================
|
||
-- auth_roles: 角色表
|
||
-- 系统预置角色,用于 RBAC 权限控制
|
||
-- ============================================================
|
||
CREATE TABLE auth_roles (
|
||
id SERIAL PRIMARY KEY,
|
||
code VARCHAR(50) UNIQUE NOT NULL,
|
||
name VARCHAR(50) NOT NULL,
|
||
level INT NOT NULL DEFAULT 100,
|
||
description VARCHAR(200) DEFAULT '',
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE auth_roles IS '角色表,定义系统中所有角色类型';
|
||
COMMENT ON COLUMN auth_roles.id IS '角色唯一标识,自增主键';
|
||
COMMENT ON COLUMN auth_roles.code IS '角色代码,唯一标识:user=普通用户,admin=管理员,super_admin=超级管理员';
|
||
COMMENT ON COLUMN auth_roles.name IS '角色显示名称,如"普通用户""管理员""超级管理员"';
|
||
COMMENT ON COLUMN auth_roles.level IS '角色等级,值越小权限越高:1=超管, 10=管理员, 100=普通用户,预留间隔便于扩展';
|
||
COMMENT ON COLUMN auth_roles.description IS '角色描述说明';
|
||
COMMENT ON COLUMN auth_roles.created_at IS '创建时间';
|
||
|
||
-- 预置角色数据(level 值越小权限越高)
|
||
INSERT INTO auth_roles (code, name, level, description) VALUES
|
||
('user', '普通用户', 100, '系统普通用户,可以使用聊天、会议等功能'),
|
||
('admin', '管理员', 10, '后台管理员,可以管理用户、监控会议等'),
|
||
('super_admin', '超级管理员', 1, '最高权限管理员,可以管理角色和系统配置');
|
||
|
||
-- ============================================================
|
||
-- auth_user_roles: 用户角色关联表
|
||
-- 多对多关系,一个用户可拥有多个角色
|
||
-- ============================================================
|
||
CREATE TABLE auth_user_roles (
|
||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||
role_id INT NOT NULL REFERENCES auth_roles(id),
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
PRIMARY KEY (user_id, role_id)
|
||
);
|
||
|
||
COMMENT ON TABLE auth_user_roles IS '用户角色关联表,建立用户与角色的多对多关系';
|
||
COMMENT ON COLUMN auth_user_roles.user_id IS '关联的用户 ID';
|
||
COMMENT ON COLUMN auth_user_roles.role_id IS '关联的角色 ID';
|
||
COMMENT ON COLUMN auth_user_roles.created_at IS '角色分配时间';
|
||
|
||
-- ============================================================
|
||
-- auth_oauth_accounts: 第三方登录账号绑定表
|
||
-- 存储微信/QQ 等 OAuth 账号与本地 auth_users 的绑定关系
|
||
-- ============================================================
|
||
CREATE TABLE auth_oauth_accounts (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||
provider VARCHAR(20) NOT NULL,
|
||
open_id VARCHAR(128) NOT NULL,
|
||
union_id VARCHAR(128) NOT NULL DEFAULT '',
|
||
nickname VARCHAR(100) NOT NULL DEFAULT '',
|
||
avatar VARCHAR(500) NOT NULL DEFAULT '',
|
||
access_token VARCHAR(1024) NOT NULL DEFAULT '',
|
||
refresh_token VARCHAR(1024) NOT NULL DEFAULT '',
|
||
expires_at TIMESTAMP(0) DEFAULT NULL,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
UNIQUE (provider, open_id)
|
||
);
|
||
|
||
COMMENT ON TABLE auth_oauth_accounts IS '第三方登录账号绑定表';
|
||
COMMENT ON COLUMN auth_oauth_accounts.user_id IS '关联本地用户 ID';
|
||
COMMENT ON COLUMN auth_oauth_accounts.provider IS '第三方平台标识:wechat / qq';
|
||
COMMENT ON COLUMN auth_oauth_accounts.open_id IS '第三方平台用户 OpenID';
|
||
COMMENT ON COLUMN auth_oauth_accounts.union_id IS '第三方平台 UnionID';
|
||
COMMENT ON COLUMN auth_oauth_accounts.nickname IS '第三方平台昵称快照';
|
||
COMMENT ON COLUMN auth_oauth_accounts.avatar IS '第三方平台头像快照';
|
||
COMMENT ON COLUMN auth_oauth_accounts.access_token IS '第三方平台 access_token';
|
||
COMMENT ON COLUMN auth_oauth_accounts.refresh_token IS '第三方平台 refresh_token';
|
||
COMMENT ON COLUMN auth_oauth_accounts.expires_at IS '第三方平台 token 过期时间';
|
||
|
||
CREATE INDEX idx_auth_oauth_accounts_user ON auth_oauth_accounts (user_id);
|
||
CREATE INDEX idx_auth_oauth_accounts_union ON auth_oauth_accounts (union_id);
|
||
|
||
-- ============================================================
|
||
-- 插入默认超级管理员(系统预置唯一账号)
|
||
-- 用户名: super_admin 密码: admin123456 (bcrypt hash)
|
||
-- 实际部署时应更换密码
|
||
-- ============================================================
|
||
INSERT INTO auth_users (username, email, password_hash, nickname, status)
|
||
VALUES ('super_admin', 'super_admin@echochat.com', '$2a$10$Osjn5JVXuEHhtPwBW5Lyo.4gnyYtpFAZYrmbf5fzvN.M5DqcSggb2', '超级管理员', 1);
|
||
|
||
INSERT INTO auth_user_roles (user_id, role_id)
|
||
VALUES (1, (SELECT id FROM auth_roles WHERE code = 'super_admin'));
|
||
|
||
-- ============================================================
|
||
-- contact_friendships: 好友关系表
|
||
-- 双向存储:A→B 和 B→A 各一条记录,便于查询"我的好友列表"
|
||
-- ============================================================
|
||
CREATE TABLE contact_friendships (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||
friend_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||
remark VARCHAR(50) DEFAULT '',
|
||
group_id BIGINT DEFAULT NULL,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
message VARCHAR(200) DEFAULT '',
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
UNIQUE (user_id, friend_id)
|
||
);
|
||
|
||
COMMENT ON TABLE contact_friendships IS '好友关系表,双向存储(A→B和B→A各一条记录)';
|
||
COMMENT ON COLUMN contact_friendships.id IS '记录唯一标识';
|
||
COMMENT ON COLUMN contact_friendships.user_id IS '发起方用户 ID';
|
||
COMMENT ON COLUMN contact_friendships.friend_id IS '好友用户 ID';
|
||
COMMENT ON COLUMN contact_friendships.remark IS '好友备注名,仅对当前用户可见';
|
||
COMMENT ON COLUMN contact_friendships.group_id IS '所属好友分组 ID,关联 contact_groups 表';
|
||
COMMENT ON COLUMN contact_friendships.status IS '好友关系状态:0=待确认(已发送申请),1=已接受(互为好友),2=已拒绝,3=已拉黑';
|
||
COMMENT ON COLUMN contact_friendships.message IS '好友申请附言';
|
||
COMMENT ON COLUMN contact_friendships.created_at IS '记录创建时间(申请发送时间)';
|
||
COMMENT ON COLUMN contact_friendships.updated_at IS '最后更新时间(状态变更时间)';
|
||
|
||
CREATE INDEX idx_friendships_user_status ON contact_friendships (user_id, status);
|
||
CREATE INDEX idx_friendships_friend_status ON contact_friendships (friend_id, status);
|
||
|
||
-- ============================================================
|
||
-- contact_groups: 好友分组表
|
||
-- 每个用户可自定义好友分组
|
||
-- ============================================================
|
||
CREATE TABLE contact_groups (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||
name VARCHAR(50) NOT NULL,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE contact_groups IS '好友分组表,用户可自定义分组管理好友';
|
||
COMMENT ON COLUMN contact_groups.id IS '分组唯一标识';
|
||
COMMENT ON COLUMN contact_groups.user_id IS '所属用户 ID';
|
||
COMMENT ON COLUMN contact_groups.name IS '分组名称,如"同事""家人""朋友"等';
|
||
COMMENT ON COLUMN contact_groups.sort_order IS '排序权重,数值越小越靠前';
|
||
COMMENT ON COLUMN contact_groups.created_at IS '创建时间';
|
||
|
||
CREATE INDEX idx_contact_groups_user ON contact_groups (user_id);
|
||
|
||
-- ============================================================
|
||
-- im_conversations: 会话表
|
||
-- 存储单聊(type=1)和群聊(type=2,预留)会话
|
||
-- 冗余 last_msg_* 字段以避免会话列表查询时 JOIN 消息表
|
||
-- ============================================================
|
||
CREATE TABLE im_conversations (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
type SMALLINT NOT NULL DEFAULT 1,
|
||
creator_id BIGINT NOT NULL,
|
||
last_message_id BIGINT DEFAULT NULL,
|
||
last_msg_content TEXT DEFAULT '',
|
||
last_msg_time TIMESTAMP(0) DEFAULT NULL,
|
||
last_msg_sender_id BIGINT DEFAULT NULL,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE im_conversations IS '会话表,支持单聊(type=1)和群聊(type=2,预留)';
|
||
COMMENT ON COLUMN im_conversations.id IS '会话唯一标识,自增主键';
|
||
COMMENT ON COLUMN im_conversations.type IS '会话类型:1=单聊,2=群聊(预留)';
|
||
COMMENT ON COLUMN im_conversations.creator_id IS '会话创建者用户 ID';
|
||
COMMENT ON COLUMN im_conversations.last_message_id IS '最后一条消息 ID,用于快速定位';
|
||
COMMENT ON COLUMN im_conversations.last_msg_content IS '最后消息预览文本,用于会话列表展示';
|
||
COMMENT ON COLUMN im_conversations.last_msg_time IS '最后消息时间,用于会话列表排序';
|
||
COMMENT ON COLUMN im_conversations.last_msg_sender_id IS '最后消息发送者 ID';
|
||
COMMENT ON COLUMN im_conversations.created_at IS '会话创建时间';
|
||
COMMENT ON COLUMN im_conversations.updated_at IS '会话信息更新时间';
|
||
|
||
CREATE INDEX idx_im_conversations_updated ON im_conversations (updated_at DESC);
|
||
|
||
-- ============================================================
|
||
-- im_conversation_members: 会话成员表
|
||
-- 每个会话的每个成员一条记录,存储个人视图(置顶/未读/软删除)
|
||
-- 单聊两条记录,群聊 N 条记录
|
||
-- ============================================================
|
||
CREATE TABLE im_conversation_members (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
conversation_id BIGINT NOT NULL REFERENCES im_conversations(id),
|
||
user_id BIGINT NOT NULL,
|
||
is_pinned BOOLEAN DEFAULT FALSE,
|
||
is_deleted BOOLEAN DEFAULT FALSE,
|
||
unread_count INT DEFAULT 0,
|
||
last_read_msg_id BIGINT DEFAULT 0,
|
||
clear_before_msg_id BIGINT DEFAULT 0,
|
||
role SMALLINT NOT NULL DEFAULT 0,
|
||
nickname VARCHAR(50) DEFAULT '',
|
||
is_muted BOOLEAN NOT NULL DEFAULT FALSE,
|
||
is_do_not_disturb BOOLEAN NOT NULL DEFAULT FALSE,
|
||
joined_at TIMESTAMP(0) DEFAULT NULL,
|
||
at_me_count INT DEFAULT 0,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
UNIQUE (conversation_id, user_id)
|
||
);
|
||
|
||
COMMENT ON TABLE im_conversation_members IS '会话成员表,存储每个成员对会话的个人视图';
|
||
COMMENT ON COLUMN im_conversation_members.id IS '记录唯一标识';
|
||
COMMENT ON COLUMN im_conversation_members.conversation_id IS '所属会话 ID';
|
||
COMMENT ON COLUMN im_conversation_members.user_id IS '成员用户 ID';
|
||
COMMENT ON COLUMN im_conversation_members.is_pinned IS '是否置顶该会话';
|
||
COMMENT ON COLUMN im_conversation_members.is_deleted IS '是否删除该会话(软删除,不影响对方视图)';
|
||
COMMENT ON COLUMN im_conversation_members.unread_count IS '该成员在此会话中的未读消息数';
|
||
COMMENT ON COLUMN im_conversation_members.last_read_msg_id IS '该成员最后已读消息 ID';
|
||
COMMENT ON COLUMN im_conversation_members.clear_before_msg_id IS '清空聊天记录时的消息截止 ID(个人视图,不影响对方)';
|
||
COMMENT ON COLUMN im_conversation_members.role IS '成员角色:0=普通成员,1=管理员,2=群主';
|
||
COMMENT ON COLUMN im_conversation_members.nickname IS '群内昵称(仅群聊有效)';
|
||
COMMENT ON COLUMN im_conversation_members.is_muted IS '是否被禁言';
|
||
COMMENT ON COLUMN im_conversation_members.is_do_not_disturb IS '是否消息免打扰(仍计未读但灰色显示,不推送通知)';
|
||
COMMENT ON COLUMN im_conversation_members.joined_at IS '加入群聊时间';
|
||
COMMENT ON COLUMN im_conversation_members.at_me_count IS '被@提醒未读计数,打开会话后清零';
|
||
COMMENT ON COLUMN im_conversation_members.created_at IS '加入会话时间';
|
||
COMMENT ON COLUMN im_conversation_members.updated_at IS '最后更新时间';
|
||
|
||
CREATE INDEX idx_im_conv_members_user ON im_conversation_members (user_id, is_deleted);
|
||
CREATE INDEX idx_im_conv_members_conv ON im_conversation_members (conversation_id);
|
||
|
||
-- ============================================================
|
||
-- im_messages: 消息表
|
||
-- 存储所有聊天消息,通过 conversation_id 关联会话
|
||
-- 游标分页查询核心索引:(conversation_id, created_at DESC)
|
||
-- ============================================================
|
||
CREATE TABLE im_messages (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
conversation_id BIGINT NOT NULL REFERENCES im_conversations(id),
|
||
sender_id BIGINT NOT NULL,
|
||
type SMALLINT NOT NULL DEFAULT 1,
|
||
content TEXT NOT NULL,
|
||
extra JSONB DEFAULT NULL,
|
||
status SMALLINT NOT NULL DEFAULT 1,
|
||
client_msg_id VARCHAR(64) DEFAULT '',
|
||
at_user_ids BIGINT[] DEFAULT NULL,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE im_messages IS '聊天消息表,存储所有消息内容';
|
||
COMMENT ON COLUMN im_messages.id IS '消息唯一标识,自增主键';
|
||
COMMENT ON COLUMN im_messages.conversation_id IS '所属会话 ID';
|
||
COMMENT ON COLUMN im_messages.sender_id IS '发送者用户 ID';
|
||
COMMENT ON COLUMN im_messages.type IS '消息类型:1=文本,2=图片(预留),3=语音(预留),4=视频(预留),5=文件(预留)';
|
||
COMMENT ON COLUMN im_messages.content IS '消息内容(文本消息为纯文本,其他类型为 URL 或描述)';
|
||
COMMENT ON COLUMN im_messages.extra IS '扩展数据(JSON 格式),预留图片尺寸、语音时长等元信息';
|
||
COMMENT ON COLUMN im_messages.status IS '消息状态:1=正常,2=已撤回,3=已删除';
|
||
COMMENT ON COLUMN im_messages.client_msg_id IS '客户端消息唯一 ID,用于幂等去重防止网络重试重复发送';
|
||
COMMENT ON COLUMN im_messages.at_user_ids IS '@提醒用户 ID 列表,NULL=无@,包含 0 表示 @所有人';
|
||
COMMENT ON COLUMN im_messages.created_at IS '消息发送时间';
|
||
|
||
CREATE INDEX idx_im_messages_conv_time ON im_messages (conversation_id, created_at DESC);
|
||
CREATE INDEX idx_im_messages_conv_id ON im_messages (conversation_id, id DESC);
|
||
CREATE INDEX idx_im_messages_content_search ON im_messages USING gin(to_tsvector('simple', content));
|
||
|
||
-- ============================================================
|
||
-- im_groups: 群聊信息表
|
||
-- 与 im_conversations (type=2) 一对一关联,存储群聊独有属性
|
||
-- ============================================================
|
||
CREATE TABLE im_groups (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
conversation_id BIGINT NOT NULL UNIQUE REFERENCES im_conversations(id),
|
||
name VARCHAR(100) NOT NULL DEFAULT '',
|
||
avatar VARCHAR(500) DEFAULT '',
|
||
owner_id BIGINT NOT NULL,
|
||
notice TEXT DEFAULT '',
|
||
max_members INT NOT NULL DEFAULT 200,
|
||
is_searchable BOOLEAN NOT NULL DEFAULT TRUE,
|
||
is_all_muted BOOLEAN NOT NULL DEFAULT FALSE,
|
||
status SMALLINT NOT NULL DEFAULT 1,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE im_groups IS '群聊信息表';
|
||
COMMENT ON COLUMN im_groups.id IS '群唯一标识';
|
||
COMMENT ON COLUMN im_groups.conversation_id IS '关联 im_conversations.id';
|
||
COMMENT ON COLUMN im_groups.name IS '群名称';
|
||
COMMENT ON COLUMN im_groups.avatar IS '群头像 URL(MinIO)';
|
||
COMMENT ON COLUMN im_groups.owner_id IS '群主用户 ID';
|
||
COMMENT ON COLUMN im_groups.notice IS '群公告内容';
|
||
COMMENT ON COLUMN im_groups.max_members IS '最大成员数,默认 200';
|
||
COMMENT ON COLUMN im_groups.is_searchable IS '是否可被搜索发现';
|
||
COMMENT ON COLUMN im_groups.is_all_muted IS '是否全体禁言';
|
||
COMMENT ON COLUMN im_groups.status IS '群状态:1=正常,2=已解散';
|
||
|
||
CREATE INDEX idx_im_groups_owner ON im_groups(owner_id);
|
||
CREATE INDEX idx_im_groups_name ON im_groups USING gin(to_tsvector('simple', name));
|
||
|
||
-- ============================================================
|
||
-- im_group_join_requests: 入群申请表
|
||
-- 记录用户主动申请加入群聊的审批流程
|
||
-- ============================================================
|
||
CREATE TABLE im_group_join_requests (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
group_id BIGINT NOT NULL REFERENCES im_groups(id),
|
||
user_id BIGINT NOT NULL,
|
||
message TEXT DEFAULT '',
|
||
reviewer_id BIGINT DEFAULT NULL,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE im_group_join_requests IS '入群申请表';
|
||
COMMENT ON COLUMN im_group_join_requests.id IS '申请唯一标识';
|
||
COMMENT ON COLUMN im_group_join_requests.group_id IS '目标群 ID';
|
||
COMMENT ON COLUMN im_group_join_requests.user_id IS '申请人用户 ID';
|
||
COMMENT ON COLUMN im_group_join_requests.message IS '申请附言';
|
||
COMMENT ON COLUMN im_group_join_requests.reviewer_id IS '审批人用户 ID';
|
||
COMMENT ON COLUMN im_group_join_requests.status IS '状态:0=待审批,1=通过,2=拒绝';
|
||
|
||
CREATE INDEX idx_group_join_req_group ON im_group_join_requests(group_id, status);
|
||
CREATE INDEX idx_group_join_req_user ON im_group_join_requests(user_id);
|
||
|
||
-- ============================================================
|
||
-- im_message_reads: 群聊消息已读记录表(消息级别)
|
||
-- 复合主键 (message_id, user_id),存储每条群消息的已读用户
|
||
-- ============================================================
|
||
CREATE TABLE im_message_reads (
|
||
message_id BIGINT NOT NULL,
|
||
user_id BIGINT NOT NULL,
|
||
read_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
PRIMARY KEY (message_id, user_id)
|
||
);
|
||
|
||
COMMENT ON TABLE im_message_reads IS '群聊消息已读记录表(消息级别)';
|
||
COMMENT ON COLUMN im_message_reads.message_id IS '消息 ID';
|
||
COMMENT ON COLUMN im_message_reads.user_id IS '已读用户 ID';
|
||
COMMENT ON COLUMN im_message_reads.read_at IS '已读时间';
|
||
|
||
CREATE INDEX idx_msg_reads_user ON im_message_reads(user_id, read_at);
|
||
|
||
-- ============================================================
|
||
-- notify_notifications: 通知消息表
|
||
-- 统一通知中心持久化存储,覆盖好友 / 群聊 / 会议 / 系统广播四类场景
|
||
-- 写入失败不回滚业务;WS 推送失败不回滚入库(降级策略)
|
||
-- ============================================================
|
||
CREATE TABLE notify_notifications (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
user_id BIGINT NOT NULL,
|
||
type VARCHAR(50) NOT NULL,
|
||
title VARCHAR(100) NOT NULL DEFAULT '',
|
||
content VARCHAR(500) NOT NULL DEFAULT '',
|
||
extra JSONB DEFAULT NULL,
|
||
actor_id BIGINT DEFAULT NULL,
|
||
target_type VARCHAR(30) NOT NULL DEFAULT '',
|
||
target_id BIGINT DEFAULT NULL,
|
||
is_read BOOLEAN NOT NULL DEFAULT FALSE,
|
||
read_at TIMESTAMP(0) DEFAULT NULL,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE notify_notifications IS '通知消息表,统一通知中心持久化载体';
|
||
COMMENT ON COLUMN notify_notifications.id IS '通知唯一标识,自增主键';
|
||
COMMENT ON COLUMN notify_notifications.user_id IS '接收者用户 ID(每条通知对应单个用户)';
|
||
COMMENT ON COLUMN notify_notifications.type IS '通知类型常量:friend_request / friend_accepted / friend_rejected / group_invite / group_join_request / group_join_approved / group_join_rejected / group_kicked / group_role_changed / system_broadcast / meeting_invite / meeting_reminder';
|
||
COMMENT ON COLUMN notify_notifications.title IS '通知标题(前端列表主文案)';
|
||
COMMENT ON COLUMN notify_notifications.content IS '通知副文案(详细说明或申请附言)';
|
||
COMMENT ON COLUMN notify_notifications.extra IS '扩展数据 JSON,保存类型相关的原始参数(group_id / request_id / role 等)';
|
||
COMMENT ON COLUMN notify_notifications.actor_id IS '触发此通知的主体用户 ID(申请人 / 邀请人等),系统广播为 NULL';
|
||
COMMENT ON COLUMN notify_notifications.target_type IS '业务对象类型:user / group / meeting / system,用于点击跳转路由';
|
||
COMMENT ON COLUMN notify_notifications.target_id IS '业务对象 ID,与 target_type 联合定位跳转目标';
|
||
COMMENT ON COLUMN notify_notifications.is_read IS '是否已读:FALSE=未读,TRUE=已读';
|
||
COMMENT ON COLUMN notify_notifications.read_at IS '已读时间,未读时为 NULL';
|
||
COMMENT ON COLUMN notify_notifications.created_at IS '通知生成时间';
|
||
|
||
-- 列表查询核心索引(按用户倒序分页)
|
||
CREATE INDEX idx_notify_user_time ON notify_notifications (user_id, created_at DESC);
|
||
-- 未读数统计索引(覆盖索引)
|
||
CREATE INDEX idx_notify_user_unread ON notify_notifications (user_id, is_read) WHERE is_read = FALSE;
|
||
-- 清理任务索引(按创建时间扫描旧数据)
|
||
CREATE INDEX idx_notify_created_at ON notify_notifications (created_at);
|
||
|
||
-- ============================================================
|
||
-- Phase 2e-2 会议 MVP(设计文档 §5.1)
|
||
-- meeting_rooms 会议房间
|
||
-- meeting_participants 参与者记录
|
||
-- meeting_chats 会议内文字聊天
|
||
-- 所有时间字段沿用项目风格 TIMESTAMP(0)(而非设计草案的 TIMESTAMPTZ),与
|
||
-- auth_users / im_messages 等表保持一致;Go 侧 model 搭配 gorm:"type:timestamp(0)"。
|
||
-- ============================================================
|
||
|
||
CREATE TABLE meeting_rooms (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
room_code VARCHAR(20) UNIQUE NOT NULL,
|
||
title VARCHAR(200) NOT NULL,
|
||
host_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||
type SMALLINT NOT NULL DEFAULT 1,
|
||
password_hash VARCHAR(255) DEFAULT NULL,
|
||
max_members INT NOT NULL DEFAULT 50,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
scheduled_at TIMESTAMP(0) DEFAULT NULL,
|
||
started_at TIMESTAMP(0) DEFAULT NULL,
|
||
ended_at TIMESTAMP(0) DEFAULT NULL,
|
||
ended_reason VARCHAR(20) DEFAULT NULL,
|
||
settings JSONB NOT NULL DEFAULT '{}',
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE meeting_rooms IS '会议房间表';
|
||
COMMENT ON COLUMN meeting_rooms.room_code IS '用户可见的会议号 XXX-XXX-XXX,全局唯一';
|
||
COMMENT ON COLUMN meeting_rooms.title IS '会议标题';
|
||
COMMENT ON COLUMN meeting_rooms.host_id IS '主持人用户 ID,引用 auth_users.id';
|
||
COMMENT ON COLUMN meeting_rooms.type IS '会议类型:1=即时(MVP 仅此),2=预约(Phase 2e-3)';
|
||
COMMENT ON COLUMN meeting_rooms.password_hash IS '入会密码 bcrypt 哈希,NULL 表示无密码';
|
||
COMMENT ON COLUMN meeting_rooms.max_members IS '房间最大成员数;MVP 应用层强制 8,schema 默认 50 供后续扩展';
|
||
COMMENT ON COLUMN meeting_rooms.status IS '0=未开始(仅预约),1=进行中,2=已结束';
|
||
COMMENT ON COLUMN meeting_rooms.scheduled_at IS '预约开始时间,即时会议为 NULL';
|
||
COMMENT ON COLUMN meeting_rooms.started_at IS '实际开始时间,host 首次加入时回填';
|
||
COMMENT ON COLUMN meeting_rooms.ended_at IS '实际结束时间';
|
||
COMMENT ON COLUMN meeting_rooms.ended_reason IS '结束原因:host_ended / empty_ttl / admin_force / system_error';
|
||
COMMENT ON COLUMN meeting_rooms.settings IS 'JSON 配置:{mute_on_join, allow_chat, record_enabled 等}';
|
||
|
||
-- 主持人 + 状态的列表查询(如"我主持的进行中会议")
|
||
CREATE INDEX idx_meeting_rooms_host_status ON meeting_rooms (host_id, status, created_at DESC);
|
||
-- 会议号快速查找(唯一约束已自动建索引,冗余索引已移除;入会查询走 UNIQUE)
|
||
|
||
-- ============================================================
|
||
-- meeting_participants: 参与者记录(出入场完整轨迹)
|
||
-- ON DELETE CASCADE:room 删除时自动清理参与者
|
||
-- UNIQUE(room_id, user_id):同一用户在同一房间只有一条记录(重入仅更新 left_at=NULL)
|
||
-- ============================================================
|
||
CREATE TABLE meeting_participants (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
room_id BIGINT NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
|
||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||
role SMALLINT NOT NULL DEFAULT 0,
|
||
joined_at TIMESTAMP(0) NOT NULL DEFAULT NOW(),
|
||
left_at TIMESTAMP(0) DEFAULT NULL,
|
||
left_reason VARCHAR(20) DEFAULT NULL,
|
||
duration INT NOT NULL DEFAULT 0,
|
||
UNIQUE (room_id, user_id)
|
||
);
|
||
|
||
COMMENT ON TABLE meeting_participants IS '会议参与者记录(一次入会一行,重入更新)';
|
||
COMMENT ON COLUMN meeting_participants.room_id IS '所属会议房间 ID,引用 meeting_rooms.id,CASCADE 删除';
|
||
COMMENT ON COLUMN meeting_participants.user_id IS '参与者用户 ID,引用 auth_users.id';
|
||
COMMENT ON COLUMN meeting_participants.role IS '0=普通,1=主持人(MVP 仅此两档),2=联合主持人(第二期)';
|
||
COMMENT ON COLUMN meeting_participants.joined_at IS '加入时间,记录创建即代表加入';
|
||
COMMENT ON COLUMN meeting_participants.left_at IS '离开时间,NULL 表示仍在会议中';
|
||
COMMENT ON COLUMN meeting_participants.left_reason IS '离会原因:self / kicked / host_end / empty_ttl / disconnect';
|
||
COMMENT ON COLUMN meeting_participants.duration IS '本次参会时长(秒),left_at 写入时同步计算';
|
||
|
||
-- 按房间查询成员(按加入顺序正序,便于"最早加入者接任主持人")
|
||
CREATE INDEX idx_meeting_participants_room ON meeting_participants (room_id, joined_at ASC);
|
||
-- 按用户查询历史("我的会议记录")
|
||
CREATE INDEX idx_meeting_participants_user ON meeting_participants (user_id, joined_at DESC);
|
||
|
||
-- ============================================================
|
||
-- meeting_chats: 会议内文字聊天(独立于 im_messages)
|
||
-- 清理策略:每日扫描 meeting_rooms.status=2 AND ended_at < NOW() - INTERVAL '24 hours'
|
||
-- 批量 DELETE FROM meeting_chats WHERE room_id IN (...)
|
||
-- ============================================================
|
||
CREATE TABLE meeting_chats (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
room_id BIGINT NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
|
||
user_id BIGINT NOT NULL REFERENCES auth_users(id),
|
||
content TEXT NOT NULL,
|
||
created_at TIMESTAMP(0) NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
COMMENT ON TABLE meeting_chats IS '会议内文字聊天消息(会议结束 24 小时后批量清理)';
|
||
COMMENT ON COLUMN meeting_chats.room_id IS '所属会议房间 ID,引用 meeting_rooms.id,CASCADE 删除';
|
||
COMMENT ON COLUMN meeting_chats.user_id IS '发送者用户 ID,引用 auth_users.id';
|
||
COMMENT ON COLUMN meeting_chats.content IS '消息内容(纯文本,MVP 不支持富媒体)';
|
||
COMMENT ON COLUMN meeting_chats.created_at IS '消息发送时间';
|
||
|
||
-- 按房间正序读取(会议室打开时按时间线加载历史聊天)
|
||
CREATE INDEX idx_meeting_chats_room_created ON meeting_chats (room_id, created_at ASC);
|