52 lines
3.1 KiB
PL/PgSQL
52 lines
3.1 KiB
PL/PgSQL
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)** | **这张认证申请单的唯一编号**。<br>后端代码在处理“审核通过”这个动作时,操作的是这个 ID(例如:`Approve(verificationId)`)。 |
|
||
-- | **`user_id`** | BIGINT | **外键 (Foreign Key)** | **谁在申请?**<br>关联到 `users` 表。通过这个字段,我们可以查到申请人的昵称、手机号等信息。 |
|
||
-- | **`role`** | VARCHAR | 普通字段 | **申请什么身份?**<br>枚举值:`player` (打手), `owner` (店长)。<br>因为一个用户可以同时是打手和店长,所以需要区分这条记录是申请哪个身份的。 |
|
||
-- | **`status`** | VARCHAR | 状态字段 | **当前进度如何?**<br>枚举值:`pending` (待审核), `approved` (已通过), `rejected` (已驳回)。 |
|
||
-- | **`materials`** | **JSONB** | **非结构化数据** | **提交了什么证明材料?**<br>**精华设计**:这里不使用多张表或多个字段,而是用 JSON 存。因为打手需要传“身份证+段位图”,店长需要传“营业执照”。不同角色的材料结构不同,用 JSONB 最灵活,以后改规则不需要改表结构。 |
|
||
-- | **`reject_reason`** | TEXT | 文本 | **驳回理由**。<br>只有当 `status` = 'rejected' 时才有值,告诉用户哪里填错了。 |
|
||
-- | **`reviewed_by`** | BIGINT | 审计字段 | **谁审核的?**<br>记录是哪个管理员(Admin ID)点击了通过或拒绝。用于内部追责和审计。 |
|
||
-- | **`reviewed_at`** | TIMESTAMPTZ | 时间字段 | **什么时候审核的?**<br>用于统计管理员的工作效率,或者展示给用户“审核耗时”。 |
|
||
-- | **`created_at`** | TIMESTAMPTZ | 时间字段 | **申请提交时间**。 |
|
||
-- | **`updated_at`** | TIMESTAMPTZ | 时间字段 | **最后更新时间**。<br>比如用户重新上传了图片,这个时间会变。 | |