Compare commits
No commits in common. "a8f20a70fb13868e2ca37c4a5ae04d26fb026c3f" and "e8e352b6b6f006544341786f24e87f01016c09ce" have entirely different histories.
a8f20a70fb
...
e8e352b6b6
@ -1,3 +1,2 @@
|
||||
import { relations } from "drizzle-orm/relations";
|
||||
import { } from "./schema";
|
||||
|
||||
import { relations } from 'drizzle-orm/relations';
|
||||
import {} from './schema';
|
||||
|
@ -1,277 +1,359 @@
|
||||
import { mysqlTable, mysqlSchema, AnyMySqlColumn, index, primaryKey, unique, bigint, varchar, int, json, timestamp, text, datetime, tinyint, date } from "drizzle-orm/mysql-core"
|
||||
import { sql } from "drizzle-orm"
|
||||
import {
|
||||
mysqlTable,
|
||||
mysqlSchema,
|
||||
AnyMySqlColumn,
|
||||
index,
|
||||
primaryKey,
|
||||
unique,
|
||||
bigint,
|
||||
varchar,
|
||||
int,
|
||||
json,
|
||||
timestamp,
|
||||
text,
|
||||
datetime,
|
||||
tinyint,
|
||||
date,
|
||||
} from 'drizzle-orm/mysql-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
export const sysDict = mysqlTable("sys_dict", {
|
||||
id: bigint({ mode: "number" }).autoincrement().notNull(),
|
||||
code: varchar({ length: 50 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
value: varchar({ length: 200 }),
|
||||
description: varchar({ length: 500 }),
|
||||
icon: varchar({ length: 100 }),
|
||||
pid: bigint({ mode: "number" }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int("sort_order").default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
isSystem: tinyint("is_system").default(0).notNull(),
|
||||
color: varchar({ length: 20 }),
|
||||
extra: json(),
|
||||
createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().onUpdateNow().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_level").on(table.level),
|
||||
index("idx_pid").on(table.pid),
|
||||
index("idx_sort").on(table.sortOrder),
|
||||
index("idx_status").on(table.status),
|
||||
primaryKey({ columns: [table.id], name: "sys_dict_id"}),
|
||||
unique("uk_code").on(table.code),
|
||||
]);
|
||||
export const sysDict = mysqlTable(
|
||||
'sys_dict',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).autoincrement().notNull(),
|
||||
code: varchar({ length: 50 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
value: varchar({ length: 200 }),
|
||||
description: varchar({ length: 500 }),
|
||||
icon: varchar({ length: 100 }),
|
||||
pid: bigint({ mode: 'number' }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int('sort_order').default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
isSystem: tinyint('is_system').default(0).notNull(),
|
||||
color: varchar({ length: 20 }),
|
||||
extra: json(),
|
||||
createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().onUpdateNow().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_level').on(table.level),
|
||||
index('idx_pid').on(table.pid),
|
||||
index('idx_sort').on(table.sortOrder),
|
||||
index('idx_status').on(table.status),
|
||||
primaryKey({ columns: [table.id], name: 'sys_dict_id' }),
|
||||
unique('uk_code').on(table.code),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysOperationLogs = mysqlTable("sys_operation_logs", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }),
|
||||
username: varchar({ length: 100 }),
|
||||
module: varchar({ length: 50 }).notNull(),
|
||||
action: varchar({ length: 50 }).notNull(),
|
||||
target: varchar({ length: 200 }),
|
||||
targetId: bigint("target_id", { mode: "number" }),
|
||||
requestData: text("request_data"),
|
||||
responseData: text("response_data"),
|
||||
status: varchar({ length: 20 }).notNull(),
|
||||
ip: varchar({ length: 45 }),
|
||||
userAgent: varchar("user_agent", { length: 200 }),
|
||||
duration: bigint({ mode: "number" }),
|
||||
errorMsg: text("error_msg"),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_created_at").on(table.createdAt),
|
||||
index("idx_ip").on(table.ip),
|
||||
index("idx_module_action").on(table.module, table.action),
|
||||
index("idx_status").on(table.status),
|
||||
index("idx_target").on(table.targetId),
|
||||
index("idx_user_id").on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: "sys_operation_logs_id"}),
|
||||
]);
|
||||
export const sysOperationLogs = mysqlTable(
|
||||
'sys_operation_logs',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }),
|
||||
username: varchar({ length: 100 }),
|
||||
module: varchar({ length: 50 }).notNull(),
|
||||
action: varchar({ length: 50 }).notNull(),
|
||||
target: varchar({ length: 200 }),
|
||||
targetId: bigint('target_id', { mode: 'number' }),
|
||||
requestData: text('request_data'),
|
||||
responseData: text('response_data'),
|
||||
status: varchar({ length: 20 }).notNull(),
|
||||
ip: varchar({ length: 45 }),
|
||||
userAgent: varchar('user_agent', { length: 200 }),
|
||||
duration: bigint({ mode: 'number' }),
|
||||
errorMsg: text('error_msg'),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_created_at').on(table.createdAt),
|
||||
index('idx_ip').on(table.ip),
|
||||
index('idx_module_action').on(table.module, table.action),
|
||||
index('idx_status').on(table.status),
|
||||
index('idx_target').on(table.targetId),
|
||||
index('idx_user_id').on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_operation_logs_id' }),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysOrganizations = mysqlTable("sys_organizations", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
code: varchar({ length: 100 }).notNull(),
|
||||
name: varchar({ length: 200 }).notNull(),
|
||||
fullName: varchar("full_name", { length: 200 }),
|
||||
description: text(),
|
||||
pid: bigint({ mode: "number" }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
type: varchar({ length: 20 }),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
sortOrder: int("sort_order").default(0).notNull(),
|
||||
leaderId: bigint("leader_id", { mode: "number" }),
|
||||
address: varchar({ length: 200 }),
|
||||
phone: varchar({ length: 50 }),
|
||||
extra: json(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
updatedBy: bigint("updated_by", { mode: "number" }),
|
||||
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
deletedAt: datetime("deleted_at", { mode: 'string'}),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_deleted_at").on(table.deletedAt),
|
||||
index("idx_leader_id").on(table.leaderId),
|
||||
index("idx_name").on(table.name),
|
||||
index("idx_path").on(table.path),
|
||||
index("idx_pid").on(table.pid),
|
||||
index("idx_sort").on(table.pid, table.sortOrder),
|
||||
index("idx_status").on(table.status),
|
||||
index("idx_type").on(table.type),
|
||||
primaryKey({ columns: [table.id], name: "sys_organizations_id"}),
|
||||
unique("uk_code").on(table.code, table.deletedAt),
|
||||
]);
|
||||
export const sysOrganizations = mysqlTable(
|
||||
'sys_organizations',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
code: varchar({ length: 100 }).notNull(),
|
||||
name: varchar({ length: 200 }).notNull(),
|
||||
fullName: varchar('full_name', { length: 200 }),
|
||||
description: text(),
|
||||
pid: bigint({ mode: 'number' }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
type: varchar({ length: 20 }),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
sortOrder: int('sort_order').default(0).notNull(),
|
||||
leaderId: bigint('leader_id', { mode: 'number' }),
|
||||
address: varchar({ length: 200 }),
|
||||
phone: varchar({ length: 50 }),
|
||||
extra: json(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
updatedBy: bigint('updated_by', { mode: 'number' }),
|
||||
updatedAt: datetime('updated_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_leader_id').on(table.leaderId),
|
||||
index('idx_name').on(table.name),
|
||||
index('idx_path').on(table.path),
|
||||
index('idx_pid').on(table.pid),
|
||||
index('idx_sort').on(table.pid, table.sortOrder),
|
||||
index('idx_status').on(table.status),
|
||||
index('idx_type').on(table.type),
|
||||
primaryKey({ columns: [table.id], name: 'sys_organizations_id' }),
|
||||
unique('uk_code').on(table.code, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysPermission = mysqlTable("sys_permission", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }),
|
||||
level: tinyint().default(1).notNull(),
|
||||
permissionKey: varchar("permission_key", { length: 100 }).notNull(),
|
||||
name: varchar({ length: 50 }).notNull(),
|
||||
description: varchar({ length: 255 }).default(''),
|
||||
type: tinyint().default(1).notNull(),
|
||||
apiPathKey: varchar("api_path_key", { length: 200 }),
|
||||
pagePathKey: varchar("page_path_key", { length: 200 }),
|
||||
module: varchar({ length: 30 }).notNull(),
|
||||
sort: bigint({ mode: "number" }),
|
||||
icon: varchar({ length: 100 }),
|
||||
status: tinyint().default(1),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: datetime("updated_at", { mode: 'string'}),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_level").on(table.level),
|
||||
index("idx_module").on(table.module),
|
||||
index("idx_parent_sort").on(table.pid, table.sort),
|
||||
index("idx_pid").on(table.pid),
|
||||
index("idx_time").on(table.createdAt),
|
||||
index("idx_type").on(table.type),
|
||||
primaryKey({ columns: [table.id], name: "sys_permission_id"}),
|
||||
unique("uniq_permission_key").on(table.permissionKey),
|
||||
]);
|
||||
export const sysPermissions = mysqlTable(
|
||||
'sys_permissions',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
code: varchar({ length: 100 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
type: varchar({ length: 20 }).notNull(),
|
||||
resource: varchar({ length: 50 }),
|
||||
action: varchar({ length: 50 }),
|
||||
description: text(),
|
||||
pid: bigint({ mode: 'number' }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int('sort_order').default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
meta: json(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
updatedBy: bigint('updated_by', { mode: 'number' }),
|
||||
updatedAt: datetime('updated_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_pid').on(table.pid),
|
||||
index('idx_resource_action').on(table.resource, table.action),
|
||||
index('idx_sort').on(table.pid, table.sortOrder),
|
||||
index('idx_status').on(table.status),
|
||||
index('idx_type').on(table.type),
|
||||
primaryKey({ columns: [table.id], name: 'sys_permissions_id' }),
|
||||
unique('uk_code').on(table.code, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysRolePermissions = mysqlTable("sys_role_permissions", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
permissionId: bigint("permission_id", { mode: "number" }).notNull(),
|
||||
isHalf: tinyint("is_half").default(0).notNull(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_is_half").on(table.isHalf),
|
||||
index("idx_permission_id").on(table.permissionId),
|
||||
index("idx_role_id").on(table.roleId),
|
||||
primaryKey({ columns: [table.id], name: "sys_role_permissions_id"}),
|
||||
unique("uk_role_permission").on(table.roleId, table.permissionId),
|
||||
]);
|
||||
export const sysRolePermissions = mysqlTable(
|
||||
'sys_role_permissions',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
permissionId: bigint('permission_id', { mode: 'number' }).notNull(),
|
||||
isHalf: tinyint('is_half').default(0).notNull(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_is_half').on(table.isHalf),
|
||||
index('idx_permission_id').on(table.permissionId),
|
||||
index('idx_role_id').on(table.roleId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_role_permissions_id' }),
|
||||
unique('uk_role_permission').on(table.roleId, table.permissionId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysRoles = mysqlTable("sys_roles", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
code: varchar({ length: 50 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
description: text(),
|
||||
pid: bigint({ mode: "number" }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int("sort_order").default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
isSystem: tinyint("is_system").default(0).notNull(),
|
||||
permissionsSnapshot: json("permissions_snapshot"),
|
||||
extra: json(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
updatedBy: bigint("updated_by", { mode: "number" }),
|
||||
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
deletedAt: datetime("deleted_at", { mode: 'string'}),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_deleted_at").on(table.deletedAt),
|
||||
index("idx_is_system").on(table.isSystem),
|
||||
index("idx_name").on(table.name),
|
||||
index("idx_path").on(table.path),
|
||||
index("idx_pid").on(table.pid),
|
||||
index("idx_sort").on(table.pid, table.sortOrder),
|
||||
index("idx_status").on(table.status),
|
||||
primaryKey({ columns: [table.id], name: "sys_roles_id"}),
|
||||
unique("uk_code").on(table.code, table.deletedAt),
|
||||
]);
|
||||
export const sysRoles = mysqlTable(
|
||||
'sys_roles',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
code: varchar({ length: 50 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
description: text(),
|
||||
pid: bigint({ mode: 'number' }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int('sort_order').default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
isSystem: tinyint('is_system').default(0).notNull(),
|
||||
permissionsSnapshot: json('permissions_snapshot'),
|
||||
extra: json(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
updatedBy: bigint('updated_by', { mode: 'number' }),
|
||||
updatedAt: datetime('updated_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_is_system').on(table.isSystem),
|
||||
index('idx_name').on(table.name),
|
||||
index('idx_path').on(table.path),
|
||||
index('idx_pid').on(table.pid),
|
||||
index('idx_sort').on(table.pid, table.sortOrder),
|
||||
index('idx_status').on(table.status),
|
||||
primaryKey({ columns: [table.id], name: 'sys_roles_id' }),
|
||||
unique('uk_code').on(table.code, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysTags = mysqlTable("sys_tags", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
name: varchar({ length: 50 }).notNull(),
|
||||
type: varchar({ length: 50 }).default('user'),
|
||||
color: varchar({ length: 50 }),
|
||||
description: text(),
|
||||
usageCount: int("usage_count").default(0).notNull(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
deletedAt: datetime("deleted_at", { mode: 'string'}),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_deleted_at").on(table.deletedAt),
|
||||
index("idx_name").on(table.name),
|
||||
index("idx_type").on(table.type),
|
||||
index("idx_usage_count").on(table.usageCount),
|
||||
primaryKey({ columns: [table.id], name: "sys_tags_id"}),
|
||||
unique("uk_name_type").on(table.name, table.type, table.deletedAt),
|
||||
]);
|
||||
export const sysTags = mysqlTable(
|
||||
'sys_tags',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
name: varchar({ length: 50 }).notNull(),
|
||||
type: varchar({ length: 50 }).default('user'),
|
||||
color: varchar({ length: 50 }),
|
||||
description: text(),
|
||||
usageCount: int('usage_count').default(0).notNull(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_name').on(table.name),
|
||||
index('idx_type').on(table.type),
|
||||
index('idx_usage_count').on(table.usageCount),
|
||||
primaryKey({ columns: [table.id], name: 'sys_tags_id' }),
|
||||
unique('uk_name_type').on(table.name, table.type, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysUserOrganizations = mysqlTable("sys_user_organizations", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
organizationId: bigint("organization_id", { mode: "number" }).notNull(),
|
||||
isPrimary: tinyint("is_primary").default(0).notNull(),
|
||||
position: varchar({ length: 100 }),
|
||||
joinedAt: datetime("joined_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_is_primary").on(table.isPrimary),
|
||||
index("idx_joined_at").on(table.joinedAt),
|
||||
index("idx_organization_id").on(table.organizationId),
|
||||
index("idx_user_id").on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: "sys_user_organizations_id"}),
|
||||
unique("uk_user_org").on(table.userId, table.organizationId),
|
||||
]);
|
||||
export const sysUserOrganizations = mysqlTable(
|
||||
'sys_user_organizations',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
organizationId: bigint('organization_id', { mode: 'number' }).notNull(),
|
||||
isPrimary: tinyint('is_primary').default(0).notNull(),
|
||||
position: varchar({ length: 100 }),
|
||||
joinedAt: datetime('joined_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_is_primary').on(table.isPrimary),
|
||||
index('idx_joined_at').on(table.joinedAt),
|
||||
index('idx_organization_id').on(table.organizationId),
|
||||
index('idx_user_id').on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_user_organizations_id' }),
|
||||
unique('uk_user_org').on(table.userId, table.organizationId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysUserRoles = mysqlTable("sys_user_roles", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
expiredAt: datetime("expired_at", { mode: 'string'}),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_created_at").on(table.createdAt),
|
||||
index("idx_expired_at").on(table.expiredAt),
|
||||
index("idx_role_id").on(table.roleId),
|
||||
index("idx_user_id").on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: "sys_user_roles_id"}),
|
||||
unique("uk_user_role").on(table.userId, table.roleId),
|
||||
]);
|
||||
export const sysUserRoles = mysqlTable(
|
||||
'sys_user_roles',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
expiredAt: datetime('expired_at', { mode: 'string' }),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_created_at').on(table.createdAt),
|
||||
index('idx_expired_at').on(table.expiredAt),
|
||||
index('idx_role_id').on(table.roleId),
|
||||
index('idx_user_id').on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_user_roles_id' }),
|
||||
unique('uk_user_role').on(table.userId, table.roleId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysUserTags = mysqlTable("sys_user_tags", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
tagId: bigint("tag_id", { mode: "number" }).notNull(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_created_at").on(table.createdAt),
|
||||
index("idx_tag_id").on(table.tagId),
|
||||
index("idx_user_id").on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: "sys_user_tags_id"}),
|
||||
unique("uk_user_tag").on(table.userId, table.tagId),
|
||||
]);
|
||||
export const sysUserTags = mysqlTable(
|
||||
'sys_user_tags',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
tagId: bigint('tag_id', { mode: 'number' }).notNull(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_created_at').on(table.createdAt),
|
||||
index('idx_tag_id').on(table.tagId),
|
||||
index('idx_user_id').on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_user_tags_id' }),
|
||||
unique('uk_user_tag').on(table.userId, table.tagId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysUsers = mysqlTable("sys_users", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
username: varchar({ length: 50 }).notNull(),
|
||||
email: varchar({ length: 100 }).notNull(),
|
||||
mobile: varchar({ length: 20 }),
|
||||
passwordHash: varchar("password_hash", { length: 255 }).notNull(),
|
||||
avatar: varchar({ length: 255 }),
|
||||
nickname: varchar({ length: 100 }),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
gender: tinyint().default(0),
|
||||
// you can use { mode: 'date' }, if you want to have Date as type for this column
|
||||
birthday: date({ mode: 'string' }),
|
||||
bio: varchar({ length: 500 }),
|
||||
loginCount: int("login_count").default(0).notNull(),
|
||||
lastLoginAt: datetime("last_login_at", { mode: 'string'}),
|
||||
lastLoginIp: varchar("last_login_ip", { length: 45 }),
|
||||
failedAttempts: int("failed_attempts").default(0).notNull(),
|
||||
lockedUntil: datetime("locked_until", { mode: 'string'}),
|
||||
isRoot: tinyint("is_root").default(0).notNull(),
|
||||
extra: json(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
updatedBy: bigint("updated_by", { mode: "number" }),
|
||||
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
deletedAt: datetime("deleted_at", { mode: 'string'}),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_created_at").on(table.createdAt),
|
||||
index("idx_deleted_at").on(table.deletedAt),
|
||||
index("idx_is_root").on(table.isRoot),
|
||||
index("idx_last_login").on(table.lastLoginAt),
|
||||
index("idx_mobile").on(table.mobile),
|
||||
index("idx_status").on(table.status),
|
||||
primaryKey({ columns: [table.id], name: "sys_users_id"}),
|
||||
unique("uk_email").on(table.email, table.deletedAt),
|
||||
unique("uk_username").on(table.username, table.deletedAt),
|
||||
]);
|
||||
export const sysUsers = mysqlTable(
|
||||
'sys_users',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
username: varchar({ length: 50 }).notNull(),
|
||||
email: varchar({ length: 100 }).notNull(),
|
||||
mobile: varchar({ length: 20 }),
|
||||
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
|
||||
avatar: varchar({ length: 255 }),
|
||||
nickname: varchar({ length: 100 }),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
gender: tinyint().default(0),
|
||||
// you can use { mode: 'date' }, if you want to have Date as type for this column
|
||||
birthday: date({ mode: 'string' }),
|
||||
bio: varchar({ length: 500 }),
|
||||
loginCount: int('login_count').default(0).notNull(),
|
||||
lastLoginAt: datetime('last_login_at', { mode: 'string' }),
|
||||
lastLoginIp: varchar('last_login_ip', { length: 45 }),
|
||||
failedAttempts: int('failed_attempts').default(0).notNull(),
|
||||
lockedUntil: datetime('locked_until', { mode: 'string' }),
|
||||
isRoot: tinyint('is_root').default(0).notNull(),
|
||||
extra: json(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
updatedBy: bigint('updated_by', { mode: 'number' }),
|
||||
updatedAt: datetime('updated_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_created_at').on(table.createdAt),
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_is_root').on(table.isRoot),
|
||||
index('idx_last_login').on(table.lastLoginAt),
|
||||
index('idx_mobile').on(table.mobile),
|
||||
index('idx_status').on(table.status),
|
||||
primaryKey({ columns: [table.id], name: 'sys_users_id' }),
|
||||
unique('uk_email').on(table.email, table.deletedAt),
|
||||
unique('uk_username').on(table.username, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
@ -1,278 +1,357 @@
|
||||
import { mysqlTable, mysqlSchema, AnyMySqlColumn, index, primaryKey, unique, varchar, int, json, timestamp, text, datetime, tinyint, date, } from "drizzle-orm/mysql-core"
|
||||
import { bigintString as bigint } from "./customType"
|
||||
import { sql } from "drizzle-orm"
|
||||
import {
|
||||
mysqlTable,
|
||||
index,
|
||||
primaryKey,
|
||||
unique,
|
||||
varchar,
|
||||
int,
|
||||
json,
|
||||
timestamp,
|
||||
text,
|
||||
datetime,
|
||||
tinyint,
|
||||
date,
|
||||
} from 'drizzle-orm/mysql-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { bigintString as bigint } from './customType';
|
||||
|
||||
export const sysDict = mysqlTable("sys_dict", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
code: varchar({ length: 50 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
value: varchar({ length: 200 }),
|
||||
description: varchar({ length: 500 }),
|
||||
icon: varchar({ length: 100 }),
|
||||
pid: bigint({ mode: "number" }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int("sort_order").default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
isSystem: tinyint("is_system").default(0).notNull(),
|
||||
color: varchar({ length: 20 }),
|
||||
extra: json(),
|
||||
createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().onUpdateNow().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_level").on(table.level),
|
||||
index("idx_pid").on(table.pid),
|
||||
index("idx_sort").on(table.sortOrder),
|
||||
index("idx_status").on(table.status),
|
||||
primaryKey({ columns: [table.id], name: "sys_dict_id"}),
|
||||
unique("uk_code").on(table.code),
|
||||
]);
|
||||
export const sysDict = mysqlTable(
|
||||
'sys_dict',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
code: varchar({ length: 50 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
value: varchar({ length: 200 }),
|
||||
description: varchar({ length: 500 }),
|
||||
icon: varchar({ length: 100 }),
|
||||
pid: bigint({ mode: 'number' }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int('sort_order').default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
isSystem: tinyint('is_system').default(0).notNull(),
|
||||
color: varchar({ length: 20 }),
|
||||
extra: json(),
|
||||
createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().onUpdateNow().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_level').on(table.level),
|
||||
index('idx_pid').on(table.pid),
|
||||
index('idx_sort').on(table.sortOrder),
|
||||
index('idx_status').on(table.status),
|
||||
primaryKey({ columns: [table.id], name: 'sys_dict_id' }),
|
||||
unique('uk_code').on(table.code),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysOperationLogs = mysqlTable("sys_operation_logs", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }),
|
||||
username: varchar({ length: 100 }),
|
||||
module: varchar({ length: 50 }).notNull(),
|
||||
action: varchar({ length: 50 }).notNull(),
|
||||
target: varchar({ length: 200 }),
|
||||
targetId: bigint("target_id", { mode: "number" }),
|
||||
requestData: text("request_data"),
|
||||
responseData: text("response_data"),
|
||||
status: varchar({ length: 20 }).notNull(),
|
||||
ip: varchar({ length: 45 }),
|
||||
userAgent: varchar("user_agent", { length: 200 }),
|
||||
duration: bigint({ mode: "number" }),
|
||||
errorMsg: text("error_msg"),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_created_at").on(table.createdAt),
|
||||
index("idx_ip").on(table.ip),
|
||||
index("idx_module_action").on(table.module, table.action),
|
||||
index("idx_status").on(table.status),
|
||||
index("idx_target").on(table.targetId),
|
||||
index("idx_user_id").on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: "sys_operation_logs_id"}),
|
||||
]);
|
||||
export const sysOperationLogs = mysqlTable(
|
||||
'sys_operation_logs',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }),
|
||||
username: varchar({ length: 100 }),
|
||||
module: varchar({ length: 50 }).notNull(),
|
||||
action: varchar({ length: 50 }).notNull(),
|
||||
target: varchar({ length: 200 }),
|
||||
targetId: bigint('target_id', { mode: 'number' }),
|
||||
requestData: text('request_data'),
|
||||
responseData: text('response_data'),
|
||||
status: varchar({ length: 20 }).notNull(),
|
||||
ip: varchar({ length: 45 }),
|
||||
userAgent: varchar('user_agent', { length: 200 }),
|
||||
duration: bigint({ mode: 'number' }),
|
||||
errorMsg: text('error_msg'),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_created_at').on(table.createdAt),
|
||||
index('idx_ip').on(table.ip),
|
||||
index('idx_module_action').on(table.module, table.action),
|
||||
index('idx_status').on(table.status),
|
||||
index('idx_target').on(table.targetId),
|
||||
index('idx_user_id').on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_operation_logs_id' }),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysOrganizations = mysqlTable("sys_organizations", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
code: varchar({ length: 100 }).notNull(),
|
||||
name: varchar({ length: 200 }).notNull(),
|
||||
fullName: varchar("full_name", { length: 200 }),
|
||||
description: text(),
|
||||
pid: bigint({ mode: "number" }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
type: varchar({ length: 20 }),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
sortOrder: int("sort_order").default(0).notNull(),
|
||||
leaderId: bigint("leader_id", { mode: "number" }),
|
||||
address: varchar({ length: 200 }),
|
||||
phone: varchar({ length: 50 }),
|
||||
extra: json(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
updatedBy: bigint("updated_by", { mode: "number" }),
|
||||
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
deletedAt: datetime("deleted_at", { mode: 'string'}),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_deleted_at").on(table.deletedAt),
|
||||
index("idx_leader_id").on(table.leaderId),
|
||||
index("idx_name").on(table.name),
|
||||
index("idx_path").on(table.path),
|
||||
index("idx_pid").on(table.pid),
|
||||
index("idx_sort").on(table.pid, table.sortOrder),
|
||||
index("idx_status").on(table.status),
|
||||
index("idx_type").on(table.type),
|
||||
primaryKey({ columns: [table.id], name: "sys_organizations_id"}),
|
||||
unique("uk_code").on(table.code, table.deletedAt),
|
||||
]);
|
||||
export const sysOrganizations = mysqlTable(
|
||||
'sys_organizations',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
code: varchar({ length: 100 }).notNull(),
|
||||
name: varchar({ length: 200 }).notNull(),
|
||||
fullName: varchar('full_name', { length: 200 }),
|
||||
description: text(),
|
||||
pid: bigint({ mode: 'number' }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
type: varchar({ length: 20 }),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
sortOrder: int('sort_order').default(0).notNull(),
|
||||
leaderId: bigint('leader_id', { mode: 'number' }),
|
||||
address: varchar({ length: 200 }),
|
||||
phone: varchar({ length: 50 }),
|
||||
extra: json(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
updatedBy: bigint('updated_by', { mode: 'number' }),
|
||||
updatedAt: datetime('updated_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_leader_id').on(table.leaderId),
|
||||
index('idx_name').on(table.name),
|
||||
index('idx_path').on(table.path),
|
||||
index('idx_pid').on(table.pid),
|
||||
index('idx_sort').on(table.pid, table.sortOrder),
|
||||
index('idx_status').on(table.status),
|
||||
index('idx_type').on(table.type),
|
||||
primaryKey({ columns: [table.id], name: 'sys_organizations_id' }),
|
||||
unique('uk_code').on(table.code, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysPermission = mysqlTable("sys_permission", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }),
|
||||
level: tinyint().default(1).notNull(),
|
||||
permissionKey: varchar("permission_key", { length: 100 }).notNull(),
|
||||
name: varchar({ length: 50 }).notNull(),
|
||||
description: varchar({ length: 255 }).default(''),
|
||||
type: tinyint().default(1).notNull(),
|
||||
apiPathKey: varchar("api_path_key", { length: 200 }),
|
||||
pagePathKey: varchar("page_path_key", { length: 200 }),
|
||||
module: varchar({ length: 30 }).notNull(),
|
||||
sort: bigint({ mode: "number" }),
|
||||
icon: varchar({ length: 100 }),
|
||||
status: tinyint().default(1),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: datetime("updated_at", { mode: 'string'}),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_level").on(table.level),
|
||||
index("idx_module").on(table.module),
|
||||
index("idx_parent_sort").on(table.pid, table.sort),
|
||||
index("idx_pid").on(table.pid),
|
||||
index("idx_time").on(table.createdAt),
|
||||
index("idx_type").on(table.type),
|
||||
primaryKey({ columns: [table.id], name: "sys_permission_id"}),
|
||||
unique("uniq_permission_key").on(table.permissionKey),
|
||||
]);
|
||||
export const sysPermissions = mysqlTable(
|
||||
'sys_permissions',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
code: varchar({ length: 100 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
type: varchar({ length: 20 }).notNull(),
|
||||
resource: varchar({ length: 50 }),
|
||||
action: varchar({ length: 50 }),
|
||||
description: text(),
|
||||
pid: bigint({ mode: 'number' }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int('sort_order').default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
meta: json(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
updatedBy: bigint('updated_by', { mode: 'number' }),
|
||||
updatedAt: datetime('updated_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_pid').on(table.pid),
|
||||
index('idx_resource_action').on(table.resource, table.action),
|
||||
index('idx_sort').on(table.pid, table.sortOrder),
|
||||
index('idx_status').on(table.status),
|
||||
index('idx_type').on(table.type),
|
||||
primaryKey({ columns: [table.id], name: 'sys_permissions_id' }),
|
||||
unique('uk_code').on(table.code, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysRolePermissions = mysqlTable("sys_role_permissions", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
permissionId: bigint("permission_id", { mode: "number" }).notNull(),
|
||||
isHalf: tinyint("is_half").default(0).notNull(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_is_half").on(table.isHalf),
|
||||
index("idx_permission_id").on(table.permissionId),
|
||||
index("idx_role_id").on(table.roleId),
|
||||
primaryKey({ columns: [table.id], name: "sys_role_permissions_id"}),
|
||||
unique("uk_role_permission").on(table.roleId, table.permissionId),
|
||||
]);
|
||||
export const sysRolePermissions = mysqlTable(
|
||||
'sys_role_permissions',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
permissionId: bigint('permission_id', { mode: 'number' }).notNull(),
|
||||
isHalf: tinyint('is_half').default(0).notNull(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_is_half').on(table.isHalf),
|
||||
index('idx_permission_id').on(table.permissionId),
|
||||
index('idx_role_id').on(table.roleId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_role_permissions_id' }),
|
||||
unique('uk_role_permission').on(table.roleId, table.permissionId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysRoles = mysqlTable("sys_roles", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
code: varchar({ length: 50 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
description: text(),
|
||||
pid: bigint({ mode: "number" }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int("sort_order").default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
isSystem: tinyint("is_system").default(0).notNull(),
|
||||
permissionsSnapshot: json("permissions_snapshot"),
|
||||
extra: json(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
updatedBy: bigint("updated_by", { mode: "number" }),
|
||||
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
deletedAt: datetime("deleted_at", { mode: 'string'}),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_deleted_at").on(table.deletedAt),
|
||||
index("idx_is_system").on(table.isSystem),
|
||||
index("idx_name").on(table.name),
|
||||
index("idx_path").on(table.path),
|
||||
index("idx_pid").on(table.pid),
|
||||
index("idx_sort").on(table.pid, table.sortOrder),
|
||||
index("idx_status").on(table.status),
|
||||
primaryKey({ columns: [table.id], name: "sys_roles_id"}),
|
||||
unique("uk_code").on(table.code, table.deletedAt),
|
||||
]);
|
||||
export const sysRoles = mysqlTable(
|
||||
'sys_roles',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
code: varchar({ length: 50 }).notNull(),
|
||||
name: varchar({ length: 100 }).notNull(),
|
||||
description: text(),
|
||||
pid: bigint({ mode: 'number' }),
|
||||
path: varchar({ length: 500 }),
|
||||
level: int().default(1).notNull(),
|
||||
sortOrder: int('sort_order').default(0).notNull(),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
isSystem: tinyint('is_system').default(0).notNull(),
|
||||
permissionsSnapshot: json('permissions_snapshot'),
|
||||
extra: json(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
updatedBy: bigint('updated_by', { mode: 'number' }),
|
||||
updatedAt: datetime('updated_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_is_system').on(table.isSystem),
|
||||
index('idx_name').on(table.name),
|
||||
index('idx_path').on(table.path),
|
||||
index('idx_pid').on(table.pid),
|
||||
index('idx_sort').on(table.pid, table.sortOrder),
|
||||
index('idx_status').on(table.status),
|
||||
primaryKey({ columns: [table.id], name: 'sys_roles_id' }),
|
||||
unique('uk_code').on(table.code, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysTags = mysqlTable("sys_tags", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
name: varchar({ length: 50 }).notNull(),
|
||||
type: varchar({ length: 50 }).default('user'),
|
||||
color: varchar({ length: 50 }),
|
||||
description: text(),
|
||||
usageCount: int("usage_count").default(0).notNull(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
deletedAt: datetime("deleted_at", { mode: 'string'}),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_deleted_at").on(table.deletedAt),
|
||||
index("idx_name").on(table.name),
|
||||
index("idx_type").on(table.type),
|
||||
index("idx_usage_count").on(table.usageCount),
|
||||
primaryKey({ columns: [table.id], name: "sys_tags_id"}),
|
||||
unique("uk_name_type").on(table.name, table.type, table.deletedAt),
|
||||
]);
|
||||
export const sysTags = mysqlTable(
|
||||
'sys_tags',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
name: varchar({ length: 50 }).notNull(),
|
||||
type: varchar({ length: 50 }).default('user'),
|
||||
color: varchar({ length: 50 }),
|
||||
description: text(),
|
||||
usageCount: int('usage_count').default(0).notNull(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_name').on(table.name),
|
||||
index('idx_type').on(table.type),
|
||||
index('idx_usage_count').on(table.usageCount),
|
||||
primaryKey({ columns: [table.id], name: 'sys_tags_id' }),
|
||||
unique('uk_name_type').on(table.name, table.type, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysUserOrganizations = mysqlTable("sys_user_organizations", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
organizationId: bigint("organization_id", { mode: "number" }).notNull(),
|
||||
isPrimary: tinyint("is_primary").default(0).notNull(),
|
||||
position: varchar({ length: 100 }),
|
||||
joinedAt: datetime("joined_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_is_primary").on(table.isPrimary),
|
||||
index("idx_joined_at").on(table.joinedAt),
|
||||
index("idx_organization_id").on(table.organizationId),
|
||||
index("idx_user_id").on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: "sys_user_organizations_id"}),
|
||||
unique("uk_user_org").on(table.userId, table.organizationId),
|
||||
]);
|
||||
export const sysUserOrganizations = mysqlTable(
|
||||
'sys_user_organizations',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
organizationId: bigint('organization_id', { mode: 'number' }).notNull(),
|
||||
isPrimary: tinyint('is_primary').default(0).notNull(),
|
||||
position: varchar({ length: 100 }),
|
||||
joinedAt: datetime('joined_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_is_primary').on(table.isPrimary),
|
||||
index('idx_joined_at').on(table.joinedAt),
|
||||
index('idx_organization_id').on(table.organizationId),
|
||||
index('idx_user_id').on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_user_organizations_id' }),
|
||||
unique('uk_user_org').on(table.userId, table.organizationId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysUserRoles = mysqlTable("sys_user_roles", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
expiredAt: datetime("expired_at", { mode: 'string'}),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_created_at").on(table.createdAt),
|
||||
index("idx_expired_at").on(table.expiredAt),
|
||||
index("idx_role_id").on(table.roleId),
|
||||
index("idx_user_id").on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: "sys_user_roles_id"}),
|
||||
unique("uk_user_role").on(table.userId, table.roleId),
|
||||
]);
|
||||
export const sysUserRoles = mysqlTable(
|
||||
'sys_user_roles',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
expiredAt: datetime('expired_at', { mode: 'string' }),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_created_at').on(table.createdAt),
|
||||
index('idx_expired_at').on(table.expiredAt),
|
||||
index('idx_role_id').on(table.roleId),
|
||||
index('idx_user_id').on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_user_roles_id' }),
|
||||
unique('uk_user_role').on(table.userId, table.roleId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysUserTags = mysqlTable("sys_user_tags", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
tagId: bigint("tag_id", { mode: "number" }).notNull(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_created_at").on(table.createdAt),
|
||||
index("idx_tag_id").on(table.tagId),
|
||||
index("idx_user_id").on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: "sys_user_tags_id"}),
|
||||
unique("uk_user_tag").on(table.userId, table.tagId),
|
||||
]);
|
||||
export const sysUserTags = mysqlTable(
|
||||
'sys_user_tags',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
tagId: bigint('tag_id', { mode: 'number' }).notNull(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_created_at').on(table.createdAt),
|
||||
index('idx_tag_id').on(table.tagId),
|
||||
index('idx_user_id').on(table.userId),
|
||||
primaryKey({ columns: [table.id], name: 'sys_user_tags_id' }),
|
||||
unique('uk_user_tag').on(table.userId, table.tagId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysUsers = mysqlTable("sys_users", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
username: varchar({ length: 50 }).notNull(),
|
||||
email: varchar({ length: 100 }).notNull(),
|
||||
mobile: varchar({ length: 20 }),
|
||||
passwordHash: varchar("password_hash", { length: 255 }).notNull(),
|
||||
avatar: varchar({ length: 255 }),
|
||||
nickname: varchar({ length: 100 }),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
gender: tinyint().default(0),
|
||||
// you can use { mode: 'date' }, if you want to have Date as type for this column
|
||||
birthday: date({ mode: 'string' }),
|
||||
bio: varchar({ length: 500 }),
|
||||
loginCount: int("login_count").default(0).notNull(),
|
||||
lastLoginAt: datetime("last_login_at", { mode: 'string'}),
|
||||
lastLoginIp: varchar("last_login_ip", { length: 45 }),
|
||||
failedAttempts: int("failed_attempts").default(0).notNull(),
|
||||
lockedUntil: datetime("locked_until", { mode: 'string'}),
|
||||
isRoot: tinyint("is_root").default(0).notNull(),
|
||||
extra: json(),
|
||||
createdBy: bigint("created_by", { mode: "number" }),
|
||||
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
updatedBy: bigint("updated_by", { mode: "number" }),
|
||||
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
deletedAt: datetime("deleted_at", { mode: 'string'}),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("idx_created_at").on(table.createdAt),
|
||||
index("idx_deleted_at").on(table.deletedAt),
|
||||
index("idx_is_root").on(table.isRoot),
|
||||
index("idx_last_login").on(table.lastLoginAt),
|
||||
index("idx_mobile").on(table.mobile),
|
||||
index("idx_status").on(table.status),
|
||||
primaryKey({ columns: [table.id], name: "sys_users_id"}),
|
||||
unique("uk_email").on(table.email, table.deletedAt),
|
||||
unique("uk_username").on(table.username, table.deletedAt),
|
||||
]);
|
||||
export const sysUsers = mysqlTable(
|
||||
'sys_users',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
username: varchar({ length: 50 }).notNull(),
|
||||
email: varchar({ length: 100 }).notNull(),
|
||||
mobile: varchar({ length: 20 }),
|
||||
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
|
||||
avatar: varchar({ length: 255 }),
|
||||
nickname: varchar({ length: 100 }),
|
||||
status: varchar({ length: 20 }).default('active').notNull(),
|
||||
gender: tinyint().default(0),
|
||||
// you can use { mode: 'date' }, if you want to have Date as type for this column
|
||||
birthday: date({ mode: 'string' }),
|
||||
bio: varchar({ length: 500 }),
|
||||
loginCount: int('login_count').default(0).notNull(),
|
||||
lastLoginAt: datetime('last_login_at', { mode: 'string' }),
|
||||
lastLoginIp: varchar('last_login_ip', { length: 45 }),
|
||||
failedAttempts: int('failed_attempts').default(0).notNull(),
|
||||
lockedUntil: datetime('locked_until', { mode: 'string' }),
|
||||
isRoot: tinyint('is_root').default(0).notNull(),
|
||||
extra: json(),
|
||||
createdBy: bigint('created_by', { mode: 'number' }),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
updatedBy: bigint('updated_by', { mode: 'number' }),
|
||||
updatedAt: datetime('updated_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.notNull(),
|
||||
deletedAt: datetime('deleted_at', { mode: 'string' }),
|
||||
version: int().default(1).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('idx_created_at').on(table.createdAt),
|
||||
index('idx_deleted_at').on(table.deletedAt),
|
||||
index('idx_is_root').on(table.isRoot),
|
||||
index('idx_last_login').on(table.lastLoginAt),
|
||||
index('idx_mobile').on(table.mobile),
|
||||
index('idx_status').on(table.status),
|
||||
primaryKey({ columns: [table.id], name: 'sys_users_id' }),
|
||||
unique('uk_email').on(table.email, table.deletedAt),
|
||||
unique('uk_username').on(table.username, table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
@ -331,14 +331,14 @@ export type UpdateDictSuccessType = Static<(typeof UpdateDictResponsesSchema)[20
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const SortDictResponsesSchema = {
|
||||
200: responseWrapperSchema(t.Null({
|
||||
200: responseWrapperSchema(t.Null(), {
|
||||
description: '排序成功',
|
||||
examples: {
|
||||
code: 0,
|
||||
message: '字典项排序成功',
|
||||
data: null,
|
||||
},
|
||||
})),
|
||||
}),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
@ -373,16 +373,14 @@ export type SortDictSuccessType = Static<(typeof SortDictResponsesSchema)[200]>;
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const DeleteDictResponsesSchema = {
|
||||
200: responseWrapperSchema(t.Null(
|
||||
{
|
||||
description: '删除成功',
|
||||
examples: {
|
||||
code: 0,
|
||||
message: '字典项删除成功',
|
||||
data: null,
|
||||
},
|
||||
}
|
||||
)),
|
||||
200: responseWrapperSchema(t.Null(), {
|
||||
description: '删除成功',
|
||||
examples: {
|
||||
code: 0,
|
||||
message: '字典项删除成功',
|
||||
data: null,
|
||||
},
|
||||
}),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
|
@ -14,7 +14,6 @@ import { testController } from './test/test.controller';
|
||||
import { captchaController } from './captcha/captcha.controller';
|
||||
import { authController } from './auth/auth.controller';
|
||||
import { dictController } from './dict/dict.controller';
|
||||
import { permissionController } from './permission/permission.controller';
|
||||
|
||||
/**
|
||||
* 主路由控制器 - API 路由总入口
|
||||
@ -39,6 +38,4 @@ export const controllers = new Elysia({
|
||||
// 验证码接口
|
||||
.group('/captcha', (app) => app.use(captchaController))
|
||||
// 字典接口
|
||||
.group('/dict', (app) => app.use(dictController))
|
||||
// 权限接口
|
||||
.group('/permission', (app) => app.use(permissionController));
|
||||
.group('/dict', (app) => app.use(dictController));
|
||||
|
@ -1,139 +0,0 @@
|
||||
/**
|
||||
* @file 权限模块Controller
|
||||
* @author AI Assistant
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 权限模块路由控制器,仅实现新增权限接口
|
||||
*/
|
||||
|
||||
import { Elysia } from 'elysia';
|
||||
import { CreatePermissionSchema, GetPermissionTreeByPidParamsSchema } from './permission.schema';
|
||||
import { CreatePermissionResponsesSchema } from './permission.response';
|
||||
import { DeletePermissionParamsSchema } from './permission.schema';
|
||||
import { DeletePermissionResponsesSchema } from './permission.response';
|
||||
import { UpdatePermissionParamsSchema, UpdatePermissionBodySchema } from './permission.schema';
|
||||
import { UpdatePermissionResponsesSchema } from './permission.response';
|
||||
import { permissionService } from './permission.service';
|
||||
import { tags } from '@/constants/swaggerTags';
|
||||
import { GetPermissionTreeQuerySchema, SortPermissionSchema } from './permission.schema';
|
||||
import { GetPermissionTreeResponsesSchema, SortPermissionResponsesSchema } from './permission.response';
|
||||
import { GetPermissionDetailParamsSchema } from './permission.schema';
|
||||
import { GetPermissionDetailResponsesSchema } from './permission.response';
|
||||
|
||||
export const permissionController = new Elysia()
|
||||
/**
|
||||
* 新增权限接口
|
||||
* @route POST /api/permission
|
||||
* @description 创建新的权限项,支持树形结构
|
||||
*/
|
||||
.post(
|
||||
'/',
|
||||
async ({ body }) => permissionService.createPermission(body),
|
||||
{
|
||||
body: CreatePermissionSchema,
|
||||
detail: {
|
||||
summary: '新增权限',
|
||||
description: '创建新的权限项,支持树形结构,需校验唯一性、层级、父子module一致性等',
|
||||
tags: [tags.permission],
|
||||
operationId: 'createPermission',
|
||||
},
|
||||
response: CreatePermissionResponsesSchema,
|
||||
}
|
||||
)
|
||||
/**
|
||||
* 删除权限接口
|
||||
* @route DELETE /api/permission/:id
|
||||
* @description 删除指定权限,需检查子权限和引用关系
|
||||
*/
|
||||
.delete(
|
||||
'/:id',
|
||||
async ({ params }) => permissionService.deletePermission(params.id),
|
||||
{
|
||||
params: DeletePermissionParamsSchema,
|
||||
detail: {
|
||||
summary: '删除权限',
|
||||
description: '删除指定权限,会检查子权限和角色引用关系,采用逻辑删除',
|
||||
tags: [tags.permission],
|
||||
operationId: 'deletePermission',
|
||||
},
|
||||
response: DeletePermissionResponsesSchema,
|
||||
}
|
||||
)
|
||||
/**
|
||||
* 修改权限接口
|
||||
* @route PUT /api/permission/:id
|
||||
* @description 修改指定权限,支持字段变更、同级重名、父子module一致、非法移动、状态可变更等校验
|
||||
*/
|
||||
.put(
|
||||
'/:id',
|
||||
async ({ params, body }) => permissionService.updatePermission(params.id, body),
|
||||
{
|
||||
params: UpdatePermissionParamsSchema,
|
||||
body: UpdatePermissionBodySchema,
|
||||
detail: {
|
||||
summary: '修改权限',
|
||||
description: '修改指定权限,支持字段变更、同级重名、父子module一致、非法移动、状态可变更等校验',
|
||||
tags: [tags.permission],
|
||||
operationId: 'updatePermission',
|
||||
},
|
||||
response: UpdatePermissionResponsesSchema,
|
||||
}
|
||||
)
|
||||
/**
|
||||
* 查看权限完整树接口
|
||||
* @route GET /api/permission/tree
|
||||
* @description 查询权限完整树,支持多条件筛选
|
||||
*/
|
||||
.get(
|
||||
'/tree',
|
||||
async ({ query }) => permissionService.getPermissionTree(query),
|
||||
{
|
||||
query: GetPermissionTreeQuerySchema,
|
||||
detail: {
|
||||
summary: '查看权限完整树',
|
||||
description: '查询权限完整树,支持module、status、type多条件筛选',
|
||||
tags: [tags.permission],
|
||||
operationId: 'getPermissionTree',
|
||||
},
|
||||
response: GetPermissionTreeResponsesSchema,
|
||||
}
|
||||
)
|
||||
/**
|
||||
* 权限排序接口
|
||||
* @route PUT /api/permission/sort
|
||||
* @description 批量更新权限排序,支持同级权限的排序调整
|
||||
*/
|
||||
.put(
|
||||
'/sort',
|
||||
async ({ body }) => permissionService.sortPermissions(body),
|
||||
{
|
||||
body: SortPermissionSchema,
|
||||
detail: {
|
||||
summary: '权限排序',
|
||||
description: '批量更新权限排序,要求所有权限必须在同一级别,排序值不能重复',
|
||||
tags: [tags.permission],
|
||||
operationId: 'sortPermissions',
|
||||
},
|
||||
response: SortPermissionResponsesSchema,
|
||||
}
|
||||
)
|
||||
/**
|
||||
* 获取权限详情接口
|
||||
* @route GET /api/permission/:id
|
||||
* @description 查询指定权限的详细信息
|
||||
*/
|
||||
.get(
|
||||
'/:id',
|
||||
async ({ params }) => permissionService.getPermissionDetail(params.id),
|
||||
{
|
||||
params: GetPermissionDetailParamsSchema,
|
||||
detail: {
|
||||
summary: '获取权限详情',
|
||||
description: '查询指定权限的详细信息,包括权限名称、类型、状态、module、排序等',
|
||||
tags: [tags.permission],
|
||||
operationId: 'getPermissionDetail',
|
||||
},
|
||||
response: GetPermissionDetailResponsesSchema,
|
||||
}
|
||||
);
|
@ -1,800 +0,0 @@
|
||||
# 权限模块业务逻辑文档
|
||||
|
||||
## 模块概述
|
||||
|
||||
权限模块提供基于RBAC(基于角色的访问控制)的权限管理功能,支持树形结构的权限数据CRUD操作。权限数据用于控制用户对系统资源的访问权限,包括菜单、按钮、接口和数据权限。
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### 表结构:sys_permission
|
||||
|
||||
```sql
|
||||
CREATE TABLE sys_permission (
|
||||
id BIGINT PRIMARY KEY COMMENT '主键ID',
|
||||
pid BIGINT DEFAULT 0 COMMENT '父权限ID',
|
||||
level TINYINT NOT NULL DEFAULT 1 COMMENT '权限层级',
|
||||
permission_key VARCHAR(100) NOT NULL COMMENT '权限标识',
|
||||
name VARCHAR(50) NOT NULL COMMENT '权限名称',
|
||||
description VARCHAR(255) DEFAULT '' COMMENT '权限描述',
|
||||
type TINYINT NOT NULL DEFAULT 1 COMMENT '权限类型(1=菜单 2=按钮 3=接口 4=数据)',
|
||||
api_path_key VARCHAR(200) DEFAULT NULL COMMENT '接口路径标识',
|
||||
page_path_key VARCHAR(200) DEFAULT NULL COMMENT '前端路由标识',
|
||||
module VARCHAR(30) NOT NULL COMMENT '所属模块',
|
||||
sort BIGINT DEFAULT 0 COMMENT '排序值',
|
||||
icon VARCHAR(100) DEFAULT NULL COMMENT '图标标识',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态(1=启用, 0禁用)',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
/* 唯一约束 */
|
||||
UNIQUE KEY uniq_permission_key (permission_key),
|
||||
|
||||
/* 单列索引 */
|
||||
KEY idx_pid (pid),
|
||||
KEY idx_level (level),
|
||||
KEY idx_type (type),
|
||||
KEY idx_module (module),
|
||||
KEY idx_time (created_at),
|
||||
|
||||
/* 复合索引 */
|
||||
KEY idx_parent_sort (pid, sort)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='RBAC权限表';
|
||||
```
|
||||
|
||||
## 接口设计
|
||||
|
||||
### 1. 新增权限接口 (POST /api/permission)
|
||||
|
||||
#### 业务逻辑
|
||||
|
||||
1. **参数验证**
|
||||
- 验证必填字段:permission_key、name、type、module
|
||||
- 验证permission_key唯一性(全局唯一)
|
||||
- 验证name在同级下的唯一性
|
||||
- 验证pid的有效性(如果指定了父级)
|
||||
- 验证level深度不超过10层
|
||||
- 验证type的有效性(1=菜单 2=按钮 3=接口 4=数据)
|
||||
- 验证module格式(字母数字下划线,长度1-30)
|
||||
|
||||
2. **业务规则**
|
||||
- 默认pid为0(顶级权限)
|
||||
- 默认level为1(顶级)
|
||||
- 默认status为1(启用)
|
||||
- 默认sort_order为0
|
||||
- 如果有父级,level = 父级level + 1
|
||||
- 如果指定了父级,需要验证父级是否存在且状态为1
|
||||
- 如果存在父权限,module必须与父权限一致
|
||||
- 同级权限允许重名,但permission_key必须唯一
|
||||
|
||||
3. **数据处理**
|
||||
- permission_key转换为小写并去除两端空格
|
||||
- name去除两端空格
|
||||
- description去除两端空格
|
||||
- api_path_key去除两端空格(如果提供)
|
||||
- page_path_key去除两端空格(如果提供)
|
||||
- module转换为小写并去除两端空格
|
||||
- icon去除两端空格(如果提供)
|
||||
- 自动计算level(如果指定了pid)
|
||||
- 自动生成sort_order(同级最大sort_order + 1)
|
||||
|
||||
4. **错误处理**
|
||||
- permission_key已存在:409 Conflict
|
||||
- name在同级下已存在:409 Conflict
|
||||
- 父级不存在:404 Not Found
|
||||
- 父级状态非启用:400 Bad Request
|
||||
- level超过10层:400 Bad Request
|
||||
- type值无效:400 Bad Request
|
||||
- module格式错误:400 Bad Request
|
||||
- 父权限module不一致:400 Bad Request
|
||||
|
||||
#### 性能考虑
|
||||
|
||||
1. **数据库索引**
|
||||
- permission_key字段有唯一索引,查询快速
|
||||
- pid字段有索引,支持父子关系查询
|
||||
- level字段有索引,支持层级查询
|
||||
- module字段有索引,支持模块筛选
|
||||
|
||||
2. **并发控制**
|
||||
- 使用分布式锁防止permission_key重复创建
|
||||
- 使用数据库事务确保数据一致性
|
||||
- 使用乐观锁防止并发更新冲突
|
||||
|
||||
#### 业务流程图
|
||||
|
||||
```
|
||||
开始
|
||||
↓
|
||||
验证必填参数
|
||||
↓
|
||||
检查permission_key唯一性
|
||||
↓
|
||||
验证父权限(如果指定)
|
||||
↓
|
||||
检查module一致性
|
||||
↓
|
||||
计算level和sort_order
|
||||
↓
|
||||
数据预处理
|
||||
↓
|
||||
保存到数据库
|
||||
↓
|
||||
返回成功响应
|
||||
```
|
||||
|
||||
#### 数据验证规则
|
||||
|
||||
1. **permission_key**
|
||||
- 必填
|
||||
- 长度:1-100字符
|
||||
- 格式:字母、数字、下划线
|
||||
- 全局唯一
|
||||
|
||||
2. **name**
|
||||
- 必填
|
||||
- 长度:1-50字符
|
||||
- 同级下唯一
|
||||
|
||||
3. **type**
|
||||
- 必填
|
||||
- 值:1(菜单)、2(按钮)、3(接口)、4(数据)
|
||||
|
||||
4. **module**
|
||||
- 必填
|
||||
- 长度:1-30字符
|
||||
- 格式:字母、数字、下划线
|
||||
- 与父权限一致(如果存在父权限)
|
||||
|
||||
5. **pid**
|
||||
- 可选
|
||||
- 默认:0
|
||||
- 必须指向存在的权限
|
||||
|
||||
6. **api_path_key**
|
||||
- 可选
|
||||
- 长度:0-200字符
|
||||
- type为3(接口)时建议填写
|
||||
|
||||
7. **page_path_key**
|
||||
- 可选
|
||||
- 长度:0-200字符
|
||||
- type为1(菜单)时建议填写
|
||||
|
||||
#### 响应示例
|
||||
|
||||
**成功响应 (200)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "权限创建成功",
|
||||
"data": {
|
||||
"id": "123456789",
|
||||
"permission_key": "user:create",
|
||||
"name": "创建用户",
|
||||
"description": "创建新用户的权限",
|
||||
"type": 3,
|
||||
"module": "user",
|
||||
"pid": "0",
|
||||
"level": 1,
|
||||
"sort_order": 1,
|
||||
"status": 1,
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"updated_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (409)**
|
||||
```json
|
||||
{
|
||||
"code": 409,
|
||||
"message": "权限标识已存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
|
||||
1. **正常创建权限**
|
||||
- 输入:有效的权限数据
|
||||
- 预期:创建成功,返回权限信息
|
||||
|
||||
2. **重复permission_key**
|
||||
- 输入:已存在的permission_key
|
||||
- 预期:返回409错误
|
||||
|
||||
3. **无效父权限**
|
||||
- 输入:不存在的pid
|
||||
- 预期:返回404错误
|
||||
|
||||
4. **模块不一致**
|
||||
- 输入:父权限module与子权限不一致
|
||||
- 预期:返回400错误
|
||||
|
||||
5. **层级超限**
|
||||
- 输入:level超过10层
|
||||
- 预期:返回400错误
|
||||
|
||||
#### 注意事项
|
||||
|
||||
1. **性能优化**
|
||||
- 使用批量操作减少数据库访问
|
||||
- 合理使用缓存减少查询压力
|
||||
- 优化树形结构构建算法
|
||||
|
||||
2. **数据一致性**
|
||||
- 使用事务确保数据完整性
|
||||
- 及时更新相关缓存
|
||||
- 处理并发冲突
|
||||
|
||||
3. **安全性**
|
||||
- 严格验证输入参数
|
||||
- 记录所有操作日志
|
||||
- 防止权限提升攻击
|
||||
|
||||
4. **可维护性**
|
||||
- 代码结构清晰
|
||||
- 错误处理完善
|
||||
- 文档详细完整
|
||||
|
||||
---
|
||||
|
||||
### 2. 删除权限接口 (DELETE /api/permission/:id)
|
||||
|
||||
#### 业务逻辑
|
||||
|
||||
1. **参数验证**
|
||||
- 验证id格式(bigint字符串)
|
||||
- 验证id是否存在
|
||||
|
||||
2. **业务规则**
|
||||
- 只允许删除状态为启用(status=1)的权限
|
||||
- 检查是否有子权限(pid=当前id),有则不允许删除
|
||||
- 检查是否被角色、用户等引用(如有,需提示或禁止删除,具体视业务需求)
|
||||
- 逻辑删除:将status设为0(禁用),不物理删除
|
||||
- 记录删除操作日志
|
||||
|
||||
3. **错误处理**
|
||||
- 权限不存在:404 Not Found
|
||||
- 权限状态非启用:400 Bad Request
|
||||
- 存在子权限:400 Bad Request
|
||||
- 被引用不可删除:400 Bad Request
|
||||
|
||||
#### 性能与安全考虑
|
||||
|
||||
- 子权限、引用检查需加索引优化
|
||||
- 删除操作需加分布式锁防止并发冲突
|
||||
- 需管理员权限
|
||||
- 防止越权删除
|
||||
|
||||
#### 缓存策略
|
||||
|
||||
- 删除后清除相关权限缓存、权限树缓存、模块权限缓存
|
||||
|
||||
#### 响应示例
|
||||
|
||||
**成功响应 (200)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "权限删除成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (400/404)**
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "存在子权限,无法删除",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
|
||||
1. **正常删除**
|
||||
- 输入:无子权限、未被引用的权限id
|
||||
- 预期:删除成功
|
||||
2. **有子权限**
|
||||
- 输入:有子权限的权限id
|
||||
- 预期:返回400错误
|
||||
3. **权限不存在**
|
||||
- 输入:不存在的id
|
||||
- 预期:返回404错误
|
||||
4. **权限已禁用**
|
||||
- 输入:status=0的权限id
|
||||
- 预期:返回400错误
|
||||
5. **被引用**
|
||||
- 输入:被角色/用户引用的权限id
|
||||
- 预期:返回400错误
|
||||
|
||||
---
|
||||
|
||||
### 3. 修改权限接口 (PUT /api/permission/:id)
|
||||
|
||||
#### 业务逻辑
|
||||
|
||||
1. **参数验证**
|
||||
- 验证id格式(bigint字符串)
|
||||
- 验证id是否存在
|
||||
- 校验可修改字段(如name、description、type、apiPathKey、pagePathKey、module、sort、icon、status等)
|
||||
- name同级唯一性校验(如有修改)
|
||||
- type、module等字段格式校验
|
||||
|
||||
2. **业务规则**
|
||||
- 只允许修改状态为启用(status=1)的权限
|
||||
- 存在父权限时,module必须与父权限一致
|
||||
- 同级允许重名,但permissionKey必须唯一
|
||||
- 不允许修改permissionKey(如需支持请说明)
|
||||
- 不允许将权限移动到自身或其子节点下
|
||||
- 修改type、module等需考虑子权限影响
|
||||
- 记录修改操作日志
|
||||
|
||||
3. **错误处理**
|
||||
- 权限不存在:404 Not Found
|
||||
- 权限状态非启用:400 Bad Request
|
||||
- name同级下已存在:409 Conflict
|
||||
- 父权限module不一致:400 Bad Request
|
||||
- 非法移动:400 Bad Request
|
||||
|
||||
#### 性能与安全考虑
|
||||
|
||||
- name、module、type等字段加索引优化
|
||||
- 修改操作需加分布式锁防止并发冲突
|
||||
- 需管理员权限
|
||||
- 防止越权修改
|
||||
|
||||
#### 缓存策略
|
||||
|
||||
- 修改后清除相关权限缓存、权限树缓存、模块权限缓存
|
||||
|
||||
#### 响应示例
|
||||
|
||||
**成功响应 (200)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "权限修改成功",
|
||||
"data": {
|
||||
"id": "123456789",
|
||||
"permission_key": "user:create",
|
||||
"name": "创建用户",
|
||||
"description": "创建新用户的权限",
|
||||
"type": 3,
|
||||
"module": "user",
|
||||
"pid": "0",
|
||||
"level": 1,
|
||||
"sort": 1,
|
||||
"icon": null,
|
||||
"status": 1,
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"updated_at": "2024-01-15T10:40:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (400/404/409)**
|
||||
```json
|
||||
{
|
||||
"code": 409,
|
||||
"message": "同级下已存在该名称",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
|
||||
1. **正常修改**
|
||||
- 输入:有效的权限id和修改字段
|
||||
- 预期:修改成功,返回新数据
|
||||
2. **同级重名**
|
||||
- 输入:同级下已存在的name
|
||||
- 预期:返回409错误
|
||||
3. **权限不存在**
|
||||
- 输入:不存在的id
|
||||
- 预期:返回404错误
|
||||
4. **状态非启用**
|
||||
- 输入:status=0的权限id
|
||||
- 预期:返回400错误
|
||||
5. **父权限module不一致**
|
||||
- 输入:module与父权限不一致
|
||||
- 预期:返回400错误
|
||||
6. **非法移动**
|
||||
- 输入:pid为自身或子节点
|
||||
- 预期:返回400错误
|
||||
|
||||
---
|
||||
|
||||
### 4. 查看权限完整树接口 (GET /api/permission/tree)
|
||||
|
||||
#### 业务逻辑
|
||||
|
||||
1. **参数校验**
|
||||
- 支持可选参数:module(模块)、status(状态)、type(权限类型)
|
||||
- module:字符串,筛选所属模块
|
||||
- status:数字或字符串,1=启用,0=禁用,默认全部
|
||||
- type:数字或字符串,1=菜单,2=按钮,3=接口,4=数据,默认全部
|
||||
|
||||
2. **业务规则**
|
||||
- 查询所有符合条件的权限数据
|
||||
- 按level和sort排序
|
||||
- 构建树形结构(递归或循环)
|
||||
- 只返回未被禁用(或根据status参数)权限
|
||||
- 支持多模块、多类型、多状态筛选
|
||||
|
||||
3. **错误处理**
|
||||
- 参数格式错误:400 Bad Request
|
||||
- 查询异常:500 Internal Server Error
|
||||
|
||||
#### 性能与安全考虑
|
||||
|
||||
- 查询前加索引优化(module、status、type、pid、sort)
|
||||
- 构建树结构时避免N+1查询,建议一次性查全量后内存组装
|
||||
- 支持缓存(如Redis),提升高频查询性能
|
||||
- 需管理员或有权限用户访问
|
||||
|
||||
#### 缓存策略
|
||||
|
||||
- 支持按module、status、type等条件缓存完整树结构
|
||||
- 查询后写入缓存,更新/删除/新增权限时清理相关缓存
|
||||
|
||||
#### 响应示例
|
||||
|
||||
**成功响应 (200)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "查询成功",
|
||||
"data": [
|
||||
{
|
||||
"id": "1",
|
||||
"permission_key": "system",
|
||||
"name": "系统管理",
|
||||
"type": 1,
|
||||
"module": "system",
|
||||
"pid": "0",
|
||||
"level": 1,
|
||||
"sort": 1,
|
||||
"status": 1,
|
||||
"children": [
|
||||
{
|
||||
"id": "2",
|
||||
"permission_key": "user",
|
||||
"name": "用户管理",
|
||||
"type": 1,
|
||||
"module": "system",
|
||||
"pid": "1",
|
||||
"level": 2,
|
||||
"sort": 1,
|
||||
"status": 1,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
|
||||
1. **正常查询**
|
||||
- 输入:无参数
|
||||
- 预期:返回完整权限树
|
||||
2. **按模块筛选**
|
||||
- 输入:module=system
|
||||
- 预期:只返回system模块的权限树
|
||||
3. **按状态筛选**
|
||||
- 输入:status=1
|
||||
- 预期:只返回启用权限
|
||||
4. **按类型筛选**
|
||||
- 输入:type=1
|
||||
- 预期:只返回菜单类型权限
|
||||
5. **参数错误**
|
||||
- 输入:非法status/type/module
|
||||
- 预期:返回400错误
|
||||
|
||||
---
|
||||
|
||||
### 5. 权限排序接口 (PUT /api/permission/sort)
|
||||
|
||||
#### 业务逻辑
|
||||
|
||||
1. **参数验证**
|
||||
- 接收权限ID和排序值的数组,格式:`[{id: string, sort: string}]`
|
||||
- 验证ID格式(bigint字符串)
|
||||
- 验证sort格式(数字字符串)
|
||||
- 验证数组不为空,最多处理100条记录
|
||||
- 验证所有权限ID的唯一性(数组内不能重复)
|
||||
|
||||
2. **业务规则**
|
||||
- 权限ID必须存在且状态为启用
|
||||
- 只能对同级权限进行排序(所有权限必须有相同的pid)
|
||||
- 同级权限的sort值不能重复
|
||||
- 排序值必须为非负整数
|
||||
- 支持批量更新多个权限的排序值
|
||||
- 事务中执行,要么全部成功要么全部失败
|
||||
|
||||
3. **数据处理**
|
||||
- 验证所有权限是否存在且状态正常
|
||||
- 检查所有权限是否在同一级别(相同pid)
|
||||
- 检查sort值的有效性和唯一性
|
||||
- 批量更新权限的sort字段
|
||||
- 记录操作日志
|
||||
|
||||
4. **错误处理**
|
||||
- 权限不存在:404 Not Found
|
||||
- 权限状态非启用:400 Bad Request
|
||||
- 权限不在同一级别:400 Bad Request
|
||||
- sort值重复:409 Conflict
|
||||
- sort值格式错误:400 Bad Request
|
||||
- 数组为空或超过限制:400 Bad Request
|
||||
- 权限ID重复:400 Bad Request
|
||||
|
||||
#### 性能考虑
|
||||
|
||||
1. **数据库优化**
|
||||
- 批量查询权限信息减少数据库访问
|
||||
- 使用事务确保数据一致性
|
||||
- 利用pid+sort复合索引提升查询性能
|
||||
- 批量更新减少数据库交互次数
|
||||
|
||||
2. **并发控制**
|
||||
- 使用分布式锁防止并发排序冲突
|
||||
- 锁定范围:相同pid下的所有权限
|
||||
- 锁定时间:10秒TTL
|
||||
|
||||
#### 安全考虑
|
||||
|
||||
- 需要管理员权限或特定的权限排序权限
|
||||
- 防止越权操作其他模块的权限
|
||||
- 记录操作日志便于审计
|
||||
|
||||
#### 缓存策略
|
||||
|
||||
- 排序后清除权限树缓存
|
||||
- 清除相关模块的权限缓存
|
||||
- 更新权限变更时间戳
|
||||
|
||||
#### 业务流程
|
||||
|
||||
```
|
||||
开始
|
||||
↓
|
||||
验证参数格式和数量
|
||||
↓
|
||||
获取分布式锁
|
||||
↓
|
||||
查询所有权限是否存在
|
||||
↓
|
||||
验证权限状态和级别
|
||||
↓
|
||||
检查sort值唯一性
|
||||
↓
|
||||
开启数据库事务
|
||||
↓
|
||||
批量更新sort值
|
||||
↓
|
||||
提交事务
|
||||
↓
|
||||
释放分布式锁
|
||||
↓
|
||||
清除缓存
|
||||
↓
|
||||
返回成功响应
|
||||
```
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": [
|
||||
{"id": "123", "sort": "1"},
|
||||
{"id": "124", "sort": "2"},
|
||||
{"id": "125", "sort": "3"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
**成功响应 (200)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "权限排序更新成功",
|
||||
"data": {
|
||||
"updated_count": 3,
|
||||
"updated_permissions": [
|
||||
{
|
||||
"id": "123",
|
||||
"name": "用户管理",
|
||||
"sort": "1"
|
||||
},
|
||||
{
|
||||
"id": "124",
|
||||
"name": "角色管理",
|
||||
"sort": "2"
|
||||
},
|
||||
{
|
||||
"id": "125",
|
||||
"name": "权限管理",
|
||||
"sort": "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (400)**
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "权限不在同一级别",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (409)**
|
||||
```json
|
||||
{
|
||||
"code": 409,
|
||||
"message": "排序值重复",
|
||||
"data": {
|
||||
"duplicate_sort": "2",
|
||||
"duplicate_ids": ["124", "125"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
|
||||
1. **正常排序**
|
||||
- 输入:同级权限的有效排序数据
|
||||
- 预期:排序更新成功
|
||||
|
||||
2. **权限不存在**
|
||||
- 输入:包含不存在权限ID的数据
|
||||
- 预期:返回404错误
|
||||
|
||||
3. **权限状态非启用**
|
||||
- 输入:包含禁用权限的数据
|
||||
- 预期:返回400错误
|
||||
|
||||
4. **权限不在同一级别**
|
||||
- 输入:不同pid的权限混合
|
||||
- 预期:返回400错误
|
||||
|
||||
5. **sort值重复**
|
||||
- 输入:相同sort值的权限
|
||||
- 预期:返回409错误
|
||||
|
||||
6. **数组为空**
|
||||
- 输入:空数组
|
||||
- 预期:返回400错误
|
||||
|
||||
7. **超过限制**
|
||||
- 输入:超过100条的数据
|
||||
- 预期:返回400错误
|
||||
|
||||
8. **权限ID重复**
|
||||
- 输入:重复的权限ID
|
||||
- 预期:返回400错误
|
||||
|
||||
#### 注意事项
|
||||
|
||||
1. **排序逻辑**
|
||||
- 前端通常通过拖拽操作触发排序
|
||||
- 需要考虑拖拽后相邻权限的sort值调整
|
||||
- 建议前端计算好新的排序值再提交
|
||||
|
||||
2. **性能优化**
|
||||
- 避免频繁的排序操作
|
||||
- 考虑防抖处理,用户操作完成后统一提交
|
||||
- 大批量权限排序时考虑分页处理
|
||||
|
||||
3. **用户体验**
|
||||
- 排序操作应该有loading状态
|
||||
- 失败时回滚前端显示状态
|
||||
- 成功后实时更新权限树显示
|
||||
|
||||
---
|
||||
|
||||
### 7. 获取权限详情接口 (GET /api/permission/:id)
|
||||
|
||||
#### 业务逻辑
|
||||
|
||||
1. **接口用途**
|
||||
- 根据权限ID获取单个权限的详细信息。
|
||||
- 用于权限管理页面的详情查看、编辑前数据回显等场景。
|
||||
|
||||
2. **参数验证**
|
||||
- 验证id格式(bigint字符串,前端需以字符串传递,防止精度丢失)。
|
||||
- 验证id是否存在。
|
||||
|
||||
3. **业务规则**
|
||||
- 仅查询单条权限数据,不包含子权限。
|
||||
- 返回所有主要字段(见响应结构)。
|
||||
- 若权限不存在,返回404。
|
||||
- 若权限被禁用(status=0),仍可查询详情。
|
||||
|
||||
4. **数据处理**
|
||||
- 查询sys_permission表,按id精确查找。
|
||||
- 字段类型转换:bigint字段以字符串返回。
|
||||
- 返回完整的权限信息,包括:id、pid、level、permission_key、name、description、type、api_path_key、page_path_key、module、sort、icon、status、created_at、updated_at。
|
||||
|
||||
5. **错误处理**
|
||||
- 权限不存在:404 Not Found
|
||||
- id格式错误:400 Bad Request
|
||||
|
||||
6. **安全性**
|
||||
- 需具备权限管理相关权限方可访问。
|
||||
- 记录操作日志。
|
||||
|
||||
7. **性能考虑**
|
||||
- id字段为主键,查询效率高。
|
||||
- 单条数据查询,无性能瓶颈。
|
||||
|
||||
#### 响应结构
|
||||
|
||||
**成功响应 (200)**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取权限详情成功",
|
||||
"data": {
|
||||
"id": "123456789",
|
||||
"pid": "0",
|
||||
"level": 1,
|
||||
"permission_key": "user:create",
|
||||
"name": "创建用户",
|
||||
"description": "创建新用户的权限",
|
||||
"type": 3,
|
||||
"api_path_key": "/api/user/create",
|
||||
"page_path_key": null,
|
||||
"module": "user",
|
||||
"sort": 1,
|
||||
"icon": null,
|
||||
"status": 1,
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"updated_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (404)**
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "权限不存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (400)**
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "参数格式错误",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
|
||||
1. **正常获取权限详情**
|
||||
- 输入:存在的权限ID
|
||||
- 预期:返回对应权限的详细信息
|
||||
2. **权限不存在**
|
||||
- 输入:不存在的权限ID
|
||||
- 预期:返回404错误
|
||||
3. **ID格式错误**
|
||||
- 输入:非bigint格式的ID
|
||||
- 预期:返回400错误
|
||||
|
||||
#### 注意事项
|
||||
|
||||
- 前端传递ID需为字符串,防止大数精度丢失。
|
||||
- 查询结果不包含子权限,仅为单条详情。
|
||||
- 可用于详情页、编辑页的数据回显。
|
||||
- 需做好权限校验,防止越权访问。
|
@ -1,366 +0,0 @@
|
||||
/**
|
||||
* @file 权限模块响应格式定义
|
||||
* @author AI Assistant
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 定义权限模块的响应格式,包括新增权限等
|
||||
*/
|
||||
|
||||
import { t, type Static } from 'elysia';
|
||||
import { responseWrapperSchema } from '@/utils/responseFormate';
|
||||
|
||||
/**
|
||||
* 新增权限成功响应数据结构
|
||||
*/
|
||||
export const CreatePermissionSuccessSchema = t.Object({
|
||||
id: t.String({
|
||||
description: '权限ID(bigint类型以字符串返回防止精度丢失)',
|
||||
examples: ['1', '2', '100'],
|
||||
}),
|
||||
permissionKey: t.String({
|
||||
description: '权限标识',
|
||||
examples: ['user:create', 'user:read', 'user:update'],
|
||||
}),
|
||||
name: t.String({
|
||||
description: '权限名称',
|
||||
examples: ['创建用户', '查看用户', '修改用户'],
|
||||
}),
|
||||
description: t.Optional(
|
||||
t.String({
|
||||
description: '权限描述',
|
||||
examples: ['创建新用户的权限', '查看用户信息的权限'],
|
||||
}),
|
||||
),
|
||||
type: t.Number({
|
||||
description: '权限类型:1=菜单,2=按钮,3=接口,4=数据',
|
||||
examples: [1, 2, 3, 4],
|
||||
}),
|
||||
apiPathKey: t.Optional(
|
||||
t.String({
|
||||
description: '接口路径标识',
|
||||
examples: ['/api/user', '/api/user/create', 'POST:/api/user'],
|
||||
}),
|
||||
),
|
||||
pagePathKey: t.Optional(
|
||||
t.String({
|
||||
description: '前端路由标识',
|
||||
examples: ['/user', '/user/list', '/user/detail'],
|
||||
}),
|
||||
),
|
||||
module: t.String({
|
||||
description: '所属模块',
|
||||
examples: ['user', 'role', 'permission', 'system'],
|
||||
}),
|
||||
pid: t.String({
|
||||
description: '父权限ID(bigint类型以字符串返回)',
|
||||
examples: ['0', '1', '2'],
|
||||
}),
|
||||
level: t.Number({
|
||||
description: '权限层级',
|
||||
examples: [1, 2, 3],
|
||||
}),
|
||||
sort: t.String({
|
||||
description: '排序值,同级内排序使用',
|
||||
examples: ['0','1', '10', '100'],
|
||||
}),
|
||||
icon: t.Optional(
|
||||
t.String({
|
||||
description: '图标标识',
|
||||
examples: ['icon-user', 'icon-add', '/icons/user.png'],
|
||||
}),
|
||||
),
|
||||
status: t.Number({
|
||||
description: '权限状态:1=启用,0=禁用',
|
||||
examples: [1, 0],
|
||||
}),
|
||||
createdAt: t.String({
|
||||
description: '创建时间',
|
||||
examples: ['2024-12-19T10:30:00Z'],
|
||||
}),
|
||||
updatedAt: t.String({
|
||||
description: '更新时间',
|
||||
examples: ['2024-12-19T10:30:00Z'],
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* 新增权限接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const CreatePermissionResponsesSchema = {
|
||||
200: responseWrapperSchema(CreatePermissionSuccessSchema),
|
||||
409: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '唯一性冲突',
|
||||
examples: ['权限标识已存在', '权限名称在同级下已存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '参数错误',
|
||||
examples: ['参数校验失败', '父级权限状态非启用', '权限层级超过限制', '权限类型无效', '模块格式错误', '父权限模块不一致'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['父级权限不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
403: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '权限不足',
|
||||
examples: ['权限不足', '需要管理员权限'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
500: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '服务器错误',
|
||||
examples: ['内部服务器错误'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 新增权限成功响应数据类型 */
|
||||
export type CreatePermissionSuccessType = Static<(typeof CreatePermissionResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 删除权限接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const DeletePermissionResponsesSchema = {
|
||||
200: responseWrapperSchema(t.Object({})),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '请求错误',
|
||||
examples: ['存在子权限,无法删除', '权限状态非启用', '被引用不可删除'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['权限不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 删除权限成功响应类型 */
|
||||
export type DeletePermissionSuccessType = Static<(typeof DeletePermissionResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 修改权限接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const UpdatePermissionResponsesSchema = {
|
||||
200: responseWrapperSchema(CreatePermissionSuccessSchema),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '请求错误',
|
||||
examples: ['权限状态非启用', '父权限module不一致', '非法移动'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['权限不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
409: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '唯一性冲突',
|
||||
examples: ['同级下已存在该名称'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 修改权限成功响应类型 */
|
||||
export type UpdatePermissionSuccessType = Static<(typeof UpdatePermissionResponsesSchema)[200]>;
|
||||
|
||||
/**
|
||||
* 权限树节点Schema
|
||||
* @description 递归结构,children为子权限数组
|
||||
*/
|
||||
export const PermissionTreeItemSchema = t.Recursive((self) =>
|
||||
t.Object({
|
||||
id: t.String({ description: '权限ID(bigint字符串)', examples: ['1', '2', '100'] }),
|
||||
permissionKey: t.String({ description: '权限标识', examples: ['user:create'] }),
|
||||
name: t.String({ description: '权限名称', examples: ['创建用户'] }),
|
||||
description: t.Optional(t.String({ description: '权限描述', examples: ['创建新用户的权限'] })),
|
||||
type: t.Number({ description: '权限类型:1=菜单,2=按钮,3=接口,4=数据', examples: [1, 2, 3, 4] }),
|
||||
apiPathKey: t.Optional(t.String({ description: '接口路径标识', examples: ['/api/user'] })),
|
||||
pagePathKey: t.Optional(t.String({ description: '前端路由标识', examples: ['/user'] })),
|
||||
module: t.String({ description: '所属模块', examples: ['user'] }),
|
||||
pid: t.String({ description: '父权限ID(bigint字符串)', examples: ['0', '1'] }),
|
||||
level: t.Number({ description: '权限层级', examples: [1, 2, 3] }),
|
||||
sort: t.String({ description: '排序值', examples: ['0', '1', '10'] }),
|
||||
icon: t.Optional(t.String({ description: '图标标识', examples: ['icon-user'] })),
|
||||
status: t.Number({ description: '权限状态:1=启用,0=禁用', examples: [1, 0] }),
|
||||
createdAt: t.String({ description: '创建时间', examples: ['2024-12-19T10:30:00Z'] }),
|
||||
updatedAt: t.String({ description: '更新时间', examples: ['2024-12-19T10:30:00Z'] }),
|
||||
children: t.Optional(t.Array(self)),
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 查看权限完整树接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const GetPermissionTreeResponsesSchema = {
|
||||
200: responseWrapperSchema(t.Array(PermissionTreeItemSchema)),
|
||||
400: responseWrapperSchema(t.Object({ error: t.String({ description: '请求错误', examples: ['参数错误'] }) })),
|
||||
500: responseWrapperSchema(t.Object({ error: t.String({ description: '服务器错误', examples: ['内部服务器错误'] }) })),
|
||||
};
|
||||
|
||||
/** 查看权限完整树成功响应类型 */
|
||||
export type GetPermissionTreeSuccessType = Static<typeof GetPermissionTreeResponsesSchema[200]>;
|
||||
|
||||
/**
|
||||
* 权限排序成功响应数据中的权限项Schema
|
||||
* @description 排序成功后返回的权限信息
|
||||
*/
|
||||
export const SortedPermissionItemSchema = t.Object({
|
||||
/** 权限ID */
|
||||
id: t.String({
|
||||
description: '权限ID(bigint字符串)',
|
||||
examples: ['123', '124', '125'],
|
||||
}),
|
||||
/** 权限名称 */
|
||||
name: t.String({
|
||||
description: '权限名称',
|
||||
examples: ['用户管理', '角色管理', '权限管理'],
|
||||
}),
|
||||
/** 排序值 */
|
||||
sort: t.String({
|
||||
description: '更新后的排序值',
|
||||
examples: ['1', '2', '3'],
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* 权限排序成功响应数据Schema
|
||||
* @description 权限排序操作成功后的响应数据结构
|
||||
*/
|
||||
export const SortPermissionSuccessSchema = t.Object({
|
||||
/** 更新的权限数量 */
|
||||
updated_count: t.Number({
|
||||
description: '成功更新的权限数量',
|
||||
examples: [3, 5, 10],
|
||||
}),
|
||||
/** 更新的权限列表 */
|
||||
updated_permissions: t.Array(SortedPermissionItemSchema, {
|
||||
description: '成功更新的权限列表',
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* 权限排序接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const SortPermissionResponsesSchema = {
|
||||
200: responseWrapperSchema(SortPermissionSuccessSchema),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '请求错误',
|
||||
examples: [
|
||||
'权限不在同一级别',
|
||||
'sort值格式错误',
|
||||
'数组为空或超过限制',
|
||||
'权限ID重复',
|
||||
'权限状态非启用'
|
||||
],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '权限不存在',
|
||||
examples: ['权限不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
409: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '排序值冲突',
|
||||
examples: ['排序值重复'],
|
||||
}),
|
||||
duplicate_sort: t.Optional(t.String({
|
||||
description: '重复的排序值',
|
||||
examples: ['2'],
|
||||
})),
|
||||
duplicate_ids: t.Optional(t.Array(t.String(), {
|
||||
description: '使用重复排序值的权限ID列表',
|
||||
examples: [['124', '125']],
|
||||
})),
|
||||
}),
|
||||
),
|
||||
403: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '权限不足',
|
||||
examples: ['权限不足', '需要管理员权限'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
500: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '服务器错误',
|
||||
examples: ['内部服务器错误', '数据库操作失败'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 权限排序成功响应数据类型 */
|
||||
export type SortPermissionSuccessType = Static<typeof SortPermissionResponsesSchema[200]>;
|
||||
|
||||
/**
|
||||
* 获取权限详情接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const GetPermissionDetailResponsesSchema = {
|
||||
200: responseWrapperSchema(CreatePermissionSuccessSchema),
|
||||
404: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '资源不存在',
|
||||
examples: ['权限不存在'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
400: responseWrapperSchema(
|
||||
t.Object({
|
||||
error: t.String({
|
||||
description: '参数错误',
|
||||
examples: ['参数格式错误'],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
/** 获取权限详情成功响应数据类型 */
|
||||
export type GetPermissionDetailSuccessType = Static<typeof GetPermissionDetailResponsesSchema[200]>;
|
@ -1,403 +0,0 @@
|
||||
/**
|
||||
* @file 权限模块Schema定义
|
||||
* @author AI Assistant
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 定义权限模块的Schema,包括新增权限、删除权限、修改权限、查看权限树等
|
||||
*/
|
||||
|
||||
import { t, type Static } from 'elysia';
|
||||
|
||||
/**
|
||||
* 新增权限请求参数Schema
|
||||
* @description 新增权限的请求参数验证规则
|
||||
*/
|
||||
export const CreatePermissionSchema = t.Object({
|
||||
/** 权限标识,全局唯一 */
|
||||
permissionKey: t
|
||||
.Transform(
|
||||
t.String({
|
||||
minLength: 1,
|
||||
maxLength: 100,
|
||||
pattern: '^[a-zA-Z0-9_]+$',
|
||||
description: '权限标识,全局唯一,只允许字母数字下划线,自动转换为小写并去除两端空格',
|
||||
examples: ['user:create', 'user:read', 'user:update', 'user:delete'],
|
||||
}),
|
||||
)
|
||||
.Decode((value: string) => value.trim().toLowerCase())
|
||||
.Encode((value: string) => value),
|
||||
/** 权限名称 */
|
||||
name: t
|
||||
.Transform(
|
||||
t.String({
|
||||
minLength: 1,
|
||||
maxLength: 50,
|
||||
description: '权限名称,同级下唯一,自动去除两端空格',
|
||||
examples: ['创建用户', '查看用户', '修改用户', '删除用户'],
|
||||
}),
|
||||
)
|
||||
.Decode((value: string) => value.trim())
|
||||
.Encode((value: string) => value),
|
||||
/** 权限描述 */
|
||||
description: t.Optional(
|
||||
t.Transform(
|
||||
t.String({
|
||||
maxLength: 255,
|
||||
description: '权限描述,自动去除两端空格',
|
||||
examples: ['创建新用户的权限', '查看用户信息的权限'],
|
||||
}),
|
||||
)
|
||||
.Decode((value: string) => value.trim())
|
||||
.Encode((value: string) => value),
|
||||
),
|
||||
/** 权限类型(1=菜单 2=按钮 3=接口 4=数据) */
|
||||
type: t.Union([
|
||||
t.Literal(1),
|
||||
t.Literal(2),
|
||||
t.Literal(3),
|
||||
t.Literal(4),
|
||||
t.Literal('1'),
|
||||
t.Literal('2'),
|
||||
t.Literal('3'),
|
||||
t.Literal('4'),
|
||||
], {
|
||||
description: '权限类型:1=菜单,2=按钮,3=接口,4=数据',
|
||||
examples: [1, 2, 3, 4],
|
||||
}),
|
||||
/** 接口路径标识 */
|
||||
apiPathKey: t.Optional(
|
||||
t.Transform(
|
||||
t.String({
|
||||
maxLength: 200,
|
||||
description: '接口路径标识,type为3(接口)时建议填写,自动去除两端空格',
|
||||
examples: ['/api/user', '/api/user/create', 'POST:/api/user'],
|
||||
}),
|
||||
)
|
||||
.Decode((value: string) => value.trim())
|
||||
.Encode((value: string) => value),
|
||||
),
|
||||
/** 前端路由标识 */
|
||||
pagePathKey: t.Optional(
|
||||
t.Transform(
|
||||
t.String({
|
||||
maxLength: 200,
|
||||
description: '前端路由标识,type为1(菜单)时建议填写,自动去除两端空格',
|
||||
examples: ['/user', '/user/list', '/user/detail'],
|
||||
}),
|
||||
)
|
||||
.Decode((value: string) => value.trim())
|
||||
.Encode((value: string) => value),
|
||||
),
|
||||
/** 所属模块 */
|
||||
module: t
|
||||
.Transform(
|
||||
t.String({
|
||||
minLength: 1,
|
||||
maxLength: 30,
|
||||
pattern: '^[a-zA-Z0-9_]+$',
|
||||
description: '所属模块,只允许字母数字下划线,自动转换为小写并去除两端空格',
|
||||
examples: ['user', 'role', 'permission', 'system'],
|
||||
}),
|
||||
)
|
||||
.Decode((value: string) => value.trim().toLowerCase())
|
||||
.Encode((value: string) => value),
|
||||
/** 父权限ID,0表示顶级 */
|
||||
pid: t.Optional(
|
||||
t.Union([
|
||||
t.Literal('0'),
|
||||
t.String({
|
||||
pattern: '^[1-9]\\d*$',
|
||||
description: '父权限ID,Bigint字符串形式',
|
||||
}),
|
||||
], {
|
||||
description: '父权限ID,0表示顶级权限',
|
||||
examples: ['0', '1', '2'],
|
||||
default: '0',
|
||||
}),
|
||||
),
|
||||
/** 排序值 */
|
||||
sort: t.Optional(
|
||||
t.String({
|
||||
maxLength: 20,
|
||||
description: '排序值,同级内排序使用',
|
||||
examples: ['0','1', '10', '100'],
|
||||
})
|
||||
),
|
||||
/** 图标标识 */
|
||||
icon: t.Optional(
|
||||
t.Transform(
|
||||
t.String({
|
||||
maxLength: 100,
|
||||
description: '图标标识,CSS类名或图标路径,自动去除两端空格',
|
||||
examples: ['icon-user', 'icon-add', '/icons/user.png'],
|
||||
}),
|
||||
)
|
||||
.Decode((value: string) => value.trim())
|
||||
.Encode((value: string) => value),
|
||||
),
|
||||
/** 状态(1=启用, 0=禁用) */
|
||||
status: t.Optional(
|
||||
t.Union([
|
||||
t.Literal(1),
|
||||
t.Literal(0),
|
||||
t.Literal('1'),
|
||||
t.Literal('0'),
|
||||
], {
|
||||
description: '权限状态:1=启用,0=禁用',
|
||||
examples: [1, 0],
|
||||
default: 1,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
/** 新增权限请求参数类型 */
|
||||
export type CreatePermissionRequest = Static<typeof CreatePermissionSchema>;
|
||||
|
||||
/**
|
||||
* 删除权限接口参数Schema
|
||||
* @description 校验DELETE /api/permission/:id 路径参数
|
||||
*/
|
||||
export const DeletePermissionParamsSchema = t.Object({
|
||||
id: t.String({
|
||||
pattern: '^[1-9]\\d*$',
|
||||
description: '权限ID,bigint字符串,必须为正整数',
|
||||
examples: ['1', '100', '123456789'],
|
||||
}),
|
||||
});
|
||||
|
||||
/** 删除权限参数类型 */
|
||||
export type DeletePermissionParams = Static<typeof DeletePermissionParamsSchema>;
|
||||
|
||||
/**
|
||||
* 获取权限详情接口参数Schema
|
||||
* @description 校验GET /api/permission/:id 路径参数
|
||||
*/
|
||||
export const GetPermissionDetailParamsSchema = t.Object({
|
||||
id: t.String({
|
||||
pattern: '^[1-9]\\d*$',
|
||||
description: '权限ID,bigint字符串,必须为正整数',
|
||||
examples: ['1', '100', '123456789'],
|
||||
}),
|
||||
});
|
||||
|
||||
/** 获取权限详情参数类型 */
|
||||
export type GetPermissionDetailParams = Static<typeof GetPermissionDetailParamsSchema>;
|
||||
|
||||
/**
|
||||
* 修改权限接口参数Schema(路径参数)
|
||||
*/
|
||||
export const UpdatePermissionParamsSchema = t.Object({
|
||||
id: t.String({
|
||||
pattern: '^[1-9]\\d*$',
|
||||
description: '权限ID,bigint字符串,必须为正整数',
|
||||
examples: ['1', '100', '123456789'],
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* 修改权限接口请求体Schema
|
||||
*/
|
||||
export const UpdatePermissionBodySchema = t.Object({
|
||||
name: t.Optional(
|
||||
t.String({
|
||||
minLength: 1,
|
||||
maxLength: 50,
|
||||
description: '权限名称,同级下唯一',
|
||||
examples: ['创建用户', '查看用户'],
|
||||
})
|
||||
),
|
||||
description: t.Optional(
|
||||
t.String({
|
||||
maxLength: 255,
|
||||
description: '权限描述',
|
||||
examples: ['创建新用户的权限'],
|
||||
})
|
||||
),
|
||||
type: t.Optional(
|
||||
t.Union([
|
||||
t.Literal(1), t.Literal(2), t.Literal(3), t.Literal(4),
|
||||
t.Literal('1'), t.Literal('2'), t.Literal('3'), t.Literal('4'),
|
||||
], {
|
||||
description: '权限类型:1=菜单,2=按钮,3=接口,4=数据',
|
||||
examples: [1, 2, 3, 4],
|
||||
})
|
||||
),
|
||||
apiPathKey: t.Optional(
|
||||
t.String({
|
||||
maxLength: 200,
|
||||
description: '接口路径标识',
|
||||
examples: ['/api/user'],
|
||||
})
|
||||
),
|
||||
pagePathKey: t.Optional(
|
||||
t.String({
|
||||
maxLength: 200,
|
||||
description: '前端路由标识',
|
||||
examples: ['/user'],
|
||||
})
|
||||
),
|
||||
module: t.Optional(
|
||||
t.String({
|
||||
minLength: 1,
|
||||
maxLength: 30,
|
||||
pattern: '^[a-zA-Z0-9_]+$',
|
||||
description: '所属模块,只允许字母数字下划线',
|
||||
examples: ['user', 'role'],
|
||||
})
|
||||
),
|
||||
pid: t.Optional(
|
||||
t.Union([
|
||||
t.Literal('0'),
|
||||
t.String({
|
||||
pattern: '^[1-9]\\d*$',
|
||||
description: '父权限ID,Bigint字符串',
|
||||
}),
|
||||
], {
|
||||
description: '父权限ID,0表示顶级权限',
|
||||
examples: ['0', '1', '2'],
|
||||
})
|
||||
),
|
||||
sort: t.Optional(
|
||||
t.Number({
|
||||
minimum: 0,
|
||||
maximum: 999999,
|
||||
description: '排序值',
|
||||
examples: [0, 1, 10],
|
||||
})
|
||||
),
|
||||
icon: t.Optional(
|
||||
t.String({
|
||||
maxLength: 100,
|
||||
description: '图标标识',
|
||||
examples: ['icon-user'],
|
||||
})
|
||||
),
|
||||
status: t.Optional(
|
||||
t.Union([
|
||||
t.Literal(1), t.Literal(0), t.Literal('1'), t.Literal('0')
|
||||
], {
|
||||
description: '权限状态:1=启用,0=禁用',
|
||||
examples: [1, 0],
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type UpdatePermissionParams = Static<typeof UpdatePermissionParamsSchema>;
|
||||
export type UpdatePermissionBody = Static<typeof UpdatePermissionBodySchema>;
|
||||
|
||||
/**
|
||||
* 权限类型枚举
|
||||
*/
|
||||
export const PermissionType = {
|
||||
MENU: 1, // 菜单
|
||||
BUTTON: 2, // 按钮
|
||||
API: 3, // 接口
|
||||
DATA: 4, // 数据
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 权限状态枚举
|
||||
*/
|
||||
export const PermissionStatus = {
|
||||
ENABLED: 1, // 启用
|
||||
DISABLED: 0, // 禁用
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 查看权限完整树接口查询参数Schema
|
||||
*/
|
||||
export const GetPermissionTreeQuerySchema = t.Object({
|
||||
module: t.Optional(
|
||||
t.String({
|
||||
minLength: 1,
|
||||
maxLength: 30,
|
||||
description: '所属模块,筛选权限所属模块',
|
||||
examples: ['system', 'user'],
|
||||
})
|
||||
),
|
||||
status: t.Optional(
|
||||
t.Union([
|
||||
t.Literal(1), t.Literal(0), t.Literal('1'), t.Literal('0'), t.Literal('all')
|
||||
], {
|
||||
description: '权限状态,1=启用,0=禁用,all=全部',
|
||||
examples: [1, 0, 'all'],
|
||||
})
|
||||
),
|
||||
type: t.Optional(
|
||||
t.Union([
|
||||
t.Literal(1), t.Literal(2), t.Literal(3), t.Literal(4),
|
||||
t.Literal('1'), t.Literal('2'), t.Literal('3'), t.Literal('4'), t.Literal('all')
|
||||
], {
|
||||
description: '权限类型,1=菜单,2=按钮,3=接口,4=数据,all=全部',
|
||||
examples: [1, 2, 3, 4, 'all'],
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type GetPermissionTreeQuery = Static<typeof GetPermissionTreeQuerySchema>;
|
||||
|
||||
/**
|
||||
* 查看指定父权限下的子权限树接口路径参数Schema
|
||||
* @description 校验GET /api/permission/tree/:pid 路径参数
|
||||
*/
|
||||
export const GetPermissionTreeByPidParamsSchema = t.Object({
|
||||
pid: t.Union([
|
||||
t.Literal('0'),
|
||||
t.String({
|
||||
pattern: '^[1-9]\\d*$',
|
||||
description: '父权限ID,bigint字符串,必须为正整数',
|
||||
}),
|
||||
], {
|
||||
description: '父权限ID,0表示获取根权限,其他值表示获取指定父权限下的子权限树',
|
||||
examples: ['0', '1', '100', '123456789'],
|
||||
}),
|
||||
});
|
||||
|
||||
/** 查看指定父权限下的子权限树参数类型 */
|
||||
export type GetPermissionTreeByPidParams = Static<typeof GetPermissionTreeByPidParamsSchema>;
|
||||
|
||||
/**
|
||||
* 权限排序项Schema
|
||||
* @description 单个权限的排序信息
|
||||
*/
|
||||
export const PermissionSortItemSchema = t.Object({
|
||||
/** 权限ID */
|
||||
id: t.String({
|
||||
pattern: '^[1-9]\\d*$',
|
||||
description: '权限ID,bigint字符串,必须为正整数',
|
||||
examples: ['1', '123', '456789'],
|
||||
}),
|
||||
/** 排序值 */
|
||||
sort: t.String({
|
||||
pattern: '^\\d+$',
|
||||
description: '排序值,必须为非负整数字符串',
|
||||
examples: ['0', '1', '10', '100'],
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* 权限排序接口请求体Schema
|
||||
* @description 批量更新权限排序
|
||||
*/
|
||||
export const SortPermissionSchema = t.Object({
|
||||
/** 权限排序数组 */
|
||||
permissions: t.Array(PermissionSortItemSchema, {
|
||||
minItems: 1,
|
||||
maxItems: 100,
|
||||
description: '权限排序数组,最少1项,最多100项',
|
||||
examples: [
|
||||
[
|
||||
{ id: '123', sort: '1' },
|
||||
{ id: '124', sort: '2' },
|
||||
{ id: '125', sort: '3' }
|
||||
]
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
/** 权限排序项类型 */
|
||||
export type PermissionSortItem = Static<typeof PermissionSortItemSchema>;
|
||||
|
||||
/** 权限排序请求参数类型 */
|
||||
export type SortPermissionRequest = Static<typeof SortPermissionSchema>;
|
@ -1,583 +0,0 @@
|
||||
/**
|
||||
* @file 权限模块Service层实现
|
||||
* @author AI Assistant
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 权限模块的业务逻辑实现,包括创建权限
|
||||
*/
|
||||
|
||||
import { Logger } from '@/plugins/logger/logger.service';
|
||||
import { db } from '@/plugins/drizzle/drizzle.service';
|
||||
import { sysPermission, sysRolePermissions } from '@/eneities';
|
||||
import { eq, and, max, ne, sql, asc, inArray, notInArray } from 'drizzle-orm';
|
||||
import { alias, bigint, int, mysqlTable } from 'drizzle-orm/mysql-core';
|
||||
import { successResponse, BusinessError } from '@/utils/responseFormate';
|
||||
import { nextId } from '@/utils/snowflake';
|
||||
import { DistributedLockService } from '@/utils/distributedLock';
|
||||
import type { CreatePermissionRequest } from './permission.schema';
|
||||
import type { CreatePermissionSuccessType } from './permission.response';
|
||||
|
||||
export class PermissionService {
|
||||
/**
|
||||
* 创建权限
|
||||
* @param body 新增权限请求参数
|
||||
* @returns Promise<CreatePermissionSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
*/
|
||||
public async createPermission(body: CreatePermissionRequest): Promise<CreatePermissionSuccessType> {
|
||||
const pid = body.pid || '0';
|
||||
const lockKey = `permission:create:key:${body.permissionKey}:name:${body.name}:pid:${pid}`;
|
||||
const lock = await DistributedLockService.acquire({ key: lockKey, ttl: 10 });
|
||||
|
||||
try {
|
||||
// 1. permissionKey唯一性校验
|
||||
const existKey = await db()
|
||||
.select({ id: sysPermission.id })
|
||||
.from(sysPermission)
|
||||
.where(eq(sysPermission.permissionKey, body.permissionKey))
|
||||
.limit(1);
|
||||
if (existKey.length > 0) {
|
||||
throw new BusinessError(`权限标识已存在: ${body.permissionKey}`, 409);
|
||||
}
|
||||
|
||||
// 2. name同级唯一性校验
|
||||
const existName = await db()
|
||||
.select({ id: sysPermission.id })
|
||||
.from(sysPermission)
|
||||
.where(and(eq(sysPermission.name, body.name), eq(sysPermission.pid, pid)))
|
||||
.limit(1);
|
||||
if (existName.length > 0) {
|
||||
throw new BusinessError(`权限名称在同级下已存在: ${body.name}`, 409);
|
||||
}
|
||||
|
||||
// 3. 父级校验与层级处理
|
||||
let level = 1;
|
||||
let parentModule = body.module;
|
||||
if (pid !== '0') {
|
||||
const parent = await db().select().from(sysPermission).where(eq(sysPermission.id, pid)).limit(1);
|
||||
if (parent.length === 0) {
|
||||
throw new BusinessError(`父级权限不存在: ${pid}`, 404);
|
||||
}
|
||||
if (parent[0]!.status !== 1) {
|
||||
throw new BusinessError(`父级权限状态非启用: ${pid}`, 400);
|
||||
}
|
||||
level = parent[0]!.level + 1;
|
||||
if (level > 10) {
|
||||
throw new BusinessError(`权限层级超过限制: ${level}`, 400);
|
||||
}
|
||||
// module必须与父权限一致
|
||||
if (parent[0]!.module !== body.module) {
|
||||
throw new BusinessError('父权限module不一致', 400);
|
||||
}
|
||||
parentModule = parent[0]!.module;
|
||||
}
|
||||
|
||||
// 4. sort处理(同级最大+1)
|
||||
let sort = '0';
|
||||
if (body.sort !== undefined) {
|
||||
sort = body.sort;
|
||||
} else {
|
||||
const maxSort = await db()
|
||||
.select({ maxSort: max(sysPermission.sort) })
|
||||
.from(sysPermission)
|
||||
.where(eq(sysPermission.pid, pid));
|
||||
sort = String(Number(maxSort[0]?.maxSort ?? 0) + 1);
|
||||
}
|
||||
|
||||
// 5. 数据写入
|
||||
const permissionId = nextId();
|
||||
await db()
|
||||
.insert(sysPermission)
|
||||
.values([
|
||||
{
|
||||
id: permissionId.toString(),
|
||||
pid: BigInt(pid),
|
||||
level,
|
||||
permissionKey: body.permissionKey,
|
||||
name: body.name,
|
||||
description: body.description ?? '',
|
||||
type: Number(body.type),
|
||||
apiPathKey: body.apiPathKey ?? null,
|
||||
pagePathKey: body.pagePathKey ?? null,
|
||||
module: parentModule,
|
||||
sort,
|
||||
icon: body.icon ?? null,
|
||||
status: body.status !== undefined ? Number(body.status) : 1,
|
||||
},
|
||||
] as any);
|
||||
|
||||
// 6. 查询刚插入的数据
|
||||
const insertedArr = await db().select().from(sysPermission).where(eq(sysPermission.id, permissionId.toString())).limit(1);
|
||||
if (!insertedArr || insertedArr.length === 0) {
|
||||
throw new BusinessError('创建权限失败', 500);
|
||||
}
|
||||
const inserted = insertedArr[0]!;
|
||||
Logger.info(inserted);
|
||||
|
||||
// 7. 返回统一响应
|
||||
return successResponse(
|
||||
{
|
||||
id: String(inserted.id),
|
||||
permissionKey: inserted.permissionKey,
|
||||
name: inserted.name,
|
||||
description: inserted.description,
|
||||
type: inserted.type,
|
||||
apiPathKey: inserted.apiPathKey,
|
||||
pagePathKey: inserted.pagePathKey,
|
||||
module: inserted.module,
|
||||
pid: String(inserted.pid),
|
||||
level: inserted.level,
|
||||
sort: inserted.sort,
|
||||
icon: inserted.icon,
|
||||
status: inserted.status,
|
||||
createdAt: inserted.createdAt,
|
||||
updatedAt: inserted.updatedAt,
|
||||
},
|
||||
'创建权限成功',
|
||||
);
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
* @param id 权限ID(bigint字符串)
|
||||
* @returns Promise<DeletePermissionSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
*/
|
||||
public async deletePermission(id: string): Promise<import('./permission.response').DeletePermissionSuccessType> {
|
||||
const lockKey = `permission:delete:id:${id}`;
|
||||
const lock = await DistributedLockService.acquire({ key: lockKey, ttl: 10 });
|
||||
try {
|
||||
const permArr = await db().select().from(sysPermission).where(eq(sysPermission.id, id)).limit(1);
|
||||
if (!permArr || permArr.length === 0) {
|
||||
throw new BusinessError('权限不存在', 404);
|
||||
}
|
||||
const perm = permArr[0]!;
|
||||
// if (perm.status !== 1) {
|
||||
// throw new BusinessError('权限状态非启用', 400);
|
||||
// }
|
||||
// 2. 子权限检查
|
||||
const childArr = await db().select({ id: sysPermission.id }).from(sysPermission).where(eq(sysPermission.pid, id)).limit(1);
|
||||
if (childArr.length > 0) {
|
||||
throw new BusinessError('存在子权限,无法删除', 400);
|
||||
}
|
||||
// 3. 被角色引用检查
|
||||
const refArr = await db().select({ id: sysRolePermissions.id }).from(sysRolePermissions).where(eq(sysRolePermissions.permissionId, id)).limit(1);
|
||||
if (refArr.length > 0) {
|
||||
throw new BusinessError('权限被角色引用,无法删除', 400);
|
||||
}
|
||||
// 4. 逻辑删除
|
||||
await db().delete(sysPermission).where(eq(sysPermission.id, id));
|
||||
Logger.info(`权限删除成功:${id}`);
|
||||
return successResponse({}, '权限删除成功');
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改权限
|
||||
* @param id 权限ID
|
||||
* @param body 修改字段
|
||||
* @returns Promise<UpdatePermissionSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
*/
|
||||
public async updatePermission(id: string, body: import('./permission.schema').UpdatePermissionBody): Promise<import('./permission.response').UpdatePermissionSuccessType> {
|
||||
const lockKey = `permission:update:id:${id}`;
|
||||
const lock = await DistributedLockService.acquire({ key: lockKey, ttl: 10 });
|
||||
try {
|
||||
// 1. 校验存在性
|
||||
const permArr = await db().select().from(sysPermission).where(eq(sysPermission.id, id)).limit(1);
|
||||
if (!permArr || permArr.length === 0) {
|
||||
throw new BusinessError('权限不存在', 404);
|
||||
}
|
||||
const perm = permArr[0]!;
|
||||
if (perm.status !== 1) {
|
||||
throw new BusinessError('权限状态非启用', 400);
|
||||
}
|
||||
// 2. 校验同级重名(如有修改name)
|
||||
if (body.name && body.name !== perm.name) {
|
||||
const existName = await db()
|
||||
.select({ id: sysPermission.id })
|
||||
.from(sysPermission)
|
||||
.where(and(eq(sysPermission.name, body.name), eq(sysPermission.pid, perm.pid as string), ne(sysPermission.id, id)))
|
||||
.limit(1);
|
||||
if (existName.length > 0) {
|
||||
throw new BusinessError('同级下已存在该名称', 409);
|
||||
}
|
||||
}
|
||||
// 3. 校验父权限module一致、非法移动
|
||||
let newPid = perm.pid;
|
||||
if (body.pid && body.pid !== String(perm.pid)) {
|
||||
if (body.pid === id) {
|
||||
throw new BusinessError('不能将权限移动到自身下', 400);
|
||||
}
|
||||
// 检查是否移动到子节点下(递归查)
|
||||
const allPerms = await db().select().from(sysPermission);
|
||||
const findChildren = (parentId: string, list: any[]): string[] => {
|
||||
const children = list.filter(item => String(item.pid) === parentId).map(item => String(item.id));
|
||||
return children.concat(children.flatMap(cid => findChildren(cid, list)));
|
||||
};
|
||||
const childrenIds = findChildren(id, allPerms);
|
||||
if (childrenIds.includes(body.pid)) {
|
||||
throw new BusinessError('不能将权限移动到自身子节点下', 400);
|
||||
}
|
||||
// 检查父权限存在且module一致
|
||||
const parentArr = await db().select().from(sysPermission).where(eq(sysPermission.id, body.pid)).limit(1);
|
||||
if (!parentArr || parentArr.length === 0) {
|
||||
throw new BusinessError('父权限不存在', 400);
|
||||
}
|
||||
if (body.module && parentArr[0]!.module !== body.module) {
|
||||
throw new BusinessError('父权限module不一致', 400);
|
||||
}
|
||||
newPid = body.pid;
|
||||
}
|
||||
// 4. 组装更新字段
|
||||
const updateData: any = {};
|
||||
if (body.name) updateData.name = body.name;
|
||||
if (body.description !== undefined) updateData.description = body.description;
|
||||
if (body.type !== undefined) updateData.type = Number(body.type);
|
||||
if (body.apiPathKey !== undefined) updateData.apiPathKey = body.apiPathKey;
|
||||
if (body.pagePathKey !== undefined) updateData.pagePathKey = body.pagePathKey;
|
||||
if (body.module !== undefined) updateData.module = body.module;
|
||||
if (body.icon !== undefined) updateData.icon = body.icon;
|
||||
if (body.status !== undefined) updateData.status = Number(body.status);
|
||||
if (body.sort !== undefined) updateData.sort = Number(body.sort);
|
||||
if (body.pid !== undefined) updateData.pid = newPid;
|
||||
updateData.updatedAt = new Date().toISOString();
|
||||
// 5. 执行更新
|
||||
await db().update(sysPermission).set(updateData).where(eq(sysPermission.id, id));
|
||||
// 6. 查询最新数据
|
||||
const updatedArr = await db().select().from(sysPermission).where(eq(sysPermission.id, id)).limit(1);
|
||||
if (!updatedArr || updatedArr.length === 0) {
|
||||
throw new BusinessError('权限修改失败', 500);
|
||||
}
|
||||
const updated = updatedArr[0]!;
|
||||
// 7. 返回统一响应
|
||||
return successResponse(
|
||||
{
|
||||
id: String(updated.id),
|
||||
permission_key: updated.permissionKey,
|
||||
name: updated.name,
|
||||
description: updated.description,
|
||||
type: updated.type,
|
||||
api_path_key: updated.apiPathKey,
|
||||
page_path_key: updated.pagePathKey,
|
||||
module: updated.module,
|
||||
pid: String(updated.pid),
|
||||
level: updated.level,
|
||||
sort: updated.sort,
|
||||
icon: updated.icon,
|
||||
status: updated.status,
|
||||
created_at: updated.createdAt,
|
||||
updated_at: updated.updatedAt,
|
||||
},
|
||||
'权限修改成功',
|
||||
);
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限完整树
|
||||
* @param query 查询参数(module、status、type)
|
||||
* @param pid 父权限ID,默认为'0'获取完整树
|
||||
* @returns Promise<GetPermissionTreeSuccessType>
|
||||
*/
|
||||
public async getPermissionTree(query: import('./permission.schema').GetPermissionTreeQuery, pid: string = '0'): Promise<import('./permission.response').GetPermissionTreeSuccessType> {
|
||||
// 1. 定义表别名
|
||||
const ctetTableName = 'ctet'
|
||||
// 锚点表
|
||||
const pointTable = alias(sysPermission, 'pt');
|
||||
// CTE表
|
||||
const cteTable = alias(sysPermission, ctetTableName);
|
||||
const cteAlias = mysqlTable(ctetTableName, { id: int() })
|
||||
// 递归表
|
||||
const recursiveTable = alias(sysPermission, 'rt');
|
||||
|
||||
// 2. 构建基础查询条件
|
||||
const baseConditions: any[] = [];
|
||||
if (query.module) baseConditions.push(eq(sysPermission.module, query.module));
|
||||
if (query.status && query.status !== 'all') baseConditions.push(eq(sysPermission.status, Number(query.status)));
|
||||
if (query.type && query.type !== 'all') baseConditions.push(eq(sysPermission.type, Number(query.type)));
|
||||
// 找根节点
|
||||
if(pid !== '0'){
|
||||
baseConditions.push(eq(pointTable.id, pid));
|
||||
}else{
|
||||
baseConditions.push(eq(pointTable.pid, pid));
|
||||
}
|
||||
|
||||
|
||||
// 3. 定义主查询
|
||||
const mainQuery = db().select({
|
||||
id: pointTable.id,
|
||||
permissionKey: pointTable.permissionKey,
|
||||
name: pointTable.name,
|
||||
description: pointTable.description,
|
||||
type: pointTable.type,
|
||||
apiPathKey: pointTable.apiPathKey,
|
||||
pagePathKey: pointTable.pagePathKey,
|
||||
module: pointTable.module,
|
||||
pid: pointTable.pid,
|
||||
level: pointTable.level,
|
||||
sort: pointTable.sort,
|
||||
icon: pointTable.icon,
|
||||
status: pointTable.status,
|
||||
createdAt: pointTable.createdAt,
|
||||
updatedAt: pointTable.updatedAt,
|
||||
})
|
||||
.from(pointTable)
|
||||
.where(and(...baseConditions))
|
||||
.union(
|
||||
db().select({
|
||||
id: recursiveTable.id,
|
||||
permissionKey: recursiveTable.permissionKey,
|
||||
name: recursiveTable.name,
|
||||
description: recursiveTable.description,
|
||||
type: recursiveTable.type,
|
||||
apiPathKey: recursiveTable.apiPathKey,
|
||||
pagePathKey: recursiveTable.pagePathKey,
|
||||
module: recursiveTable.module,
|
||||
pid: recursiveTable.pid,
|
||||
level: recursiveTable.level,
|
||||
sort: recursiveTable.sort,
|
||||
icon: recursiveTable.icon,
|
||||
status: recursiveTable.status,
|
||||
createdAt: recursiveTable.createdAt,
|
||||
updatedAt: recursiveTable.updatedAt,
|
||||
})
|
||||
.from(recursiveTable)
|
||||
.innerJoin(cteAlias, eq(recursiveTable.pid, cteTable.id))
|
||||
)
|
||||
// 4. 构建最终查询
|
||||
const finalQuery = db()
|
||||
.select({
|
||||
id: cteTable.id,
|
||||
permissionKey: cteTable.permissionKey,
|
||||
name: cteTable.name,
|
||||
description: cteTable.description,
|
||||
type: cteTable.type,
|
||||
apiPathKey: cteTable.apiPathKey,
|
||||
pagePathKey: cteTable.pagePathKey,
|
||||
module: cteTable.module,
|
||||
pid: cteTable.pid,
|
||||
level: cteTable.level,
|
||||
sort: cteTable.sort,
|
||||
icon: cteTable.icon,
|
||||
status: cteTable.status,
|
||||
createdAt: cteTable.createdAt,
|
||||
updatedAt: cteTable.updatedAt,
|
||||
})
|
||||
.from(cteAlias)
|
||||
.orderBy(asc(cteTable.level), asc(cteTable.sort));
|
||||
|
||||
// 5. 将union查询放入WITH RECURSIVE中,并构建最终查询
|
||||
const recursiveSql = sql` WITH RECURSIVE ${cteTable} AS ( ${mainQuery} ) ${finalQuery} `;
|
||||
// 6. 执行递归查询
|
||||
const [rows] = await db().execute(recursiveSql);
|
||||
const list = Array.isArray(rows) ? rows : (rows as any)[0];
|
||||
|
||||
// 7. 构建树结构
|
||||
const nodeMap: Record<string, any> = {};
|
||||
const roots: any[] = [];
|
||||
|
||||
for (const item of list as any[]) {
|
||||
const node = {
|
||||
id: String(item.id),
|
||||
permissionKey: item.permissionKey || item.permission_key,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
type: item.type,
|
||||
apiPathKey: item.apiPathKey || item.api_path_key,
|
||||
pagePathKey: item.pagePathKey || item.page_path_key,
|
||||
module: item.module,
|
||||
pid: String(item.pid),
|
||||
level: item.level,
|
||||
sort: String(item.sort || '0'),
|
||||
icon: item.icon,
|
||||
status: item.status,
|
||||
createdAt: item.createdAt || item.created_at,
|
||||
updatedAt: item.updatedAt || item.updated_at,
|
||||
children: [],
|
||||
};
|
||||
nodeMap[node.id] = node;
|
||||
}
|
||||
|
||||
// 8. 组装父子关系
|
||||
for (const node of Object.values(nodeMap)) {
|
||||
if (node.pid && node.pid !== '0' && nodeMap[node.pid]) {
|
||||
nodeMap[node.pid].children.push(node);
|
||||
} else {
|
||||
roots.push(node);
|
||||
}
|
||||
}
|
||||
return successResponse(roots, '查询权限树成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限排序
|
||||
* @param body 权限排序请求参数
|
||||
* @returns Promise<SortPermissionSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
* @type API =====================================================================
|
||||
*/
|
||||
public async sortPermissions(body: import('./permission.schema').SortPermissionRequest): Promise<import('./permission.response').SortPermissionSuccessType> {
|
||||
// 1. 参数验证 - 检查权限ID的唯一性
|
||||
const permissionIds = body.permissions.map(p => p.id);
|
||||
const uniqueIds = new Set(permissionIds);
|
||||
if (uniqueIds.size !== permissionIds.length) {
|
||||
const duplicates = permissionIds.filter((id, index) => permissionIds.indexOf(id) !== index);
|
||||
throw new BusinessError(`权限ID重复: ${duplicates.join(', ')}`, 400);
|
||||
}
|
||||
|
||||
// 2. 检查sort值的唯一性
|
||||
const sortValues = body.permissions.map(p => p.sort);
|
||||
const uniqueSorts = new Set(sortValues);
|
||||
if (uniqueSorts.size !== sortValues.length) {
|
||||
const duplicateSorts = sortValues.filter((sort, index) => sortValues.indexOf(sort) !== index);
|
||||
const duplicateIds = body.permissions
|
||||
.filter(p => duplicateSorts.includes(p.sort))
|
||||
.map(p => p.id);
|
||||
throw new BusinessError(`排序值重复:${duplicateIds.join(', ')}`, 409);
|
||||
}
|
||||
|
||||
// 3. 获取分布式锁 - 使用第一个权限的ID作为锁的一部分
|
||||
const lockKey = `permission:sort:ids:${permissionIds.join(':')}`;
|
||||
const lock = await DistributedLockService.acquire({ key: lockKey, ttl: 10 });
|
||||
|
||||
try {
|
||||
// 4. 查询所有权限是否存在且状态正常
|
||||
const permissions = await db()
|
||||
.select({
|
||||
id: sysPermission.id,
|
||||
name: sysPermission.name,
|
||||
pid: sysPermission.pid,
|
||||
status: sysPermission.status,
|
||||
sort: sysPermission.sort,
|
||||
})
|
||||
.from(sysPermission)
|
||||
.where(inArray(sysPermission.id, permissionIds));
|
||||
|
||||
// 5. 检查权限是否都存在
|
||||
if (permissions.length !== permissionIds.length) {
|
||||
const foundIds = permissions.map(p => String(p.id));
|
||||
const missingIds = permissionIds.filter(id => !foundIds.includes(id));
|
||||
throw new BusinessError(`权限不存在: ${missingIds.join(', ')}`, 404);
|
||||
}
|
||||
|
||||
// 6. 检查权限状态
|
||||
const disabledPermissions = permissions.filter(p => p.status !== 1);
|
||||
if (disabledPermissions.length > 0) {
|
||||
const disabledIds = disabledPermissions.map(p => String(p.id));
|
||||
throw new BusinessError(`权限状态非启用: ${disabledIds.join(', ')}`, 400);
|
||||
}
|
||||
|
||||
// 7. 检查所有权限是否在同一级别(相同pid)
|
||||
const pids = permissions.map(p => String(p.pid));
|
||||
const uniquePids = new Set(pids);
|
||||
if (uniquePids.size !== 1) {
|
||||
throw new BusinessError('权限不在同一级别', 400);
|
||||
}
|
||||
|
||||
// 8. 检查同级别下是否有其他权限使用了相同的sort值
|
||||
const pid = pids[0]!;
|
||||
const existingSorts = await db()
|
||||
.select({ id: sysPermission.id, sort: sysPermission.sort })
|
||||
.from(sysPermission)
|
||||
.where(
|
||||
and(
|
||||
eq(sysPermission.pid, pid),
|
||||
notInArray(sysPermission.id, permissionIds)
|
||||
)
|
||||
);
|
||||
|
||||
const existingSortValues = existingSorts.map(p => p.sort);
|
||||
const conflictingSorts = sortValues.filter(sort => existingSortValues.includes(sort));
|
||||
if (conflictingSorts.length > 0) {
|
||||
throw new BusinessError(`排序值与其他权限冲突: ${conflictingSorts.join(', ')}`, 409);
|
||||
}
|
||||
|
||||
// 9. 准备更新数据
|
||||
const updatePromises = body.permissions.map(async (permData) => {
|
||||
return db()
|
||||
.update(sysPermission)
|
||||
.set({
|
||||
sort: permData.sort,
|
||||
})
|
||||
.where(eq(sysPermission.id, permData.id));
|
||||
});
|
||||
|
||||
// 10. 批量执行更新
|
||||
await Promise.all(updatePromises);
|
||||
|
||||
// 11. 查询更新后的权限信息
|
||||
const updatedPermissions = await db()
|
||||
.select({
|
||||
id: sysPermission.id,
|
||||
name: sysPermission.name,
|
||||
sort: sysPermission.sort,
|
||||
})
|
||||
.from(sysPermission)
|
||||
.where(inArray(sysPermission.id, permissionIds))
|
||||
.orderBy(asc(sysPermission.sort));
|
||||
|
||||
Logger.info(`权限排序成功,更新了 ${updatedPermissions.length} 个权限`);
|
||||
|
||||
// 12. 返回成功响应
|
||||
return successResponse(
|
||||
{
|
||||
updated_count: updatedPermissions.length,
|
||||
updated_permissions: updatedPermissions.map(p => ({
|
||||
id: String(p.id),
|
||||
name: p.name,
|
||||
sort: p.sort,
|
||||
})),
|
||||
},
|
||||
'权限排序更新成功'
|
||||
);
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限详情
|
||||
* @param id 权限ID(bigint字符串)
|
||||
* @returns Promise<CreatePermissionSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
*/
|
||||
public async getPermissionDetail(id: string): Promise<CreatePermissionSuccessType> {
|
||||
if (!/^[1-9]\d*$/.test(id)) {
|
||||
throw new BusinessError('参数格式错误', 400);
|
||||
}
|
||||
const permArr = await db().select().from(sysPermission).where(eq(sysPermission.id, id)).limit(1);
|
||||
if (!permArr || permArr.length === 0) {
|
||||
throw new BusinessError('权限不存在', 404);
|
||||
}
|
||||
const perm = permArr[0]!;
|
||||
Logger.info(`获取权限详情:${id}`);
|
||||
return successResponse({
|
||||
id: String(perm.id),
|
||||
pid: String(perm.pid),
|
||||
level: perm.level,
|
||||
permissionKey: perm.permissionKey,
|
||||
name: perm.name,
|
||||
description: perm.description,
|
||||
type: perm.type,
|
||||
apiPathKey: perm.apiPathKey,
|
||||
pagePathKey: perm.pagePathKey,
|
||||
module: perm.module,
|
||||
sort: perm.sort,
|
||||
icon: perm.icon,
|
||||
status: perm.status,
|
||||
createdAt: perm.createdAt,
|
||||
updatedAt: perm.updatedAt,
|
||||
}, '获取权限详情成功');
|
||||
}
|
||||
}
|
||||
|
||||
export const permissionService = new PermissionService();
|
@ -1,131 +0,0 @@
|
||||
/**
|
||||
* @file Winston日志器工具类
|
||||
* @author hotok
|
||||
* @date 2025-06-28
|
||||
* @lastEditor hotok
|
||||
* @lastEditTime 2025-06-28
|
||||
* @description 基于winston的高性能日志记录器,支持分环境输出、按日期轮转、彩色美化
|
||||
*/
|
||||
|
||||
import winston from 'winston';
|
||||
import DailyRotateFile from 'winston-daily-rotate-file';
|
||||
|
||||
const loggerConfig = {
|
||||
directory: 'logs',
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
level: 'info',
|
||||
console: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* 控制台日志传输器
|
||||
*/
|
||||
|
||||
const consoleTransport = new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YY/MM/DD HH:mm:ss - SSS' }),
|
||||
winston.format.printf(({ timestamp, message, level, stack }) => {
|
||||
return `[${timestamp}] ${level} ${message} ${stack}`;
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* 应用主日志文件传输器
|
||||
*/
|
||||
const appFileTransport = new DailyRotateFile({
|
||||
filename: `${loggerConfig.directory}/app-%DATE%.log`,
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
maxSize: loggerConfig.maxSize,
|
||||
maxFiles: loggerConfig.maxFiles,
|
||||
format: winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.json()),
|
||||
});
|
||||
|
||||
/**
|
||||
* 错误专用日志文件传输器
|
||||
*/
|
||||
const errorFileTransport = new DailyRotateFile({
|
||||
filename: `${loggerConfig.directory}/error-%DATE%.log`,
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
maxSize: loggerConfig.maxSize,
|
||||
maxFiles: loggerConfig.maxFiles,
|
||||
level: 'error',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.errors({ stack: true }), // 确保堆栈信息被记录
|
||||
winston.format.json(),
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* Winston日志器实例
|
||||
*/
|
||||
const logger = winston.createLogger({
|
||||
/** 日志级别 */
|
||||
level: loggerConfig.level,
|
||||
|
||||
/** 传输器配置 */
|
||||
transports: [
|
||||
// 应用主日志文件
|
||||
appFileTransport,
|
||||
|
||||
// 错误专用日志文件
|
||||
errorFileTransport,
|
||||
|
||||
// 控制台日志(如果启用)
|
||||
...(loggerConfig.console ? [consoleTransport] : []),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* 格式化日志消息,支持字符串和对象
|
||||
* @param message 日志消息,可以是字符串或对象
|
||||
* @returns 格式化后的字符串
|
||||
*/
|
||||
const formatMessage = (message: string | object): string => {
|
||||
if (typeof message === 'string') {
|
||||
return message;
|
||||
}
|
||||
return JSON.stringify(message, (_, v) => (typeof v === 'bigint' ? v.toString() : v), 2);
|
||||
};
|
||||
|
||||
/**
|
||||
* 日志记录器类
|
||||
*/
|
||||
export class Logger {
|
||||
static debug(message: string | object): void {
|
||||
logger.debug(formatMessage(message));
|
||||
}
|
||||
static info(message: string | object): void {
|
||||
logger.info(formatMessage(message));
|
||||
}
|
||||
static warn(message: string | object): void {
|
||||
logger.warn(formatMessage(message));
|
||||
}
|
||||
static error(error: Error): void {
|
||||
logger.error({
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
cause: error.cause,
|
||||
});
|
||||
}
|
||||
static http(message: string | object): void {
|
||||
logger.http(message);
|
||||
}
|
||||
static verbose(message: string | object): void {
|
||||
logger.verbose(formatMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
// 导出默认实例
|
||||
export default Logger;
|
||||
|
||||
|
||||
Logger.info('test');
|
||||
Logger.error(new Error('test'));
|
||||
Logger.http('test');
|
||||
Logger.verbose('test');
|
||||
Logger.debug('test');
|
||||
Logger.warn('test');
|
@ -1,136 +0,0 @@
|
||||
# 权限模块开发计划
|
||||
|
||||
## 1. 权限表设计
|
||||
|
||||
```sql
|
||||
CREATE TABLE sys_permission (
|
||||
id BIGINT PRIMARY KEY COMMENT '主键ID',
|
||||
pid BIGINT DEFAULT 0 COMMENT '父权限ID',
|
||||
level TINYINT NOT NULL DEFAULT 1 COMMENT '权限层级',
|
||||
permission_key VARCHAR(100) NOT NULL COMMENT '权限标识',
|
||||
name VARCHAR(50) NOT NULL COMMENT '权限名称',
|
||||
description VARCHAR(255) DEFAULT '' COMMENT '权限描述',
|
||||
type TINYINT NOT NULL DEFAULT 1 COMMENT '权限类型(1=菜单 2=按钮 3=接口 4=数据)',
|
||||
api_path_key VARCHAR(200) DEFAULT NULL COMMENT '接口路径标识',
|
||||
page_path_key VARCHAR(200) DEFAULT NULL COMMENT '前端路由标识',
|
||||
module VARCHAR(30) NOT NULL COMMENT '所属模块',
|
||||
sort BIGINT DEFAULT 0 COMMENT '排序值',
|
||||
icon VARCHAR(100) DEFAULT NULL COMMENT '图标标识',
|
||||
status TINYINT DEFAULT 1 COMMENT '状态(1=启用, 0禁用)',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
/* 唯一约束 */
|
||||
UNIQUE KEY uniq_permission_key (permission_key),
|
||||
|
||||
/* 单列索引 */
|
||||
KEY idx_pid (pid),
|
||||
KEY idx_level (level),
|
||||
KEY idx_type (type),
|
||||
KEY idx_module (module),
|
||||
KEY idx_time (created_at),
|
||||
|
||||
/* 复合索引 */
|
||||
KEY idx_parent_sort (pid, sort)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='RBAC权限表';
|
||||
```
|
||||
|
||||
## 2. 接口功能描述
|
||||
|
||||
2.1 新增权限
|
||||
- 沿用父模块的module
|
||||
2.2 删除权限
|
||||
2.3 修改权限
|
||||
- 存在父权限时,module必须一致
|
||||
- 同级允许重名
|
||||
2.4 查看权限完整树
|
||||
- 模块、状态、类型筛选
|
||||
2.4 查看指定权限树
|
||||
- 根据id查其下级树
|
||||
2.5 排序
|
||||
|
||||
## 3. 接口开发计划
|
||||
|
||||
### 3.1 新增权限接口
|
||||
|
||||
- [x] 1.0 创建新增权限接口 (POST /api/permission)
|
||||
- [x] 1.1 生成当前接口业务逻辑文档,写入 `permission.docs.md`
|
||||
- [x] 1.2 创建 `permission.schema.ts` - 定义新增权限Schema
|
||||
- [x] 1.3 创建 `permission.response.ts` - 定义新增权限响应格式
|
||||
- [x] 1.4 创建 `permission.service.ts` - 实现新增权限业务逻辑
|
||||
- [x] 1.5 创建 `permission.controller.ts` - 实现新增权限路由
|
||||
|
||||
### 3.2 删除权限接口
|
||||
|
||||
- [x] 2.0 创建删除权限接口 (DELETE /api/permission/:id)
|
||||
- [x] 2.1 更新 `permission.docs.md` - 添加删除权限业务逻辑文档
|
||||
- [x] 2.2 更新 `permission.schema.ts` - 定义删除权限Schema
|
||||
- [x] 2.3 更新 `permission.response.ts` - 定义删除权限响应格式
|
||||
- [x] 2.4 更新 `permission.service.ts` - 实现删除权限业务逻辑
|
||||
- [x] 2.5 更新 `permission.controller.ts` - 实现删除权限路由
|
||||
|
||||
### 3.3 修改权限接口
|
||||
|
||||
- [x] 3.0 创建修改权限接口 (PUT /api/permission/:id)
|
||||
- [x] 3.1 更新 `permission.docs.md` - 添加修改权限业务逻辑文档
|
||||
- [x] 3.2 更新 `permission.schema.ts` - 定义修改权限Schema
|
||||
- [x] 3.3 更新 `permission.response.ts` - 定义修改权限响应格式
|
||||
- [x] 3.4 更新 `permission.service.ts` - 实现修改权限业务逻辑
|
||||
- [x] 3.5 更新 `permission.controller.ts` - 实现修改权限路由
|
||||
|
||||
### 3.4 查看权限完整树接口
|
||||
|
||||
- [x] 4.0 创建查看权限完整树接口 (GET /api/permission/tree)
|
||||
- [x] 4.1 更新 `permission.docs.md` - 添加查看权限完整树业务逻辑文档
|
||||
- [x] 4.2 更新 `permission.schema.ts` - 定义查看权限完整树Schema
|
||||
- [x] 4.3 更新 `permission.response.ts` - 定义查看权限完整树响应格式
|
||||
- [x] 4.4 更新 `permission.service.ts` - 实现查看权限完整树业务逻辑
|
||||
- [x] 4.5 更新 `permission.controller.ts` - 实现查看权限完整树路由
|
||||
|
||||
### 3.5 查看指定权限树接口
|
||||
|
||||
- ~~[ ] 5.0 创建查看指定权限树接口 (GET /api/permission/:id/tree)~~
|
||||
- ~~[ ] 5.1 更新 `permission.docs.md` - 添加查看指定权限树业务逻辑文档~~
|
||||
- ~~[ ] 5.2 更新 `permission.schema.ts` - 定义查看指定权限树Schema~~
|
||||
- ~~[ ] 5.3 更新 `permission.response.ts` - 定义查看指定权限树响应格式~~
|
||||
- ~~[ ] 5.4 更新 `permission.service.ts` - 实现查看指定权限树业务逻辑~~
|
||||
- ~~[ ] 5.5 更新 `permission.controller.ts` - 实现查看指定权限树路由~~
|
||||
|
||||
### 3.6 权限排序接口
|
||||
|
||||
- [x] 6.0 创建权限排序接口 (PUT /api/permission/sort)
|
||||
- [x] 6.1 更新 `permission.docs.md` - 添加权限排序业务逻辑文档
|
||||
- [x] 6.2 更新 `permission.schema.ts` - 定义权限排序Schema
|
||||
- [x] 6.3 更新 `permission.response.ts` - 定义权限排序响应格式
|
||||
- [x] 6.4 更新 `permission.service.ts` - 实现权限排序业务逻辑
|
||||
- [x] 6.5 更新 `permission.controller.ts` - 实现权限排序路由
|
||||
|
||||
### 3.7 获取权限详情接口
|
||||
|
||||
- [x] 7.0 创建获取权限详情接口 (GET /api/permission/:id)
|
||||
- [x] 7.1 更新 `permission.docs.md` - 添加获取权限详情业务逻辑文档
|
||||
- [x] 7.2 更新 `permission.schema.ts` - 定义获取权限详情Schema
|
||||
- [x] 7.3 更新 `permission.response.ts` - 定义获取权限详情响应格式
|
||||
- [x] 7.4 更新 `permission.service.ts` - 实现获取权限详情业务逻辑
|
||||
- [x] 7.5 更新 `permission.controller.ts` - 实现获取权限详情路由
|
||||
|
||||
|
||||
## 4. 相关文件
|
||||
|
||||
- `src/modules/permission/permission.docs.md` - 权限模块业务逻辑文档
|
||||
- `src/modules/permission/permission.schema.ts` - 权限模块Schema定义
|
||||
- `src/modules/permission/permission.response.ts` - 权限模块响应格式定义
|
||||
- `src/modules/permission/permission.service.ts` - 权限模块业务逻辑实现
|
||||
- `src/modules/permission/permission.controller.ts` - 权限模块路由控制器
|
||||
- `src/modules/permission/permission.test.md` - 权限模块测试用例文档
|
||||
- `src/eneities/sysPermission.ts` - 权限表实体定义
|
||||
- `src/eneities/index.ts` - 实体导出文件更新
|
||||
|
||||
### 备注
|
||||
|
||||
- 权限模块需要支持树形结构,注意父子关系的处理
|
||||
- 权限key必须唯一,需要做好唯一性验证
|
||||
- 删除权限时需要检查是否有子权限,有则不允许删除
|
||||
- 修改权限时,如果存在父权限,module必须与父权限一致
|
||||
- 同级权限允许重名,但权限key必须唯一
|
||||
- 排序功能需要支持拖拽排序,更新多个权限的sort_order值
|
Loading…
Reference in New Issue
Block a user