feat: bootstrap user rbac foundation

This commit is contained in:
wren
2026-04-29 15:23:19 +08:00
parent b45d61fa97
commit b3ad4a6f33
16 changed files with 4498 additions and 104 deletions
+287
View File
@@ -0,0 +1,287 @@
-- ==========================================================================
-- LeAudit Platform RBAC / User Schema Patch
-- 目标:在当前 leaudit_platform 新库中补齐用户、角色、权限、路由核心表
-- 设计原则:
-- 1. 兼容老系统 docauditai 的核心字段语义
-- 2. 新系统只做单地区隔离,地区字段统一使用 sso_users.area
-- 3. 采用 bigint 主键,兼容新库现有 leaudit_* bigint 引用字段
-- 4. 当前库里尚不存在 sso_users / roles / permissions / role_permissions / sys_routes / role_route / user_role
-- ============================================================================
BEGIN;
-- --------------------------------------------------------------------------
-- 1. 用户主表 sso_users
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sso_users (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
sub VARCHAR(128) NOT NULL,
username VARCHAR(128) NOT NULL,
nick_name VARCHAR(128) NOT NULL,
phone_number VARCHAR(64),
email VARCHAR(256),
ou_id VARCHAR(128) NOT NULL,
ou_name VARCHAR(255) NOT NULL,
status SMALLINT NOT NULL DEFAULT 0,
is_leader BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITHOUT TIME ZONE,
password VARCHAR(255),
try_count INTEGER,
try_login_time TIMESTAMP WITHOUT TIME ZONE,
area VARCHAR(64),
mq_person_uuid VARCHAR(64),
mq_account_uuid VARCHAR(64),
mq_synced_at TIMESTAMP WITHOUT TIME ZONE,
tenant_name VARCHAR(255),
dep_short_name VARCHAR(255),
dep_name VARCHAR(255),
CONSTRAINT sso_users_sub_key UNIQUE (sub)
);
CREATE INDEX IF NOT EXISTS idx_sso_users_sub ON sso_users(sub);
CREATE UNIQUE INDEX IF NOT EXISTS idx_sso_users_sub_unique ON sso_users(sub);
CREATE INDEX IF NOT EXISTS idx_sso_users_username ON sso_users(username);
CREATE INDEX IF NOT EXISTS idx_sso_users_area ON sso_users(area) WHERE area IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_sso_users_status ON sso_users(status);
CREATE INDEX IF NOT EXISTS idx_sso_users_deleted_at ON sso_users(deleted_at);
CREATE INDEX IF NOT EXISTS idx_sso_users_ou_id ON sso_users(ou_id);
CREATE INDEX IF NOT EXISTS idx_sso_users_is_leader ON sso_users(is_leader);
CREATE INDEX IF NOT EXISTS idx_sso_users_mq_person ON sso_users(mq_person_uuid);
CREATE INDEX IF NOT EXISTS idx_sso_users_mq_account ON sso_users(mq_account_uuid);
COMMENT ON TABLE sso_users IS '用户主表:认证身份、组织信息、地区隔离基础字段统一沉淀在这里';
COMMENT ON COLUMN sso_users.sub IS '统一身份唯一标识,OAuth / SSO 主键';
COMMENT ON COLUMN sso_users.username IS '登录名/工号/展示账号';
COMMENT ON COLUMN sso_users.nick_name IS '用户真实姓名';
COMMENT ON COLUMN sso_users.area IS '用户主地区,当前系统唯一数据隔离字段';
COMMENT ON COLUMN sso_users.password IS '密码字段:当前阶段兼容旧值,后续应迁移为哈希';
COMMENT ON COLUMN sso_users.status IS '账户状态:0=正常,1=禁用';
COMMENT ON COLUMN sso_users.deleted_at IS '软删除时间';
-- --------------------------------------------------------------------------
-- 2. 角色表 roles
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS roles (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
role_key VARCHAR(64) NOT NULL,
role_name VARCHAR(128) NOT NULL,
data_scope VARCHAR(16) DEFAULT 'SELF',
description VARCHAR(255) DEFAULT '',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
parent_role_id BIGINT,
priority INTEGER DEFAULT 0,
is_system_role BOOLEAN DEFAULT FALSE,
permissions_cache JSONB,
metadata JSONB,
CONSTRAINT roles_role_key_key UNIQUE (role_key),
CONSTRAINT fk_parent_role FOREIGN KEY (parent_role_id) REFERENCES roles(id) ON DELETE SET NULL,
CONSTRAINT chk_roles_data_scope CHECK (data_scope IN ('ALL', 'DEPT', 'SELF', 'GROUP'))
);
CREATE INDEX IF NOT EXISTS idx_roles_parent_role_id ON roles(parent_role_id);
CREATE INDEX IF NOT EXISTS idx_roles_priority ON roles(priority DESC);
CREATE INDEX IF NOT EXISTS idx_roles_permissions_cache ON roles USING GIN (permissions_cache);
COMMENT ON TABLE roles IS '角色表:当前主业务角色只使用 provincial_admin/admin/common,可选 super_admin';
COMMENT ON COLUMN roles.role_key IS '角色机器标识,例如 provincial_admin/admin/common';
COMMENT ON COLUMN roles.role_name IS '角色展示名称';
COMMENT ON COLUMN roles.data_scope IS '默认数据范围:ALL/DEPT/SELFGROUP 仅保留兼容';
COMMENT ON COLUMN roles.priority IS '角色优先级,数值越大优先级越高';
-- --------------------------------------------------------------------------
-- 3. 菜单/路由表 sys_routes
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sys_routes (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
route_path VARCHAR(255) NOT NULL,
route_name VARCHAR(128) NOT NULL,
component VARCHAR(255),
parent_id BIGINT,
route_title VARCHAR(255),
icon VARCHAR(128),
sort_order INTEGER DEFAULT 0,
is_hidden BOOLEAN DEFAULT FALSE,
is_cache BOOLEAN DEFAULT TRUE,
meta JSONB,
status INTEGER DEFAULT 0,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITHOUT TIME ZONE,
CONSTRAINT fk_parent_route FOREIGN KEY (parent_id) REFERENCES sys_routes(id) ON DELETE SET NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_routes_path ON sys_routes(route_path) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_routes_parent_id ON sys_routes(parent_id);
CREATE INDEX IF NOT EXISTS idx_routes_status ON sys_routes(status);
COMMENT ON TABLE sys_routes IS '前端菜单/页面路由表:用于控制角色可见菜单,不替代 API 权限表';
COMMENT ON COLUMN sys_routes.route_path IS '前端路由路径';
COMMENT ON COLUMN sys_routes.status IS '状态:0=启用,1=禁用';
-- --------------------------------------------------------------------------
-- 4. 角色路由关系表 role_route
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS role_route (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
role_id BIGINT NOT NULL,
route_id BIGINT NOT NULL,
permission VARCHAR(8) DEFAULT 'RW',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
status SMALLINT NOT NULL DEFAULT 1,
CONSTRAINT role_route_role_id_route_id_key UNIQUE (role_id, route_id),
CONSTRAINT fk_role_route_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
CONSTRAINT fk_role_route_route FOREIGN KEY (route_id) REFERENCES sys_routes(id) ON DELETE CASCADE,
CONSTRAINT chk_role_route_status CHECK (status IN (0, 1)),
CONSTRAINT chk_role_route_permission CHECK (permission IN ('R', 'W', 'RW'))
);
CREATE INDEX IF NOT EXISTS idx_role_route_status ON role_route(status);
CREATE INDEX IF NOT EXISTS idx_role_route_role_status ON role_route(role_id, status);
COMMENT ON TABLE role_route IS '角色与菜单路由关联表';
COMMENT ON COLUMN role_route.permission IS '路由权限类型:R=读,W=写,RW=读写';
-- --------------------------------------------------------------------------
-- 5. 权限点表 permissions
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS permissions (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
permission_key VARCHAR(100) NOT NULL,
module VARCHAR(50) NOT NULL,
resource VARCHAR(50) NOT NULL,
action VARCHAR(50) NOT NULL,
description TEXT,
display_name VARCHAR(200),
permission_type VARCHAR(20) NOT NULL DEFAULT 'API',
is_system BOOLEAN NOT NULL DEFAULT FALSE,
metadata JSONB,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
created_by BIGINT,
updated_by BIGINT,
parent_id BIGINT,
sort_order INTEGER DEFAULT 0,
route_id BIGINT,
api_path VARCHAR(255),
api_method VARCHAR(16),
related_routes BIGINT[],
CONSTRAINT permissions_permission_key_key UNIQUE (permission_key),
CONSTRAINT permissions_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES permissions(id) ON DELETE SET NULL,
CONSTRAINT permissions_route_id_fkey FOREIGN KEY (route_id) REFERENCES sys_routes(id) ON DELETE SET NULL,
CONSTRAINT permissions_key_format_check CHECK (permission_key ~ '^[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$')
);
CREATE INDEX IF NOT EXISTS idx_permissions_module ON permissions(module);
CREATE INDEX IF NOT EXISTS idx_permissions_parent_id ON permissions(parent_id);
CREATE INDEX IF NOT EXISTS idx_permissions_route_id ON permissions(route_id);
CREATE INDEX IF NOT EXISTS idx_permissions_sort_order ON permissions(sort_order);
CREATE INDEX IF NOT EXISTS idx_permissions_system ON permissions(is_system);
CREATE INDEX IF NOT EXISTS idx_permissions_type ON permissions(permission_type);
CREATE INDEX IF NOT EXISTS idx_permissions_metadata ON permissions USING GIN (metadata);
COMMENT ON TABLE permissions IS '权限点定义表:统一使用 module:resource:action 风格 permission_key';
COMMENT ON COLUMN permissions.permission_key IS '权限键,例如 documents:list:read、audit:run:execute';
COMMENT ON COLUMN permissions.related_routes IS '共享权限可关联多个路由 ID';
-- --------------------------------------------------------------------------
-- 6. 角色权限关系表 role_permissions
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS role_permissions (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
grant_type VARCHAR(10) NOT NULL DEFAULT 'GRANT',
data_scope VARCHAR(20),
condition_filter JSONB,
metadata JSONB,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
created_by BIGINT,
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_by BIGINT,
CONSTRAINT unique_role_permission UNIQUE (role_id, permission_id),
CONSTRAINT role_permissions_role_id_fkey FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
CONSTRAINT role_permissions_permission_id_fkey FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
CONSTRAINT chk_role_permissions_grant_type CHECK (grant_type IN ('GRANT', 'DENY')),
CONSTRAINT chk_role_permissions_data_scope CHECK (data_scope IS NULL OR data_scope IN ('ALL', 'DEPT', 'SELF', 'GROUP'))
);
CREATE INDEX IF NOT EXISTS idx_role_permissions_role_id ON role_permissions(role_id);
CREATE INDEX IF NOT EXISTS idx_role_permissions_permission_id ON role_permissions(permission_id);
CREATE INDEX IF NOT EXISTS idx_role_permissions_grant_type ON role_permissions(grant_type);
CREATE INDEX IF NOT EXISTS idx_role_permissions_data_scope ON role_permissions(data_scope);
CREATE INDEX IF NOT EXISTS idx_role_permissions_lookup ON role_permissions(role_id, grant_type);
CREATE INDEX IF NOT EXISTS idx_role_permissions_condition ON role_permissions USING GIN (condition_filter);
COMMENT ON TABLE role_permissions IS '角色权限关联表:grant_type 兼容旧系统 DENY 语义,当前新系统默认只配置 GRANT';
COMMENT ON COLUMN role_permissions.data_scope IS '数据范围:ALL/DEPT/SELFGROUP 仅保留兼容';
-- --------------------------------------------------------------------------
-- 7. 用户角色关系表 user_role
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS user_role (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT user_role_user_id_role_id_key UNIQUE (user_id, role_id),
CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sso_users(id) ON DELETE CASCADE,
CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_user_role_user_role ON user_role(user_id, role_id);
COMMENT ON TABLE user_role IS '用户与角色关联表;一个用户允许挂多个角色';
-- --------------------------------------------------------------------------
-- 8. 给现有 leaudit_* 业务表补用户外键
-- 当前库中这些字段均为 bigint,且当前全为空,可安全补充外键。
-- --------------------------------------------------------------------------
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_document_files_created_by'
) THEN
ALTER TABLE leaudit_document_files
ADD CONSTRAINT fk_leaudit_document_files_created_by
FOREIGN KEY (created_by) REFERENCES sso_users(id) ON DELETE SET NULL;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_audit_runs_trigger_user_id'
) THEN
ALTER TABLE leaudit_audit_runs
ADD CONSTRAINT fk_leaudit_audit_runs_trigger_user_id
FOREIGN KEY (trigger_user_id) REFERENCES sso_users(id) ON DELETE SET NULL;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_rule_sets_owner_user_id'
) THEN
ALTER TABLE leaudit_rule_sets
ADD CONSTRAINT fk_leaudit_rule_sets_owner_user_id
FOREIGN KEY (owner_user_id) REFERENCES sso_users(id) ON DELETE SET NULL;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_rule_versions_editor_user_id'
) THEN
ALTER TABLE leaudit_rule_versions
ADD CONSTRAINT fk_leaudit_rule_versions_editor_user_id
FOREIGN KEY (editor_user_id) REFERENCES sso_users(id) ON DELETE SET NULL;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'fk_leaudit_rule_versions_publisher_user_id'
) THEN
ALTER TABLE leaudit_rule_versions
ADD CONSTRAINT fk_leaudit_rule_versions_publisher_user_id
FOREIGN KEY (publisher_user_id) REFERENCES sso_users(id) ON DELETE SET NULL;
END IF;
END $$;
COMMIT;