feat: 优化web

This commit is contained in:
2026-04-23 18:58:13 +08:00
commit 544a2f3428
160 changed files with 27327 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
-- IAM 表结构(与 internal/iam/entity 中 GORM 模型一致;PostgreSQL
-- 执行:psql $DATABASE_URL -f migrations/postgres/001_iam.sql
BEGIN;
-- 租户
CREATE TABLE IF NOT EXISTS iam_tenant (
id varchar(36) PRIMARY KEY,
tenant_code varchar(64) NOT NULL,
tenant_name varchar(128) NOT NULL,
admin_user_id varchar(36) NULL,
status smallint NOT NULL DEFAULT 1,
expire_time timestamptz NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_iam_tenant_code ON iam_tenant (tenant_code);
CREATE INDEX IF NOT EXISTS idx_iam_tenant_deleted_at ON iam_tenant (deleted_at);
-- 部门(根部门 parent_id 为空串)
CREATE TABLE IF NOT EXISTS iam_dept (
id varchar(36) PRIMARY KEY,
tenant_id varchar(36) NOT NULL,
parent_id varchar(36) NOT NULL DEFAULT '',
dept_name varchar(128) NOT NULL,
dept_path text NULL,
leader_id varchar(36) NULL,
sort_order int NOT NULL DEFAULT 0,
status smallint NOT NULL DEFAULT 1,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL
);
CREATE INDEX IF NOT EXISTS idx_dept_tenant ON iam_dept (tenant_id);
CREATE INDEX IF NOT EXISTS idx_dept_parent ON iam_dept (parent_id);
CREATE INDEX IF NOT EXISTS idx_iam_dept_deleted_at ON iam_dept (deleted_at);
-- 用户
CREATE TABLE IF NOT EXISTS iam_user (
id varchar(36) PRIMARY KEY,
tenant_id varchar(36) NOT NULL,
dept_id varchar(36) NULL,
user_name varchar(64) NOT NULL,
real_name varchar(64) NULL,
password_hash varchar(255) NOT NULL,
phone varchar(20) NULL,
email varchar(128) NULL,
avatar varchar(512) NULL,
gender smallint NOT NULL DEFAULT 0,
status smallint NOT NULL DEFAULT 1,
login_attempts int NOT NULL DEFAULT 0,
locked_until timestamptz NULL,
last_login_at timestamptz NULL,
last_login_ip varchar(45) NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL
);
CREATE INDEX IF NOT EXISTS idx_user_tenant ON iam_user (tenant_id);
CREATE INDEX IF NOT EXISTS idx_user_dept ON iam_user (dept_id);
CREATE INDEX IF NOT EXISTS idx_iam_user_deleted_at ON iam_user (deleted_at);
-- 用户-部门关联
CREATE TABLE IF NOT EXISTS iam_user_dept (
id varchar(36) PRIMARY KEY,
user_id varchar(36) NOT NULL,
dept_id varchar(36) NOT NULL,
is_primary boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT uk_user_dept UNIQUE (user_id, dept_id)
);
-- 角色
CREATE TABLE IF NOT EXISTS iam_role (
id varchar(36) PRIMARY KEY,
tenant_id varchar(36) NOT NULL,
role_code varchar(64) NOT NULL,
role_name varchar(128) NOT NULL,
data_scope smallint NOT NULL DEFAULT 4,
description varchar(512) NULL,
is_builtin boolean NOT NULL DEFAULT false,
status smallint NOT NULL DEFAULT 1,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL
);
CREATE INDEX IF NOT EXISTS idx_role_tenant ON iam_role (tenant_id);
CREATE INDEX IF NOT EXISTS idx_iam_role_deleted_at ON iam_role (deleted_at);
-- 菜单(全局)
CREATE TABLE IF NOT EXISTS iam_menu (
id varchar(36) PRIMARY KEY,
parent_id varchar(36) NOT NULL DEFAULT '',
menu_name varchar(128) NOT NULL,
menu_type smallint NOT NULL,
perms varchar(128) NULL,
path varchar(255) NULL,
component varchar(255) NULL,
icon varchar(64) NULL,
sort_order int NOT NULL DEFAULT 0,
is_visible boolean NOT NULL DEFAULT true,
is_builtin boolean NOT NULL DEFAULT false,
external_link varchar(512) NULL,
status smallint NOT NULL DEFAULT 1,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_iam_menu_perms ON iam_menu (perms);
CREATE INDEX IF NOT EXISTS idx_menu_parent ON iam_menu (parent_id);
CREATE INDEX IF NOT EXISTS idx_iam_menu_deleted_at ON iam_menu (deleted_at);
-- 用户-角色
CREATE TABLE IF NOT EXISTS iam_user_role (
id varchar(36) PRIMARY KEY,
user_id varchar(36) NOT NULL,
role_id varchar(36) NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT uk_user_role UNIQUE (user_id, role_id)
);
-- 角色-菜单
CREATE TABLE IF NOT EXISTS iam_role_menu (
id varchar(36) PRIMARY KEY,
role_id varchar(36) NOT NULL,
menu_id varchar(36) NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT uk_role_menu UNIQUE (role_id, menu_id)
);
COMMIT;
+25
View File
@@ -0,0 +1,25 @@
-- 系统参数表(与 internal/system/entity/param_entity.go 中 GORM 模型一致;PostgreSQL
-- 执行:psql $DATABASE_URL -f migrations/postgres/002_system.sql
BEGIN;
CREATE TABLE IF NOT EXISTS system_param (
id varchar(36) PRIMARY KEY,
param_key varchar(100) NOT NULL,
param_value varchar(1000) NOT NULL,
param_type varchar(20) NOT NULL DEFAULT 'text',
param_group varchar(50) NOT NULL DEFAULT 'default',
param_desc varchar(500) NULL,
creator_id varchar(36) NOT NULL DEFAULT '',
create_time timestamptz NULL DEFAULT now(),
last_updater_id varchar(36) NOT NULL DEFAULT '',
update_time timestamptz NULL DEFAULT now()
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_system_param_key ON system_param (param_key);
COMMENT ON TABLE system_param IS '系统运行参数(键值)';
COMMENT ON COLUMN system_param.param_type IS 'text,number,boolean,select';
COMMENT ON COLUMN system_param.param_group IS 'basic,security,business,system,default 等';
COMMIT;
@@ -0,0 +1,17 @@
-- 可选:平台租户初始化(与 entity.PlatformTenantID / TenantCode 约定一致)
-- 需在 001_iam.sql 执行成功后运行
BEGIN;
INSERT INTO iam_tenant (id, tenant_code, tenant_name, status, created_at, updated_at)
VALUES (
'00000000-0000-0000-0000-000000000001',
'platform',
'平台',
1,
now(),
now()
)
ON CONFLICT (tenant_code) DO NOTHING;
COMMIT;
@@ -0,0 +1,122 @@
-- 平台租户内置角色 + 平台管理员账号(与 internal/iam/service 中租户初始化逻辑对齐)
-- 依赖:已执行 001_iam.sql、002_system.sql、003_seed_platform_tenant.sql
--
-- 默认管理员(平台租户 platform):
-- 用户名:admin
-- 密码:Admin@123 bcrypt DefaultCost,与 golang.org/x/crypto/bcrypt 一致)
--
-- 内置角色:
-- tenant_admin — 与 DefaultTenantAdminRoleCode 一致,data_scope=4(全部)
-- user — 普通用户占位,data_scope=1(本人)
BEGIN;
-- 固定 UUID,便于排查与文档引用
-- platform_tenant_id = 00000000-0000-0000-0000-000000000001
-- root_dept_id = 20000000-0000-4000-8000-000000000001
-- role_admin_id = 20000000-0000-4000-8000-000000000002
-- role_user_id = 20000000-0000-4000-8000-000000000006
-- admin_user_id = 20000000-0000-4000-8000-000000000003
-- 1) 平台根部门(与 TenantService.Create 中根部门一致)
INSERT INTO iam_dept (
id, tenant_id, parent_id, dept_name, dept_path, sort_order, status, created_at, updated_at
) VALUES (
'20000000-0000-4000-8000-000000000001',
'00000000-0000-0000-0000-000000000001',
'',
'平台',
'/20000000-0000-4000-8000-000000000001/',
0,
1,
now(),
now()
)
ON CONFLICT (id) DO NOTHING;
-- 2) 内置角色:租户超级管理员(与新租户初始化角色编码一致)
INSERT INTO iam_role (
id, tenant_id, role_code, role_name, data_scope, description, is_builtin, status, created_at, updated_at
) VALUES (
'20000000-0000-4000-8000-000000000002',
'00000000-0000-0000-0000-000000000001',
'tenant_admin',
'超级管理员',
4,
'内置:租户内全部数据权限(与 DefaultTenantAdminRoleCode 一致)',
true,
1,
now(),
now()
)
ON CONFLICT (id) DO NOTHING;
-- 3) 内置角色:普通用户(占位)
INSERT INTO iam_role (
id, tenant_id, role_code, role_name, data_scope, description, is_builtin, status, created_at, updated_at
) VALUES (
'20000000-0000-4000-8000-000000000006',
'00000000-0000-0000-0000-000000000001',
'user',
'普通用户',
1,
'内置:本人数据范围(DataScopeSelf',
true,
1,
now(),
now()
)
ON CONFLICT (id) DO NOTHING;
-- 4) 平台管理员用户(密码 Admin@123)
INSERT INTO iam_user (
id, tenant_id, dept_id, user_name, real_name, password_hash, status, created_at, updated_at
) VALUES (
'20000000-0000-4000-8000-000000000003',
'00000000-0000-0000-0000-000000000001',
'20000000-0000-4000-8000-000000000001',
'admin',
'平台管理员',
'$2a$10$8p7lXpy9mr7hhnAiOA8pNOgAU128xIWFxrU90iqw.F4VSw77vDEYO',
1,
now(),
now()
)
ON CONFLICT (id) DO NOTHING;
-- 5) 用户-部门(主部门)
INSERT INTO iam_user_dept (id, user_id, dept_id, is_primary, created_at)
VALUES (
'20000000-0000-4000-8000-000000000004',
'20000000-0000-4000-8000-000000000003',
'20000000-0000-4000-8000-000000000001',
true,
now()
)
ON CONFLICT (id) DO NOTHING;
-- 6) 用户-角色(绑定 tenant_admin
INSERT INTO iam_user_role (id, user_id, role_id, created_at)
VALUES (
'20000000-0000-4000-8000-000000000005',
'20000000-0000-4000-8000-000000000003',
'20000000-0000-4000-8000-000000000002',
now()
)
ON CONFLICT (id) DO NOTHING;
-- 7) 回写租户管理员
UPDATE iam_tenant
SET admin_user_id = '20000000-0000-4000-8000-000000000003'
WHERE id = '00000000-0000-0000-0000-000000000001';
-- 8) 将「超级管理员」与当前库中全部菜单关联(与 TenantService.Create 一致;无 iam_menu 数据时本步不插入行)
INSERT INTO iam_role_menu (id, role_id, menu_id, created_at)
SELECT gen_random_uuid()::text,
'20000000-0000-4000-8000-000000000002',
m.id,
now()
FROM iam_menu m
ON CONFLICT (role_id, menu_id) DO NOTHING;
COMMIT;
+66
View File
@@ -0,0 +1,66 @@
-- OAuth2 客户端与令牌表(Authorization Code + PKCEopaque access/refresh token
-- 依赖 001_iam.sql
BEGIN;
CREATE TABLE IF NOT EXISTS oauth_client (
id varchar(36) PRIMARY KEY,
client_id varchar(64) NOT NULL UNIQUE,
client_secret_hash varchar(255) NULL,
redirect_uris text NOT NULL,
is_public boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS oauth_authorization_code (
id varchar(36) PRIMARY KEY,
code_hash varchar(64) NOT NULL,
client_id varchar(64) NOT NULL,
redirect_uri text NOT NULL,
user_id varchar(36) NOT NULL,
tenant_id varchar(36) NOT NULL,
scope text NOT NULL DEFAULT '',
code_challenge varchar(128) NOT NULL,
code_challenge_method varchar(16) NOT NULL,
expires_at timestamptz NOT NULL,
used boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_oauth_authorization_code_hash ON oauth_authorization_code (code_hash);
CREATE TABLE IF NOT EXISTS oauth_access_token (
id varchar(36) PRIMARY KEY,
token_hash varchar(64) NOT NULL UNIQUE,
client_id varchar(64) NOT NULL,
user_id varchar(36) NOT NULL,
tenant_id varchar(36) NOT NULL,
scope text NOT NULL DEFAULT '',
expires_at timestamptz NOT NULL,
revoked_at timestamptz NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS oauth_refresh_token (
id varchar(36) PRIMARY KEY,
token_hash varchar(64) NOT NULL UNIQUE,
access_token_id varchar(36) NOT NULL,
client_id varchar(64) NOT NULL,
user_id varchar(36) NOT NULL,
tenant_id varchar(36) NOT NULL,
scope text NOT NULL DEFAULT '',
expires_at timestamptz NOT NULL,
revoked_at timestamptz NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_oauth_refresh_access ON oauth_refresh_token (access_token_id);
-- 开发用公开客户端(PKCE,无 secret);redirect 按实际前端修改
INSERT INTO oauth_client (id, client_id, client_secret_hash, redirect_uris, is_public) VALUES (
'30000000-0000-4000-8000-000000000001',
'spa',
NULL,
'["http://localhost:5173/callback","http://127.0.0.1:5173/callback","http://localhost:3000/callback"]',
true
) ON CONFLICT (client_id) DO NOTHING;
COMMIT;
@@ -0,0 +1,209 @@
-- 全量业务菜单种子(与当前后端能力对齐:IAM 租户/部门/角色/用户/菜单 + System 参数)
-- 依赖:001_iam.sql;建议在 004_seed_platform_builtin.sql 之后执行(以便角色已存在时可挂接权限)
--
-- 约定:
-- menu_type: 1=目录 2=菜单 3=按钮(本脚本仅录入目录+菜单,便于侧栏 /nav)
-- 「概览」(/dashboard) 不入库:全员可进,由前端壳层固定展示,不参与 iam_menu 配置与角色授权
-- path 与 Next 管理端路由对齐(可按实际前端调整)
--
-- 固定菜单 id 前缀 31000000-0000-4000-8000-* 便于识别与文档引用
BEGIN;
INSERT INTO iam_menu (
id, parent_id, menu_name, menu_type, perms, path, component, icon, sort_order,
is_visible, is_builtin, status, created_at, updated_at
) VALUES
-- 工作台(目录)
(
'31000000-0000-4000-8000-000000000001',
'',
'工作台',
1,
'workspace:root',
'',
'',
'',
10,
true,
true,
1,
now(),
now()
),
(
'31000000-0000-4000-8000-000000000003',
'31000000-0000-4000-8000-000000000001',
'个人中心',
2,
'account:profile',
'/dashboard/account',
'',
'👤',
20,
true,
true,
1,
now(),
now()
),
-- 系统:/api/v1/system/param/*
(
'31000000-0000-4000-8000-000000000010',
'',
'系统管理',
1,
'system:module',
'',
'',
'',
40,
true,
true,
1,
now(),
now()
),
(
'31000000-0000-4000-8000-000000000011',
'31000000-0000-4000-8000-000000000010',
'参数配置',
2,
'system:param:list',
'/dashboard/system/param',
'',
'🔧',
10,
true,
true,
1,
now(),
now()
),
-- IAM/api/v1/iam/*
(
'31000000-0000-4000-8000-000000000020',
'',
'权限管理',
1,
'iam:module',
'',
'',
'🛡',
50,
true,
true,
1,
now(),
now()
),
(
'31000000-0000-4000-8000-000000000021',
'31000000-0000-4000-8000-000000000020',
'租户管理',
2,
'iam:tenant:list',
'/dashboard/iam/tenant',
'',
'🏢',
10,
true,
true,
1,
now(),
now()
),
(
'31000000-0000-4000-8000-000000000022',
'31000000-0000-4000-8000-000000000020',
'部门管理',
2,
'iam:dept:tree',
'/dashboard/iam/dept',
'',
'🌳',
20,
true,
true,
1,
now(),
now()
),
(
'31000000-0000-4000-8000-000000000023',
'31000000-0000-4000-8000-000000000020',
'角色管理',
2,
'iam:role:list',
'/dashboard/iam/role',
'',
'👥',
30,
true,
true,
1,
now(),
now()
),
(
'31000000-0000-4000-8000-000000000024',
'31000000-0000-4000-8000-000000000020',
'用户管理',
2,
'iam:user:list',
'/dashboard/iam/user',
'',
'👤',
40,
true,
true,
1,
now(),
now()
),
(
'31000000-0000-4000-8000-000000000025',
'31000000-0000-4000-8000-000000000020',
'资源(菜单)',
2,
'iam:menu:tree',
'/dashboard/iam/resource',
'',
'📋',
50,
true,
true,
1,
now(),
now()
)
ON CONFLICT (id) DO NOTHING;
-- 将上述菜单授权给平台「超级管理员」角色(与 004 中 role id 一致)
INSERT INTO iam_role_menu (id, role_id, menu_id, created_at)
SELECT gen_random_uuid()::text,
'20000000-0000-4000-8000-000000000002',
m.id,
now()
FROM iam_menu m
WHERE m.id IN (
'31000000-0000-4000-8000-000000000001',
'31000000-0000-4000-8000-000000000003',
'31000000-0000-4000-8000-000000000010',
'31000000-0000-4000-8000-000000000011',
'31000000-0000-4000-8000-000000000020',
'31000000-0000-4000-8000-000000000021',
'31000000-0000-4000-8000-000000000022',
'31000000-0000-4000-8000-000000000023',
'31000000-0000-4000-8000-000000000024',
'31000000-0000-4000-8000-000000000025'
)
ON CONFLICT (role_id, menu_id) DO NOTHING;
-- 若曾执行过含「概览」菜单的旧版脚本,可手工清理(避免侧栏与前端固定入口重复):
-- DELETE FROM iam_role_menu WHERE menu_id = '31000000-0000-4000-8000-000000000002';
-- DELETE FROM iam_menu WHERE id = '31000000-0000-4000-8000-000000000002';
COMMIT;
+27
View File
@@ -0,0 +1,27 @@
# PostgreSQL 脚本
`internal/iam/entity``internal/system/entity` 中的 GORM 模型对齐,便于手工建库或对照 AutoMigrate 结果。
**建议顺序**
1. `001_iam.sql` — IAM 表
2. `002_system.sql``system_param`
3. `003_seed_platform_tenant.sql`(可选)— 平台租户一行
4. `004_seed_platform_builtin.sql`(可选)— 内置角色(`tenant_admin``user`+ 平台租户下默认管理员 `admin` / `Admin@123`,并为 `tenant_admin` 绑定当前 `iam_menu` 中全部菜单(与 `TenantService.Create` 行为一致)
5. `005_oauth.sql`(可选)— OAuth2 客户端与令牌表,并插入开发用公开客户端 `spa`
6. `006_seed_iam_menu_full.sql`(可选)— 与当前后端模块对齐的侧栏菜单(不含「概览」:`/dashboard` 由前端壳层固定展示、全员可进);含工作台下「个人中心」、系统参数、IAM 子模块;并为平台超级管理员补 `iam_role_menu``ON CONFLICT DO NOTHING`
**执行示例**
```bash
psql "$DATABASE_URL" -f migrations/postgres/001_iam.sql
psql "$DATABASE_URL" -f migrations/postgres/002_system.sql
psql "$DATABASE_URL" -f migrations/postgres/003_seed_platform_tenant.sql
psql "$DATABASE_URL" -f migrations/postgres/004_seed_platform_builtin.sql
psql "$DATABASE_URL" -f migrations/postgres/005_oauth.sql
psql "$DATABASE_URL" -f migrations/postgres/006_seed_iam_menu_full.sql
```
生产环境请在首次登录后修改默认密码;若需更换哈希,可用与业务相同的 `bcrypt.DefaultCost` 重新生成 `password_hash` 再更新 `iam_user`
若已使用 GORM `AutoMigrate`,可将本目录脚本作为文档或与迁移工具对照,避免重复执行冲突。