-- 1. identity:人、登录方式、企业主体、成员身份、会话(架构 §3.14) -- 来源:docs/architecture/schema-draft.sql(2026-11-03 验证过的草案) CREATE TABLE tenant ( id uuid PRIMARY KEY, name text NOT NULL, uscc text, -- 统一社会信用代码,MVP 可空(G9) status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'closed')), closed_at timestamptz, -- 注销:业务数据删除,本行保留给订单和发票 created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); -- 全局表:不带 tenant_id,不受 RLS 约束,只有 identity 模块能读写 CREATE TABLE user_account ( id uuid PRIMARY KEY, display_name text NOT NULL, status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled', 'anonymized')), anonymized_at timestamptz, -- 注销账号:抹掉个人信息,行保留(data-model §6.2) created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE login_identity ( id uuid PRIMARY KEY, user_id uuid NOT NULL REFERENCES user_account (id), provider text NOT NULL CHECK (provider IN ('phone', 'password', 'feishu', 'wecom', 'oidc', 'ldap')), external_org_id text NOT NULL DEFAULT '', -- 手机号、账号密码:空串 external_user_id text NOT NULL, -- 手机号:HMAC(pepper, E.164 号码),不存明文(DM5) contact_cipher bytea, -- 手机号密文,发短信和运营查询用 contact_hint text, -- 138****5678 secret_hash text, -- 只有 password 来源用(argon2id) verified_at timestamptz, last_login_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (provider, external_org_id, external_user_id) ); CREATE INDEX ON login_identity (user_id); CREATE TABLE membership ( id uuid PRIMARY KEY, tenant_id uuid NOT NULL REFERENCES tenant (id), user_id uuid NOT NULL REFERENCES user_account (id), role text NOT NULL DEFAULT 'owner' CHECK (role IN ('owner', 'admin', 'member')), -- MVP 只有 owner status text NOT NULL DEFAULT 'active' CHECK (status IN ('invited', 'active', 'disabled', 'left')), joined_at timestamptz NOT NULL DEFAULT now(), left_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, id), UNIQUE (tenant_id, user_id) ); CREATE INDEX ON membership (user_id); CREATE TABLE user_session ( id uuid PRIMARY KEY, tenant_id uuid NOT NULL, membership_id uuid NOT NULL, user_id uuid NOT NULL REFERENCES user_account (id), token_hash bytea NOT NULL UNIQUE, -- SHA-256(令牌),令牌本身不落库(AR8) client_kind text NOT NULL CHECK (client_kind IN ('web', 'desktop')), user_agent text, ip inet, created_at timestamptz NOT NULL DEFAULT now(), last_seen_at timestamptz NOT NULL DEFAULT now(), -- 最多每 5 分钟写一次 expires_at timestamptz NOT NULL, revoked_at timestamptz, revoke_reason text CHECK (revoke_reason IN ('logout', 'concurrent_login', 'admin', 'tenant_closed', 'expired')), FOREIGN KEY (tenant_id, membership_id) REFERENCES membership (tenant_id, id) ); CREATE INDEX ON user_session (user_id) WHERE revoked_at IS NULL;