CREATE TABLE user_verifications
(
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users (id),
role VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
materials JSONB NOT NULL,
reject_reason TEXT,
reviewed_by BIGINT,
reviewed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT chk_verification_status CHECK (status IN ('pending', 'approved', 'rejected')),
UNIQUE (user_id, role) -- 每个角色只有一条最新的认证记录
);
-- Note:Auto update verification_status of users table
CREATE OR REPLACE FUNCTION sync_user_verification_status()
RETURNS TRIGGER AS
$$
DECLARE
status_json JSONB;
BEGIN
SELECT jsonb_object_agg(role, status)
INTO status_json
FROM user_verifications
WHERE user_id = NEW.user_id;
UPDATE users SET verification_status = status_json WHERE id = NEW.user_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_sync_verifications
AFTER INSERT OR UPDATE
ON user_verifications
FOR EACH ROW
EXECUTE FUNCTION sync_user_verification_status();
-- | 字段名 | 类型 | 技术含义 | 业务目的与设计思考 |
-- | :--- | :--- | :--- | :--- |
-- | **`id`** | BIGINT | **主键 (Primary Key)** | **这张认证申请单的唯一编号**。
后端代码在处理“审核通过”这个动作时,操作的是这个 ID(例如:`Approve(verificationId)`)。 |
-- | **`user_id`** | BIGINT | **外键 (Foreign Key)** | **谁在申请?**
关联到 `users` 表。通过这个字段,我们可以查到申请人的昵称、手机号等信息。 |
-- | **`role`** | VARCHAR | 普通字段 | **申请什么身份?**
枚举值:`player` (打手), `owner` (店长)。
因为一个用户可以同时是打手和店长,所以需要区分这条记录是申请哪个身份的。 |
-- | **`status`** | VARCHAR | 状态字段 | **当前进度如何?**
枚举值:`pending` (待审核), `approved` (已通过), `rejected` (已驳回)。 |
-- | **`materials`** | **JSONB** | **非结构化数据** | **提交了什么证明材料?**
**精华设计**:这里不使用多张表或多个字段,而是用 JSON 存。因为打手需要传“身份证+段位图”,店长需要传“营业执照”。不同角色的材料结构不同,用 JSONB 最灵活,以后改规则不需要改表结构。 |
-- | **`reject_reason`** | TEXT | 文本 | **驳回理由**。
只有当 `status` = 'rejected' 时才有值,告诉用户哪里填错了。 |
-- | **`reviewed_by`** | BIGINT | 审计字段 | **谁审核的?**
记录是哪个管理员(Admin ID)点击了通过或拒绝。用于内部追责和审计。 |
-- | **`reviewed_at`** | TIMESTAMPTZ | 时间字段 | **什么时候审核的?**
用于统计管理员的工作效率,或者展示给用户“审核耗时”。 |
-- | **`created_at`** | TIMESTAMPTZ | 时间字段 | **申请提交时间**。 |
-- | **`updated_at`** | TIMESTAMPTZ | 时间字段 | **最后更新时间**。
比如用户重新上传了图片,这个时间会变。 |