Files
2026-05-12 16:53:22 +08:00

300 lines
15 KiB
PL/PgSQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ==========================================================================
-- 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);
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_tenant
ON sso_users(status, deleted_at, area, tenant_name);
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_dep_expr
ON sso_users(status, deleted_at, area, (COALESCE(dep_name, dep_short_name, '')));
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_ou_name
ON sso_users(status, deleted_at, area, ou_name);
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_ou_id
ON sso_users(status, deleted_at, area, ou_id);
CREATE INDEX IF NOT EXISTS idx_sso_users_active_tenant_dep_expr
ON sso_users(status, deleted_at, tenant_name, (COALESCE(dep_name, dep_short_name, '')));
CREATE INDEX IF NOT EXISTS idx_sso_users_active_tenant_dep_ou_name
ON sso_users(status, deleted_at, tenant_name, (COALESCE(dep_name, dep_short_name, '')), ou_name);
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;