feat: 添加JWT认证和模块管理功能

- 新增JWT认证插件,支持用户登录、刷新Token功能
- 添加系统模块管理功能,包括模块的创建、更新和查询
- 更新用户服务,支持用户注册、登录和Token刷新
- 修改数据库表结构,添加模块相关表和字段
- 更新文档,添加JWT认证和模块管理相关说明
This commit is contained in:
expressgy 2025-03-26 19:07:09 +08:00
parent fdc4d67092
commit 81953dd44c
28 changed files with 1811 additions and 851 deletions

10
.env
View File

@ -20,7 +20,7 @@ DB_PORT=3306
# redis # redis
REDIS_HOST=172.16.1.10 REDIS_HOST=172.16.1.10
REDIS_PORT=16379 REDIS_PORT=6379
REDIS_PASSWORD=Hxl1314521 REDIS_PASSWORD=Hxl1314521
REDIS_DB=9 REDIS_DB=9
@ -29,3 +29,11 @@ DATACENTER_ID=1
# 机器ID # 机器ID
MACHINE_ID=1 MACHINE_ID=1
# JWT配置
# 过期时间
JWT_ACCESS_EXPIRES=20m
# 刷新时间
JWT_REFRESH_EXPIRES=14d
# 密钥
JWT_SECRET=your_secure_secret_key

View File

@ -1,2 +1,3 @@
import { relations } from 'drizzle-orm/relations'; import { relations } from "drizzle-orm/relations";
import {} from './schema'; import { } from "./schema";

View File

@ -1,339 +1,262 @@
import { import { mysqlTable, mysqlSchema, AnyMySqlColumn, primaryKey, unique, bigint, int, tinyint, varchar, datetime } from "drizzle-orm/mysql-core"
mysqlTable, import { sql } from "drizzle-orm"
mysqlSchema,
AnyMySqlColumn,
primaryKey,
unique,
bigint,
int,
tinyint,
varchar,
datetime,
} from 'drizzle-orm/mysql-core';
import { sql } from 'drizzle-orm';
export const sysDict = mysqlTable( export const sysDict = mysqlTable("sys_dict", {
'sys_dict', id: bigint({ mode: "number" }).notNull(),
{ version: int().default(0).notNull(),
id: bigint({ mode: 'number' }).autoincrement().notNull(), pid: bigint({ mode: "number" }).notNull(),
version: int().default(0).notNull(), module: tinyint(),
pid: bigint({ mode: 'number' }).notNull(), dictKey: varchar("dict_key", { length: 255 }),
module: tinyint(), value: varchar({ length: 255 }),
dictKey: varchar('dict_key', { length: 255 }), description: varchar({ length: 255 }),
value: varchar({ length: 255 }), sort: int().default(0).notNull(),
description: varchar({ length: 255 }), status: int().notNull(),
sort: int().default(0).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
status: int().notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
createdAt: datetime('created_at', { mode: 'string' }) },
.default(sql`(CURRENT_TIMESTAMP)`) (table) => [
.notNull(), primaryKey({ columns: [table.id], name: "sys_dict_id"}),
updatedAt: datetime('updated_at', { mode: 'string' }) unique("uniq_dict_key").on(table.dictKey, table.pid),
.default(sql`(CURRENT_TIMESTAMP)`) ]);
.notNull(),
},
table => [
primaryKey({ columns: [table.id], name: 'sys_dict_id' }),
unique('uniq_dict_key').on(table.dictKey, table.pid),
],
);
export const sysOrganization = mysqlTable( export const sysModule = mysqlTable("sys_module", {
'sys_organization', id: int().autoincrement().notNull(),
{ version: int().default(0).notNull(),
orgId: bigint('org_id', { mode: 'number' }).autoincrement().notNull(), name: varchar({ length: 32 }).notNull(),
pid: bigint({ mode: 'number' }).notNull(), moduleKey: varchar("module_key", { length: 255 }).notNull(),
orgName: varchar('org_name', { length: 255 }), description: varchar({ length: 255 }),
orgCode: varchar('org_code', { length: 128 }), sort: int().default(0).notNull(),
orgType: int('org_type').notNull(), status: int().notNull(),
description: varchar({ length: 255 }), createdBy: bigint("created_by", { mode: "number" }).notNull(),
sort: int().default(0).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
status: int().notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), },
createdAt: datetime('created_at', { mode: 'string' }) (table) => [
.default(sql`(CURRENT_TIMESTAMP)`) primaryKey({ columns: [table.id], name: "sys_module_id"}),
.notNull(), unique("uniq_name").on(table.name),
updatedAt: datetime('updated_at', { mode: 'string' }) unique("uniq_module_key").on(table.moduleKey),
.default(sql`(CURRENT_TIMESTAMP)`) ]);
.notNull(),
},
table => [
primaryKey({ columns: [table.orgId], name: 'sys_organization_org_id' }),
unique('uniq_org_code').on(table.orgCode, table.pid),
unique('uniq_org_name').on(table.orgName, table.pid),
],
);
export const sysOrganizationManager = mysqlTable( export const sysOrganization = mysqlTable("sys_organization", {
'sys_organization_manager', orgId: bigint("org_id", { mode: "number" }).notNull(),
{ pid: bigint({ mode: "number" }).notNull(),
id: bigint({ mode: 'number' }).autoincrement().notNull(), orgName: varchar("org_name", { length: 255 }),
version: int().default(0).notNull(), orgCode: varchar("org_code", { length: 128 }),
orgId: bigint('org_id', { mode: 'number' }).notNull(), orgType: int("org_type").notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), description: varchar({ length: 255 }),
rank: int().notNull(), sort: int().default(0).notNull(),
description: varchar({ length: 255 }), status: int().notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
createdAt: datetime('created_at', { mode: 'string' }) createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.notNull(), },
updatedAt: datetime('updated_at', { mode: 'string' }) (table) => [
.default(sql`(CURRENT_TIMESTAMP)`) primaryKey({ columns: [table.orgId], name: "sys_organization_org_id"}),
.notNull(), unique("uniq_org_code").on(table.orgCode, table.pid),
}, unique("uniq_org_name").on(table.orgName, table.pid),
table => [ ]);
primaryKey({ columns: [table.id], name: 'sys_organization_manager_id' }),
unique('uniq_org_user').on(table.orgId, table.userId),
],
);
export const sysPermission = mysqlTable( export const sysOrganizationManager = mysqlTable("sys_organization_manager", {
'sys_permission', id: bigint({ mode: "number" }).notNull(),
{ version: int().default(0).notNull(),
permId: bigint('perm_id', { mode: 'number' }).autoincrement().notNull(), orgId: bigint("org_id", { mode: "number" }).notNull(),
pid: bigint({ mode: 'number' }).notNull(), userId: bigint("user_id", { mode: "number" }).notNull(),
permName: varchar('perm_name', { length: 255 }).notNull(), rank: int().notNull(),
permKey: varchar('perm_key', { length: 255 }).notNull(), status: int().notNull(),
url: varchar({ length: 255 }), description: varchar({ length: 255 }),
avatarUrl: varchar('avatar_url', { length: 255 }), createdBy: bigint("created_by", { mode: "number" }).notNull(),
description: varchar({ length: 255 }), updatedBy: bigint("updated_by", { mode: "number" }),
permType: int('perm_type').notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
isVisible: int('is_visible').default(0).notNull(), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
version: int().default(0).notNull(), },
sort: int().default(0).notNull(), (table) => [
status: int().notNull(), primaryKey({ columns: [table.id], name: "sys_organization_manager_id"}),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), unique("uniq_org_user").on(table.orgId, table.userId),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), ]);
createdAt: datetime('created_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.permId], name: 'sys_permission_perm_id' }),
unique('uniq_pid_name').on(table.permName, table.pid),
unique('uniq_perm_key').on(table.permKey),
],
);
export const sysReRolePermission = mysqlTable( export const sysPermission = mysqlTable("sys_permission", {
'sys_re_role_permission', permId: bigint("perm_id", { mode: "number" }).notNull(),
{ pid: bigint({ mode: "number" }).notNull(),
id: bigint({ mode: 'number' }).autoincrement().notNull(), permName: varchar("perm_name", { length: 255 }).notNull(),
roleId: bigint('role_id', { mode: 'number' }).notNull(), permKey: varchar("perm_key", { length: 255 }).notNull(),
permId: bigint('perm_id', { mode: 'number' }).notNull(), url: varchar({ length: 255 }),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), avatarUrl: varchar("avatar_url", { length: 255 }),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), description: varchar({ length: 255 }),
createdAt: datetime('created_at', { mode: 'string' }) permType: int("perm_type").notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) isVisible: int("is_visible").default(0).notNull(),
.notNull(), version: int().default(0).notNull(),
updatedAt: datetime('updated_at', { mode: 'string' }) sort: int().default(0).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) status: int().notNull(),
.notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
}, updatedBy: bigint("updated_by", { mode: "number" }),
table => [ createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
primaryKey({ columns: [table.id], name: 'sys_re_role_permission_id' }), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
unique('uniq_perm_role').on(table.roleId, table.permId), },
], (table) => [
); primaryKey({ columns: [table.permId], name: "sys_permission_perm_id"}),
unique("uniq_pid_name").on(table.permName, table.pid),
unique("uniq_perm_key").on(table.permKey),
]);
export const sysReUserOrganization = mysqlTable( export const sysProfile = mysqlTable("sys_profile", {
'sys_re_user_organization', id: bigint({ mode: "number" }).notNull(),
{ version: int().default(0).notNull(),
id: bigint({ mode: 'number' }).autoincrement().notNull(), name: varchar({ length: 32 }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), profileKey: varchar("profile_key", { length: 255 }).notNull(),
orgId: bigint('org_id', { mode: 'number' }).notNull(), description: varchar({ length: 255 }),
version: int().default(0).notNull(), content: varchar({ length: 255 }),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
createdAt: datetime('created_at', { mode: 'string' }) createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.notNull(), },
updatedAt: datetime('updated_at', { mode: 'string' }) (table) => [
.default(sql`(CURRENT_TIMESTAMP)`) primaryKey({ columns: [table.id], name: "sys_profile_id"}),
.notNull(), unique("uniq_name").on(table.name),
}, unique("uniq_profile_key").on(table.profileKey),
table => [ ]);
primaryKey({ columns: [table.id], name: 'sys_re_user_organization_id' }),
unique('uniq_user_org').on(table.userId, table.orgId),
],
);
export const sysReUserRole = mysqlTable( export const sysReRolePermission = mysqlTable("sys_re_role_permission", {
'sys_re_user_role', id: bigint({ mode: "number" }).notNull(),
{ roleId: bigint("role_id", { mode: "number" }).notNull(),
id: bigint({ mode: 'number' }).autoincrement().notNull(), permId: bigint("perm_id", { mode: "number" }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
roleId: bigint('role_id', { mode: 'number' }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
version: int().default(0).notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), },
createdAt: datetime('created_at', { mode: 'string' }) (table) => [
.default(sql`(CURRENT_TIMESTAMP)`) primaryKey({ columns: [table.id], name: "sys_re_role_permission_id"}),
.notNull(), unique("uniq_perm_role").on(table.roleId, table.permId),
updatedAt: datetime('updated_at', { mode: 'string' }) ]);
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.id], name: 'sys_re_user_role_id' }),
unique('uniq_user_role').on(table.userId, table.roleId),
],
);
export const sysRole = mysqlTable( export const sysReUserOrganization = mysqlTable("sys_re_user_organization", {
'sys_role', id: bigint({ mode: "number" }).notNull(),
{ userId: bigint("user_id", { mode: "number" }).notNull(),
roleId: bigint('role_id', { mode: 'number' }).autoincrement().notNull(), orgId: bigint("org_id", { mode: "number" }).notNull(),
pid: bigint({ mode: 'number' }).notNull(), version: int().default(0).notNull(),
roleName: varchar('role_name', { length: 255 }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
roleKey: varchar('role_key', { length: 255 }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
description: varchar({ length: 255 }), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
status: int().notNull(), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), },
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), (table) => [
createdAt: datetime('created_at', { mode: 'string' }) primaryKey({ columns: [table.id], name: "sys_re_user_organization_id"}),
.default(sql`(CURRENT_TIMESTAMP)`) unique("uniq_user_org").on(table.userId, table.orgId),
.notNull(), ]);
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.roleId], name: 'sys_role_role_id' }),
unique('uniq_role_pid').on(table.roleName, table.pid),
],
);
export const sysUser = mysqlTable( export const sysReUserRole = mysqlTable("sys_re_user_role", {
'sys_user', id: bigint({ mode: "number" }).notNull(),
{ userId: bigint("user_id", { mode: "number" }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), roleId: bigint("role_id", { mode: "number" }).notNull(),
pid: bigint({ mode: 'number' }).notNull(), version: int().default(0).notNull(),
username: varchar({ length: 255 }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
email: varchar({ length: 255 }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
phone: varchar({ length: 255 }), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
avatarUrl: varchar('avatar_url', { length: 255 }), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
userType: tinyint('user_type'), },
status: tinyint().default(0).notNull(), (table) => [
createdBy: bigint('created_by', { mode: 'number' }).notNull(), primaryKey({ columns: [table.id], name: "sys_re_user_role_id"}),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), unique("uniq_user_role").on(table.userId, table.roleId),
createdAt: datetime('created_at', { mode: 'string' }) ]);
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.userId], name: 'sys_user_user_id' }),
unique('uniq_username').on(table.username),
unique('uniq_email').on(table.email),
],
);
export const sysUserAuth = mysqlTable( export const sysRole = mysqlTable("sys_role", {
'sys_user_auth', roleId: bigint("role_id", { mode: "number" }).notNull(),
{ pid: bigint({ mode: "number" }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), roleName: varchar("role_name", { length: 255 }).notNull(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(), roleKey: varchar("role_key", { length: 255 }).notNull(),
passwordModified: datetime('password_modified', { mode: 'string' }).notNull(), description: varchar({ length: 255 }),
passwordExpire: datetime('password_expire', { mode: 'string' }), status: int().notNull(),
}, createdBy: bigint("created_by", { mode: "number" }).notNull(),
table => [primaryKey({ columns: [table.userId], name: 'sys_user_auth_user_id' })], updatedBy: bigint("updated_by", { mode: "number" }),
); createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
},
(table) => [
primaryKey({ columns: [table.roleId], name: "sys_role_role_id"}),
unique("uniq_role_pid").on(table.roleName, table.pid),
]);
export const sysUserAuthHistory = mysqlTable( export const sysUser = mysqlTable("sys_user", {
'sys_user_auth_history', userId: bigint("user_id", { mode: "number" }).notNull(),
{ pid: bigint({ mode: "number" }).notNull(),
id: bigint({ mode: 'number' }).autoincrement().notNull(), username: varchar({ length: 255 }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), email: varchar({ length: 255 }).notNull(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(), phone: varchar({ length: 255 }),
modifiedAt: varchar('modified_at', { length: 255 }).notNull(), avatarUrl: varchar("avatar_url", { length: 255 }),
}, userType: tinyint("user_type"),
table => [primaryKey({ columns: [table.id], name: 'sys_user_auth_history_id' })], status: tinyint().default(0).notNull(),
); createdBy: bigint("created_by", { mode: "number" }).notNull(),
updatedBy: bigint("updated_by", { mode: "number" }),
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
},
(table) => [
primaryKey({ columns: [table.userId], name: "sys_user_user_id"}),
unique("uniq_username").on(table.username),
unique("uniq_email").on(table.email),
]);
export const sysUserFieldDefinition = mysqlTable( export const sysUserAuth = mysqlTable("sys_user_auth", {
'sys_user_field_definition', userId: bigint("user_id", { mode: "number" }).notNull(),
{ passwordHash: varchar("password_hash", { length: 255 }).notNull(),
fieldId: bigint('field_id', { mode: 'number' }).autoincrement().notNull(), passwordModified: datetime("password_modified", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
version: int().default(0).notNull(), passwordExpire: datetime("password_expire", { mode: 'string'}),
fieldName: varchar('field_name', { length: 255 }).notNull(), },
fieldKey: varchar('field_key', { length: 255 }).notNull(), (table) => [
fieldType: tinyint('field_type').notNull(), primaryKey({ columns: [table.userId], name: "sys_user_auth_user_id"}),
dictModule: int('dict_module'), ]);
isRequired: tinyint('is_required').default(0).notNull(),
limit: int(),
description: varchar({ length: 255 }),
defaultValue: varchar('default_value', { length: 255 }),
defaultOptions: varchar('default_options', { length: 255 }),
sort: int().default(0).notNull(),
status: int().notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(),
createdAt: datetime('created_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.fieldId], name: 'sys_user_field_definition_field_id' }),
unique('uniq_field_name').on(table.fieldName),
unique('uniq_field_key').on(table.fieldKey),
],
);
export const sysUserFieldValue = mysqlTable( export const sysUserAuthHistory = mysqlTable("sys_user_auth_history", {
'sys_user_field_value', id: bigint({ mode: "number" }).autoincrement().notNull(),
{ userId: bigint("user_id", { mode: "number" }).notNull(),
id: bigint({ mode: 'number' }).autoincrement().notNull(), passwordHash: varchar("password_hash", { length: 255 }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), modifiedAt: datetime("modified_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
fieldId: int('field_id').notNull(), },
value: varchar({ length: 4096 }), (table) => [
createdBy: bigint('created_by', { mode: 'number' }).notNull(), primaryKey({ columns: [table.id], name: "sys_user_auth_history_id"}),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), ]);
createdAt: datetime('created_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.id], name: 'sys_user_field_value_id' }),
unique('uniq_user_field').on(table.userId, table.fieldId),
],
);
export const sysUserProfile = mysqlTable( export const sysUserFieldDefinition = mysqlTable("sys_user_field_definition", {
'sys_user_profile', fieldId: bigint("field_id", { mode: "number" }).notNull(),
{ version: int().default(0).notNull(),
id: bigint({ mode: 'number' }).autoincrement().notNull(), fieldName: varchar("field_name", { length: 255 }).notNull(),
version: int().default(0).notNull(), fieldKey: varchar("field_key", { length: 255 }).notNull(),
name: varchar({ length: 32 }).notNull(), fieldType: tinyint("field_type").notNull(),
profileKey: varchar('profile_key', { length: 255 }).notNull(), dictModule: int("dict_module"),
description: varchar({ length: 255 }), isRequired: tinyint("is_required").default(0).notNull(),
content: varchar({ length: 255 }), limit: int(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), description: varchar({ length: 255 }),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), defaultValue: varchar("default_value", { length: 255 }),
createdAt: datetime('created_at', { mode: 'string' }) defaultOptions: varchar("default_options", { length: 255 }),
.default(sql`(CURRENT_TIMESTAMP)`) sort: int().default(0).notNull(),
.notNull(), status: int().notNull(),
updatedAt: datetime('updated_at', { mode: 'string' }) createdBy: bigint("created_by", { mode: "number" }).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) updatedBy: bigint("updated_by", { mode: "number" }),
.notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
}, updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
table => [ },
primaryKey({ columns: [table.id], name: 'sys_user_profile_id' }), (table) => [
unique('uniq_name').on(table.name), primaryKey({ columns: [table.fieldId], name: "sys_user_field_definition_field_id"}),
unique('uniq_profile_key').on(table.profileKey), unique("uniq_field_name").on(table.fieldName),
], unique("uniq_field_key").on(table.fieldKey),
); ]);
export const sysUserFieldValue = mysqlTable("sys_user_field_value", {
id: bigint({ mode: "number" }).autoincrement().notNull(),
userId: bigint("user_id", { mode: "number" }).notNull(),
fieldId: int("field_id").notNull(),
value: varchar({ length: 4096 }),
createdBy: bigint("created_by", { mode: "number" }).notNull(),
updatedBy: bigint("updated_by", { mode: "number" }),
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
},
(table) => [
primaryKey({ columns: [table.id], name: "sys_user_field_value_id"}),
unique("uniq_user_field").on(table.userId, table.fieldId),
]);

View File

@ -58,7 +58,21 @@ const baseConfig = {
database: process.env.REDIS_DB || 9, database: process.env.REDIS_DB || 9,
username: 'default', username: 'default',
password: process.env.REDIS_PASSWORD || 'Hxl1314521', password: process.env.REDIS_PASSWORD || 'Hxl1314521',
} },
jwt: {
secret: process.env.JWT_SECRET || 'Hxl1314521',
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES || '20m',
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES || '14d',
whitelist: [
'/user/login',
'/user/register',
'/user/refreshToken',
'/',
// '/module',
'/docs*',
'/docs/json'
],
},
}; };
// 环境特定配置 // 环境特定配置

View File

@ -12,6 +12,8 @@
2. 用户登录 2. 用户登录
2.1 刷新Token
3. 用户登出 3. 用户登出
4. 用户注销 4. 用户注销

141
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@fastify/jwt": "^9.1.0",
"@fastify/sensible": "^6.0.3", "@fastify/sensible": "^6.0.3",
"@fastify/swagger": "^9.4.2", "@fastify/swagger": "^9.4.2",
"@fastify/swagger-ui": "^5.2.2", "@fastify/swagger-ui": "^5.2.2",
@ -1043,6 +1044,29 @@
"integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==", "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@fastify/jwt": {
"version": "9.1.0",
"resolved": "https://registry.npmmirror.com/@fastify/jwt/-/jwt-9.1.0.tgz",
"integrity": "sha512-CiGHCnS5cPMdb004c70sUWhQTfzrJHAeTywt7nVw6dAiI0z1o4WRvU94xfijhkaId4bIxTCOjFgn4sU+Gvk43w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT",
"dependencies": {
"@fastify/error": "^4.0.0",
"@lukeed/ms": "^2.0.2",
"fast-jwt": "^5.0.0",
"fastify-plugin": "^5.0.0",
"steed": "^1.1.3"
}
},
"node_modules/@fastify/merge-json-schemas": { "node_modules/@fastify/merge-json-schemas": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmmirror.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", "resolved": "https://registry.npmmirror.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz",
@ -1672,6 +1696,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/asn1.js": {
"version": "5.4.1",
"resolved": "https://registry.npmmirror.com/asn1.js/-/asn1.js-5.4.1.tgz",
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"license": "MIT",
"dependencies": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"safer-buffer": "^2.1.0"
}
},
"node_modules/async-function": { "node_modules/async-function": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz",
@ -1759,6 +1795,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/bn.js": {
"version": "4.12.1",
"resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.1.tgz",
"integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
"license": "MIT"
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -2390,6 +2432,15 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz",
@ -3016,6 +3067,21 @@
"rfdc": "^1.2.0" "rfdc": "^1.2.0"
} }
}, },
"node_modules/fast-jwt": {
"version": "5.0.6",
"resolved": "https://registry.npmmirror.com/fast-jwt/-/fast-jwt-5.0.6.tgz",
"integrity": "sha512-LPE7OCGUl11q3ZgW681cEU2d0d2JZ37hhJAmetCgNyW8waVaJVZXhyFF6U2so1Iim58Yc7pfxJe2P7MNetQH2g==",
"license": "Apache-2.0",
"dependencies": {
"@lukeed/ms": "^2.0.2",
"asn1.js": "^5.4.1",
"ecdsa-sig-formatter": "^1.0.11",
"mnemonist": "^0.40.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/fast-levenshtein": { "node_modules/fast-levenshtein": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
@ -3063,6 +3129,18 @@
], ],
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/fastfall": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/fastfall/-/fastfall-1.5.1.tgz",
"integrity": "sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==",
"license": "MIT",
"dependencies": {
"reusify": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fastify": { "node_modules/fastify": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmmirror.com/fastify/-/fastify-5.2.1.tgz", "resolved": "https://registry.npmmirror.com/fastify/-/fastify-5.2.1.tgz",
@ -3102,6 +3180,16 @@
"integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fastparallel": {
"version": "2.4.1",
"resolved": "https://registry.npmmirror.com/fastparallel/-/fastparallel-2.4.1.tgz",
"integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==",
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4",
"xtend": "^4.0.2"
}
},
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.19.1", "version": "1.19.1",
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz",
@ -3111,6 +3199,16 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fastseries": {
"version": "1.7.2",
"resolved": "https://registry.npmmirror.com/fastseries/-/fastseries-1.7.2.tgz",
"integrity": "sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==",
"license": "ISC",
"dependencies": {
"reusify": "^1.0.0",
"xtend": "^4.0.0"
}
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@ -4549,6 +4647,12 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
@ -4616,6 +4720,15 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/mnemonist": {
"version": "0.40.3",
"resolved": "https://registry.npmmirror.com/mnemonist/-/mnemonist-0.40.3.tgz",
"integrity": "sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==",
"license": "MIT",
"dependencies": {
"obliterator": "^2.0.4"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
@ -4860,6 +4973,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/obliterator": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/obliterator/-/obliterator-2.0.5.tgz",
"integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==",
"license": "MIT"
},
"node_modules/on-exit-leak-free": { "node_modules/on-exit-leak-free": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
@ -5889,6 +6008,19 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/steed": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/steed/-/steed-1.1.3.tgz",
"integrity": "sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==",
"license": "MIT",
"dependencies": {
"fastfall": "^1.5.0",
"fastparallel": "^2.2.0",
"fastq": "^1.3.0",
"fastseries": "^1.7.0",
"reusify": "^1.0.0"
}
},
"node_modules/stream-shift": { "node_modules/stream-shift": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz",
@ -6631,6 +6763,15 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",

View File

@ -35,6 +35,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@fastify/jwt": "^9.1.0",
"@fastify/sensible": "^6.0.3", "@fastify/sensible": "^6.0.3",
"@fastify/swagger": "^9.4.2", "@fastify/swagger": "^9.4.2",
"@fastify/swagger-ui": "^5.2.2", "@fastify/swagger-ui": "^5.2.2",

View File

@ -1,341 +1,265 @@
import {
mysqlTable,
mysqlSchema,
primaryKey,
unique,
int,
tinyint,
varchar,
datetime,
} from 'drizzle-orm/mysql-core';
import { sql } from 'drizzle-orm';
import { bigintString } from './customType.js'; import { bigintString } from './customType.js';
const bigint = bigintString; const bigint = bigintString;
export const sysDict = mysqlTable( import { mysqlTable, primaryKey, unique, int, tinyint, varchar, datetime } from "drizzle-orm/mysql-core"
'sys_dict', import { sql } from "drizzle-orm"
{
id: bigint({ mode: 'number' }).notNull(),
version: int().default(0).notNull(),
pid: bigint({ mode: 'number' }).notNull(),
module: tinyint(),
dictKey: varchar('dict_key', { length: 255 }),
value: varchar({ length: 255 }),
description: varchar({ length: 255 }),
sort: int().default(0).notNull(),
status: int().notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(),
createdAt: datetime('created_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.id], name: 'sys_dict_id' }),
unique('uniq_dict_key').on(table.dictKey, table.pid),
],
);
export const sysOrganization = mysqlTable( export const sysDict = mysqlTable("sys_dict", {
'sys_organization', id: bigint({ mode: "number" }).notNull(),
{ version: int().default(0).notNull(),
orgId: bigint('org_id', { mode: 'number' }).notNull(), pid: bigint({ mode: "number" }).notNull(),
pid: bigint({ mode: 'number' }).notNull(), module: tinyint(),
orgName: varchar('org_name', { length: 255 }), dictKey: varchar("dict_key", { length: 255 }),
orgCode: varchar('org_code', { length: 128 }), value: varchar({ length: 255 }),
orgType: int('org_type').notNull(), description: varchar({ length: 255 }),
description: varchar({ length: 255 }), sort: int().default(0).notNull(),
sort: int().default(0).notNull(), status: int().notNull(),
status: int().notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
createdAt: datetime('created_at', { mode: 'string' }) updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) },
.notNull(), (table) => [
updatedAt: datetime('updated_at', { mode: 'string' }) primaryKey({ columns: [table.id], name: "sys_dict_id"}),
.default(sql`(CURRENT_TIMESTAMP)`) unique("uniq_dict_key").on(table.dictKey, table.pid),
.notNull(), ]);
},
table => [
primaryKey({ columns: [table.orgId], name: 'sys_organization_org_id' }),
unique('uniq_org_code').on(table.orgCode, table.pid),
unique('uniq_org_name').on(table.orgName, table.pid),
],
);
export const sysOrganizationManager = mysqlTable( export const sysModule = mysqlTable("sys_module", {
'sys_organization_manager', id: int().autoincrement().notNull(),
{ version: int().default(0).notNull(),
id: bigint({ mode: 'number' }).notNull(), name: varchar({ length: 32 }).notNull(),
version: int().default(0).notNull(), moduleKey: varchar("module_key", { length: 255 }).notNull(),
orgId: bigint('org_id', { mode: 'number' }).notNull(), description: varchar({ length: 255 }),
userId: bigint('user_id', { mode: 'number' }).notNull(), sort: int().default(0).notNull(),
rank: int().notNull(), status: int().notNull(),
description: varchar({ length: 255 }), createdBy: bigint("created_by", { mode: "number" }).notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
createdAt: datetime('created_at', { mode: 'string' }) updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) },
.notNull(), (table) => [
updatedAt: datetime('updated_at', { mode: 'string' }) primaryKey({ columns: [table.id], name: "sys_module_id"}),
.default(sql`(CURRENT_TIMESTAMP)`) unique("uniq_name").on(table.name),
.notNull(), unique("uniq_module_key").on(table.moduleKey),
}, ]);
table => [
primaryKey({ columns: [table.id], name: 'sys_organization_manager_id' }),
unique('uniq_org_user').on(table.orgId, table.userId),
],
);
export const sysPermission = mysqlTable( export const sysOrganization = mysqlTable("sys_organization", {
'sys_permission', orgId: bigint("org_id", { mode: "number" }).notNull(),
{ pid: bigint({ mode: "number" }).notNull(),
permId: bigint('perm_id', { mode: 'number' }).notNull(), orgName: varchar("org_name", { length: 255 }),
pid: bigint({ mode: 'number' }).notNull(), orgCode: varchar("org_code", { length: 128 }),
permName: varchar('perm_name', { length: 255 }).notNull(), orgType: int("org_type").notNull(),
permKey: varchar('perm_key', { length: 255 }).notNull(), description: varchar({ length: 255 }),
url: varchar({ length: 255 }), sort: int().default(0).notNull(),
avatarUrl: varchar('avatar_url', { length: 255 }), status: int().notNull(),
description: varchar({ length: 255 }), createdBy: bigint("created_by", { mode: "number" }).notNull(),
permType: int('perm_type').notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
isVisible: int('is_visible').default(0).notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
version: int().default(0).notNull(), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
sort: int().default(0).notNull(), },
status: int().notNull(), (table) => [
createdBy: bigint('created_by', { mode: 'number' }).notNull(), primaryKey({ columns: [table.orgId], name: "sys_organization_org_id"}),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), unique("uniq_org_code").on(table.orgCode, table.pid),
createdAt: datetime('created_at', { mode: 'string' }) unique("uniq_org_name").on(table.orgName, table.pid),
.default(sql`(CURRENT_TIMESTAMP)`) ]);
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.permId], name: 'sys_permission_perm_id' }),
unique('uniq_pid_name').on(table.permName, table.pid),
unique('uniq_perm_key').on(table.permKey),
],
);
export const sysReRolePermission = mysqlTable( export const sysOrganizationManager = mysqlTable("sys_organization_manager", {
'sys_re_role_permission', id: bigint({ mode: "number" }).notNull(),
{ version: int().default(0).notNull(),
id: bigint({ mode: 'number' }).notNull(), orgId: bigint("org_id", { mode: "number" }).notNull(),
roleId: bigint('role_id', { mode: 'number' }).notNull(), userId: bigint("user_id", { mode: "number" }).notNull(),
permId: bigint('perm_id', { mode: 'number' }).notNull(), rank: int().notNull(),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), status: int().notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), description: varchar({ length: 255 }),
createdAt: datetime('created_at', { mode: 'string' }) createdBy: bigint("created_by", { mode: "number" }).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) updatedBy: bigint("updated_by", { mode: "number" }),
.notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedAt: datetime('updated_at', { mode: 'string' }) updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) },
.notNull(), (table) => [
}, primaryKey({ columns: [table.id], name: "sys_organization_manager_id"}),
table => [ unique("uniq_org_user").on(table.orgId, table.userId),
primaryKey({ columns: [table.id], name: 'sys_re_role_permission_id' }), ]);
unique('uniq_perm_role').on(table.roleId, table.permId),
],
);
export const sysReUserOrganization = mysqlTable( export const sysPermission = mysqlTable("sys_permission", {
'sys_re_user_organization', permId: bigint("perm_id", { mode: "number" }).notNull(),
{ pid: bigint({ mode: "number" }).notNull(),
id: bigint({ mode: 'number' }).notNull(), permName: varchar("perm_name", { length: 255 }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), permKey: varchar("perm_key", { length: 255 }).notNull(),
orgId: bigint('org_id', { mode: 'number' }).notNull(), url: varchar({ length: 255 }),
version: int().default(0).notNull(), avatarUrl: varchar("avatar_url", { length: 255 }),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), description: varchar({ length: 255 }),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), permType: int("perm_type").notNull(),
createdAt: datetime('created_at', { mode: 'string' }) isVisible: int("is_visible").default(0).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) version: int().default(0).notNull(),
.notNull(), sort: int().default(0).notNull(),
updatedAt: datetime('updated_at', { mode: 'string' }) status: int().notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) createdBy: bigint("created_by", { mode: "number" }).notNull(),
.notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
}, createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
table => [ updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
primaryKey({ columns: [table.id], name: 'sys_re_user_organization_id' }), },
unique('uniq_user_org').on(table.userId, table.orgId), (table) => [
], primaryKey({ columns: [table.permId], name: "sys_permission_perm_id"}),
); unique("uniq_pid_name").on(table.permName, table.pid),
unique("uniq_perm_key").on(table.permKey),
]);
export const sysReUserRole = mysqlTable( export const sysProfile = mysqlTable("sys_profile", {
'sys_re_user_role', id: bigint({ mode: "number" }).notNull(),
{ version: int().default(0).notNull(),
id: bigint({ mode: 'number' }).notNull(), name: varchar({ length: 32 }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), profileKey: varchar("profile_key", { length: 255 }).notNull(),
roleId: bigint('role_id', { mode: 'number' }).notNull(), description: varchar({ length: 255 }),
version: int().default(0).notNull(), content: varchar({ length: 255 }),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
createdAt: datetime('created_at', { mode: 'string' }) createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
.notNull(), },
updatedAt: datetime('updated_at', { mode: 'string' }) (table) => [
.default(sql`(CURRENT_TIMESTAMP)`) primaryKey({ columns: [table.id], name: "sys_profile_id"}),
.notNull(), unique("uniq_name").on(table.name),
}, unique("uniq_profile_key").on(table.profileKey),
table => [ ]);
primaryKey({ columns: [table.id], name: 'sys_re_user_role_id' }),
unique('uniq_user_role').on(table.userId, table.roleId),
],
);
export const sysRole = mysqlTable( export const sysReRolePermission = mysqlTable("sys_re_role_permission", {
'sys_role', id: bigint({ mode: "number" }).notNull(),
{ roleId: bigint("role_id", { mode: "number" }).notNull(),
roleId: bigint('role_id', { mode: 'number' }).notNull(), permId: bigint("perm_id", { mode: "number" }).notNull(),
pid: bigint({ mode: 'number' }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
roleName: varchar('role_name', { length: 255 }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
roleKey: varchar('role_key', { length: 255 }).notNull(), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
description: varchar({ length: 255 }), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
status: int().notNull(), },
createdBy: bigint('created_by', { mode: 'number' }).notNull(), (table) => [
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), primaryKey({ columns: [table.id], name: "sys_re_role_permission_id"}),
createdAt: datetime('created_at', { mode: 'string' }) unique("uniq_perm_role").on(table.roleId, table.permId),
.default(sql`(CURRENT_TIMESTAMP)`) ]);
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.roleId], name: 'sys_role_role_id' }),
unique('uniq_role_pid').on(table.roleName, table.pid),
],
);
export const sysUser = mysqlTable( export const sysReUserOrganization = mysqlTable("sys_re_user_organization", {
'sys_user', id: bigint({ mode: "number" }).notNull(),
{ userId: bigint("user_id", { mode: "number" }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), orgId: bigint("org_id", { mode: "number" }).notNull(),
pid: bigint({ mode: 'number' }).notNull(), version: int().default(0).notNull(),
username: varchar({ length: 255 }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
email: varchar({ length: 255 }).notNull(), updatedBy: bigint("updated_by", { mode: "number" }),
phone: varchar({ length: 255 }), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
avatarUrl: varchar('avatar_url', { length: 255 }), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
userType: tinyint('user_type'), },
status: tinyint().default(0).notNull(), (table) => [
createdBy: bigint('created_by', { mode: 'number' }).notNull(), primaryKey({ columns: [table.id], name: "sys_re_user_organization_id"}),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), unique("uniq_user_org").on(table.userId, table.orgId),
createdAt: datetime('created_at', { mode: 'string' }) ]);
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.userId], name: 'sys_user_user_id' }),
unique('uniq_username').on(table.username),
unique('uniq_email').on(table.email),
],
);
export const sysUserAuth = mysqlTable( export const sysReUserRole = mysqlTable("sys_re_user_role", {
'sys_user_auth', id: bigint({ mode: "number" }).notNull(),
{ userId: bigint("user_id", { mode: "number" }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), roleId: bigint("role_id", { mode: "number" }).notNull(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(), version: int().default(0).notNull(),
passwordModified: datetime('password_modified', { mode: 'string' }).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
passwordExpire: datetime('password_expire', { mode: 'string' }), updatedBy: bigint("updated_by", { mode: "number" }),
}, createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
table => [primaryKey({ columns: [table.userId], name: 'sys_user_auth_user_id' })], updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
); },
(table) => [
primaryKey({ columns: [table.id], name: "sys_re_user_role_id"}),
unique("uniq_user_role").on(table.userId, table.roleId),
]);
export const sysUserAuthHistory = mysqlTable( export const sysRole = mysqlTable("sys_role", {
'sys_user_auth_history', roleId: bigint("role_id", { mode: "number" }).notNull(),
{ pid: bigint({ mode: "number" }).notNull(),
id: bigint({ mode: 'number' }).notNull(), roleName: varchar("role_name", { length: 255 }).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), roleKey: varchar("role_key", { length: 255 }).notNull(),
passwordHash: varchar('password_hash', { length: 255 }).notNull(), description: varchar({ length: 255 }),
modifiedAt: varchar('modified_at', { length: 255 }).notNull(), status: int().notNull(),
}, createdBy: bigint("created_by", { mode: "number" }).notNull(),
table => [primaryKey({ columns: [table.id], name: 'sys_user_auth_history_id' })], updatedBy: bigint("updated_by", { mode: "number" }),
); createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
},
(table) => [
primaryKey({ columns: [table.roleId], name: "sys_role_role_id"}),
unique("uniq_role_pid").on(table.roleName, table.pid),
]);
export const sysUserFieldDefinition = mysqlTable( export const sysUser = mysqlTable("sys_user", {
'sys_user_field_definition', userId: bigint("user_id", { mode: "number" }).notNull(),
{ pid: bigint({ mode: "number" }).notNull(),
fieldId: bigint('field_id', { mode: 'number' }).notNull(), username: varchar({ length: 255 }).notNull(),
version: int().default(0).notNull(), email: varchar({ length: 255 }).notNull(),
fieldName: varchar('field_name', { length: 255 }).notNull(), phone: varchar({ length: 255 }),
fieldKey: varchar('field_key', { length: 255 }).notNull(), avatarUrl: varchar("avatar_url", { length: 255 }),
fieldType: tinyint('field_type').notNull(), userType: tinyint("user_type"),
dictModule: int('dict_module'), status: tinyint().default(0).notNull(),
isRequired: tinyint('is_required').default(0).notNull(), createdBy: bigint("created_by", { mode: "number" }).notNull(),
limit: int(), updatedBy: bigint("updated_by", { mode: "number" }),
description: varchar({ length: 255 }), createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
defaultValue: varchar('default_value', { length: 255 }), updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
defaultOptions: varchar('default_options', { length: 255 }), },
sort: int().default(0).notNull(), (table) => [
status: int().notNull(), primaryKey({ columns: [table.userId], name: "sys_user_user_id"}),
createdBy: bigint('created_by', { mode: 'number' }).notNull(), unique("uniq_username").on(table.username),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), unique("uniq_email").on(table.email),
createdAt: datetime('created_at', { mode: 'string' }) ]);
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.fieldId], name: 'sys_user_field_definition_field_id' }),
unique('uniq_field_name').on(table.fieldName),
unique('uniq_field_key').on(table.fieldKey),
],
);
export const sysUserFieldValue = mysqlTable( export const sysUserAuth = mysqlTable("sys_user_auth", {
'sys_user_field_value', userId: bigint("user_id", { mode: "number" }).notNull(),
{ passwordHash: varchar("password_hash", { length: 255 }).notNull(),
id: bigint({ mode: 'number' }).notNull(), passwordModified: datetime("password_modified", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(), passwordExpire: datetime("password_expire", { mode: 'string'}),
fieldId: int('field_id').notNull(), },
value: varchar({ length: 4096 }), (table) => [
createdBy: bigint('created_by', { mode: 'number' }).notNull(), primaryKey({ columns: [table.userId], name: "sys_user_auth_user_id"}),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), ]);
createdAt: datetime('created_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
updatedAt: datetime('updated_at', { mode: 'string' })
.default(sql`(CURRENT_TIMESTAMP)`)
.notNull(),
},
table => [
primaryKey({ columns: [table.id], name: 'sys_user_field_value_id' }),
unique('uniq_user_field').on(table.userId, table.fieldId),
],
);
export const sysUserProfile = mysqlTable( export const sysUserAuthHistory = mysqlTable("sys_user_auth_history", {
'sys_user_profile', id: bigint({ mode: "number" }).notNull(),
{ userId: bigint("user_id", { mode: "number" }).notNull(),
id: bigint({ mode: 'number' }).notNull(), passwordHash: varchar("password_hash", { length: 255 }).notNull(),
version: int().default(0).notNull(), modifiedAt: datetime("modified_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
name: varchar({ length: 32 }).notNull(), },
profileKey: varchar('profile_key', { length: 255 }).notNull(), (table) => [
description: varchar({ length: 255 }), primaryKey({ columns: [table.id], name: "sys_user_auth_history_id"}),
content: varchar({ length: 255 }), ]);
createdBy: bigint('created_by', { mode: 'number' }).notNull(),
updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), export const sysUserFieldDefinition = mysqlTable("sys_user_field_definition", {
createdAt: datetime('created_at', { mode: 'string' }) fieldId: bigint("field_id", { mode: "number" }).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) version: int().default(0).notNull(),
.notNull(), fieldName: varchar("field_name", { length: 255 }).notNull(),
updatedAt: datetime('updated_at', { mode: 'string' }) fieldKey: varchar("field_key", { length: 255 }).notNull(),
.default(sql`(CURRENT_TIMESTAMP)`) fieldType: tinyint("field_type").notNull(),
.notNull(), dictModule: int("dict_module"),
}, isRequired: tinyint("is_required").default(0).notNull(),
table => [ limit: int(),
primaryKey({ columns: [table.id], name: 'sys_user_profile_id' }), description: varchar({ length: 255 }),
unique('uniq_name').on(table.name), defaultValue: varchar("default_value", { length: 255 }),
unique('uniq_profile_key').on(table.profileKey), defaultOptions: varchar("default_options", { length: 255 }),
], sort: int().default(0).notNull(),
); status: int().notNull(),
createdBy: bigint("created_by", { mode: "number" }).notNull(),
updatedBy: bigint("updated_by", { mode: "number" }),
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
},
(table) => [
primaryKey({ columns: [table.fieldId], name: "sys_user_field_definition_field_id"}),
unique("uniq_field_name").on(table.fieldName),
unique("uniq_field_key").on(table.fieldKey),
]);
export const sysUserFieldValue = mysqlTable("sys_user_field_value", {
id: bigint({ mode: "number" }).notNull(),
userId: bigint("user_id", { mode: "number" }).notNull(),
fieldId: int("field_id").notNull(),
value: varchar({ length: 4096 }),
createdBy: bigint("created_by", { mode: "number" }).notNull(),
updatedBy: bigint("updated_by", { mode: "number" }),
createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
},
(table) => [
primaryKey({ columns: [table.id], name: "sys_user_field_value_id"}),
unique("uniq_user_field").on(table.userId, table.fieldId),
]);

View File

@ -10,6 +10,10 @@ async function constData(fastify, options) {
DISTRIBUTED_LOCK_PREFIX: { DISTRIBUTED_LOCK_PREFIX: {
// 注册用户 // 注册用户
REGISTER_USER: 'REGISTER_USER:', REGISTER_USER: 'REGISTER_USER:',
// 新增模块
CREATE_MODULE: 'CREATE_MODULE:',
// 编辑模块
UPDATE_MODULE: 'UPDATE_MODULE:',
} }
} }

View File

@ -1,11 +1,15 @@
import { hash } from 'bcrypt'; import { hash, compare } from 'bcrypt';
import fastifyPlugin from 'fastify-plugin'; import fastifyPlugin from 'fastify-plugin';
// 加密 // 加密
async function encryption(fastify, options) { async function encryption(fastify, options) {
// 注册swagger插件新版配置方式 // 注册swagger插件新版配置方式
fastify.log.warn('Register Encryption Plugin!'); fastify.log.warn('Register Encryption Plugin!');
// 初始化时注册插件 // 注册hash插件新版配置方式
// 相同密码不同salt生成的哈希完全不同
fastify.decorate('hash', (str, salt = 10) => hash(str, salt)); fastify.decorate('hash', (str, salt = 10) => hash(str, salt));
// 验证时会自动从哈希中提取salt
fastify.decorate('compareHash', (plainText, hashedPassword) => compare(plainText, hashedPassword));
//
fastify.log.warn('Register Encryption complete!'); fastify.log.warn('Register Encryption complete!');
} }

View File

@ -10,7 +10,7 @@ function errorHandler(fastify) {
fastify.setErrorHandler((error, request, reply) => { fastify.setErrorHandler((error, request, reply) => {
// 记录原始错误 // 记录原始错误
// fastify.log.error(`[ErrorHandler] ${error.message}`, error); fastify.log.trace(error);
console.log('AAAA1validation', error.validation); console.log('AAAA1validation', error.validation);
console.log('AAAA2statusCode', error.statusCode); console.log('AAAA2statusCode', error.statusCode);
@ -22,7 +22,7 @@ function errorHandler(fastify) {
// 没有请求体 // 没有请求体
const bodyError = error.validation.find( const bodyError = error.validation.find(
e => e =>
e.keyword === 'type' || e.keyword === 'type' && e.validationContext == 'body' ||
(e.keyword === 'required' && e.validationContext == 'body'), (e.keyword === 'required' && e.validationContext == 'body'),
); );

View File

@ -6,6 +6,7 @@ import encryption from '#plugins/encryption/index';
import snowflake from '#plugins/snowflake/index'; import snowflake from '#plugins/snowflake/index';
import redis from '#plugins/redis/index'; import redis from '#plugins/redis/index';
import conseData from '#plugins/const/index'; import conseData from '#plugins/const/index';
import jwt from '#plugins/jwt/index';
async function plugin(fastify, opts) { async function plugin(fastify, opts) {
fastify.log.warn('Register Global Plugin!'); fastify.log.warn('Register Global Plugin!');
@ -26,6 +27,8 @@ async function plugin(fastify, opts) {
// 注册错误处理工具 // 注册错误处理工具
await fastify.register(import('@fastify/sensible')); await fastify.register(import('@fastify/sensible'));
fastify.register(errorHandler); fastify.register(errorHandler);
// jwt
await fastify.register(jwt);
fastify.log.warn('Register Global Plugin complete!'); fastify.log.warn('Register Global Plugin complete!');
} }

48
src/plugins/jwt/index.js Normal file
View File

@ -0,0 +1,48 @@
import fastifyPlugin from 'fastify-plugin';
async function jwt(fastify, options) {
fastify.log.warn('Register JWT Plugin!');
// jwt
fastify.register(import('@fastify/jwt'), {
secret: fastify.config.jwt.secret,
verify: {
extractToken: (req) => {
// 仅从Authorization头获取token
return req.headers.authorization?.split(' ')[1];
}
}
});
// jwtWhitelistPatterns
const jwtWhitelistPatterns = fastify.config.jwt.whitelist.map(item => {
if (item.includes('*')) {
const pattern = item.replace(/\*/g, '.*');
return new RegExp(`^${pattern}$`);
}
return item;
})
// 全局验证钩子(白名单机制)
fastify.addHook('onRequest', async (req, reply) => {
const path = req.routeOptions.url;
// 检查当前路径是否在白名单中
if (jwtWhitelistPatterns?.some(pattern => {
return pattern instanceof RegExp ? pattern.test(path) : pattern === path;
})) {
return;
}
try {
await req.jwtVerify();
} catch (err) {
reply.code(401).send({
code: 401,
error: '身份验证失败'
});
}
});
// 读取数据库基本信息
fastify.log.warn('Register JWT Complete!');
}
export default fastifyPlugin(jwt);

View File

@ -11,6 +11,18 @@ async function swagger(fastify, options) {
version: fastify.config.version, version: fastify.config.version,
description: fastify.config.description, description: fastify.config.description,
}, },
// 新增全局安全配置
components: {
securitySchemes: {
apiKey: {
type: 'apiKey',
name: 'x-api-key',
in: 'header'
}
}
},
// 应用安全到所有路由
security: [{ apiKey: [] }]
}, },
// staticCSP: true, // 启用安全策略 // staticCSP: true, // 启用安全策略
exposeRoute: true, // 自动暴露路由 exposeRoute: true, // 自动暴露路由

View File

@ -1,6 +1,7 @@
import { testSchema } from '#src/schema/test.schema'; import { testSchema } from '#src/schema/test.schema';
import { testService } from '#src/services/test.service'; import { testService } from '#src/services/test.service';
import userRoute from '#routes/user.route'; import userRoute from '#routes/user.route';
import moduleRoute from '#routes/module.route';
export default async function routes(fastify, options) { export default async function routes(fastify, options) {
// 定义一个GET请求的路由路径为根路径 // 定义一个GET请求的路由路径为根路径
@ -10,8 +11,13 @@ export default async function routes(fastify, options) {
}); });
fastify.get('/test', testSchema, testService); fastify.get('/test', testSchema, testService);
// 注册子路由 // 注册子路由 -------- 用户
fastify.register(userRoute, { prefix: '/user' }); fastify.register(userRoute, { prefix: '/user' });
// 注册子路由 -------- 模块
fastify.register(moduleRoute, { prefix: '/module' });
// 注册子路由 -------- 权限
// 注册子路由 -------- 角色
fastify.route({ fastify.route({
method: 'POST', method: 'POST',

View File

@ -0,0 +1,18 @@
import { moduleListSchema, moduleCreateSchema, moduleUpdateSchema } from '#schema/module.schema';
import { moduleListService, moduleCreateService, moduleUpdateService } from '#services/module/module.service';
export default async function moduleRoute(fastify, options) {
// 1. 获取系统模块列表 23-04/23
fastify.get('/', { schema: moduleListSchema }, moduleListService);
// 2. 新增系统模块
fastify.post('/', { schema: moduleCreateSchema }, moduleCreateService);
// 3. 编辑系统模块
fastify.patch('/:id', { schema: moduleUpdateSchema }, moduleUpdateService);
// 4. 删除系统模块
// 5. 启用禁用模块
// 6. 排序
}

View File

@ -1,55 +1,11 @@
import { userDefaultSchema, userRegisterSchema } from '#src/schema/user.schema'; import { userDefaultSchema, userRegisterSchema, userLoginSchema, userRefreshTokenSchema } from '#src/schema/user.schema';
import { userDefaultService } from '#src/services/user.service'; import { userRegisterService, userLoginService, refreshTokenService } from '#src/services/user/user.service';
import { userRegisterService } from '#src/services/user/user.service';
export default async function userRoute(fastify, options) { export default async function userRoute(fastify, options) {
// 新用户注册 // 1. 新用户注册 25-03/25
fastify.post('/register', { schema: userRegisterSchema }, userRegisterService); fastify.post('/register', { schema: userRegisterSchema }, userRegisterService);
// 2. 用户登录 25-03/26
fastify.get('/', userDefaultSchema, userDefaultService); fastify.post('/login', { schema: userLoginSchema }, userLoginService);
/** // 3. 刷新token 25-03/27
* fastify.post('/refreshToken', { schema: userRefreshTokenSchema }, refreshTokenService);
* 个人接口
*
*/
// 注册
// 登录
fastify.post('/login', userDefaultSchema, userDefaultService);
// 退出登录
fastify.post('/logout', userDefaultSchema, userDefaultService);
// 修改密码
fastify.post('/changePassword', userDefaultSchema, userDefaultService);
// 获取当前用户信息
fastify.get('/getUserInfo', userDefaultSchema, userDefaultService);
// 修改当前用户信息
fastify.post('/updateUserInfo', userDefaultSchema, userDefaultService);
/**
*
* 管理接口
*
*/
// 获取用户列表
// 获取用户详细信息
// 创建用户
// 更新用户信息
// 批量删除用户
// 批量重置用户密码
// 批量启用用户
// 批量禁用用户
/**
*
* 拓展字段
*
*/
// 获取拓展字段列表
} }

View File

@ -1,17 +1,18 @@
import { isTrim } from '#utils/ajv/method'; import { isLowerCase, isTrim } from '#utils/ajv/method';
import { is } from 'drizzle-orm'; import { is } from 'drizzle-orm';
// 原子schema定义 // 原子schema定义
export const username = { export const username = {
type: 'string', type: 'string',
minLength: 4, minLength: 4,
maxLength: 20, maxLength: 32,
pattern: '^[a-z][a-z0-9_]*$', pattern: '^[a-z][a-z0-9_]*$',
isLowerCase: true, isLowerCase: true,
isTrim: true, isTrim: true,
description: '用户名4-32位小写字母开头允许数字和下划线',
errorMessage: { errorMessage: {
minLength: '用户名至少需要4个字符', minLength: '用户名至少需要4个字符',
maxLength: '用户名不能超过20个字符', maxLength: '用户名不能超过32个字符',
pattern: '必须全部小写,且只能包含字母、数字和下划线,并以字母开头', pattern: '必须全部小写,且只能包含字母、数字和下划线,并以字母开头',
}, },
}; };
@ -20,6 +21,7 @@ export const email = {
type: 'string', type: 'string',
format: 'email', format: 'email',
maxLength: 128, maxLength: 128,
description: '用户电子邮箱地址',
errorMessage: { errorMessage: {
format: '邮箱格式不正确', format: '邮箱格式不正确',
maxLength: '邮箱不能超过128个字符', maxLength: '邮箱不能超过128个字符',
@ -31,9 +33,102 @@ export const password = {
minLength: 8, minLength: 8,
maxLength: 128, maxLength: 128,
pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$', pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$',
description: '密码需包含大小写字母、数字和特殊符号8-128位',
errorMessage: { errorMessage: {
minLength: '密码长度至少8位', minLength: '密码长度至少8位',
maxLength: '密码不能超过128个字符', maxLength: '密码不能超过128个字符',
pattern: '必须包含至少一个小写字母、一个大写字母、一个数字和一个特殊字符', pattern: '必须包含至少一个小写字母、一个大写字母、一个数字和一个特殊字符',
}, },
}; };
export const page = {
type: 'integer',
nullable: true,
minimum: 1,
default: 1,
description: '页码从1开始',
errorMessage: {
type: '页码必须是整数',
minimum: '页码最小为1'
}
}
export const pageSize = {
type: 'integer',
nullable: true,
minimum: 1,
maximum: 100,
default: 10,
description: '每页条数',
errorMessage: {
type: '每页条数必须是整数',
minimum: '每页条数范围1-100',
maximum: '每页条数范围1-100'
}
}
export const sortOrder = {
type: 'string',
nullable: true,
enum: ['asc', 'desc'],
default: 'desc',
errorMessage: {
type: '排序方式必须是字符串',
enum: '排序方式只能是asc或desc'
}
}
export const sortBy = {
type: 'string',
nullable: true,
enum: ['createdAt', 'updatedAt', 'sort'],
default: 'sort',
description: '排序字段默认createdAt',
errorMessage: {
enum: '排序字段只能是createdAt/updatedAt/sort'
}
}
export const sort = {
type: 'integer',
minimum: 0,
maximum: 9999,
default: 0,
errorMessage: '排序值范围0-9999'
}
export const description = {
type: ['string', 'null'],
maxLength: 255,
default: null,
description: '描述信息'
}
export const module = {
name: {
type: 'string',
minLength: 2,
maxLength: 32,
description: '模块名称(唯一标识)',
errorMessage: {
minLength: '模块名称至少需要2个字符',
maxLength: '模块名称不能超过32个字符'
}
},
moduleKey: {
type: 'string',
maxLength: 255,
isLowerCase: true,
isTrim: true,
pattern: '^[a-z][a-z0-9_]*$',
description: '模块唯一标识Key英文小写',
errorMessage: {
maxLength: '模块名称不能超过255个字符',
pattern: '必须小写字母开头,只能包含字母、数字和下划线'
}
},
status: {
type: 'integer',
enum: [0, 1, null],
default: 0,
errorMessage: {
type: '状态值必须是整数',
enum: '状态值只能是0(正常)或1(禁用)'
}
}
}

View File

@ -0,0 +1,61 @@
export default {
'400': {
description: '请求参数错误',
type: 'object',
properties: {
code: { type: 'number', enum: [400] },
error: { type: 'string' },
details: {
type: 'array',
items: {
type: 'object',
properties: {
path: { type: 'string' },
message: { type: 'string' }
}
}
}
}
},
'401': {
description: '未授权访问',
type: 'object',
properties: {
code: { type: 'number', enum: [401] },
error: { type: 'string' }
}
},
'403': {
description: '权限不足',
type: 'object',
properties: {
code: { type: 'number', enum: [403] },
error: { type: 'string' }
}
},
'404': {
description: '找不到目标资源',
type: 'object',
properties: {
code: { type: 'number', enum: [404] },
error: { type: 'string' }
}
},
'409': {
description: '数据已存在',
type: 'object',
properties: {
code: { type: 'number', enum: [409] },
error: { type: 'string' }
},
required: ['code', 'error']
},
'500': {
description: '服务器内部错误',
type: 'object',
properties: {
code: { type: 'number', enum: [500] },
error: { type: 'string' }
}
}
};

234
src/schema/module.schema.js Normal file
View File

@ -0,0 +1,234 @@
import { page, pageSize, sortOrder, module, sortBy, description, sort } from "#schema/atomSchema";
import errorAtomSchema from "#schema/error.atomSchema";
// 系统模块分页查询
export const moduleListSchema = {
tags: ['系统管理'],
summary: '获取系统模块列表',
description: '支持分页和条件查询系统模块数据',
querystring: {
type: 'object',
properties: {
page: {
...page, // 保持原有校验规则
description: '页码从1开始不传则返回全量'
},
pageSize: {
...pageSize,
description: '每页条数(不传则返回全量)'
},
name: {
type: 'string',
nullable: true,
maxLength: 32,
description: '模块名称模糊查询',
errorMessage: {
maxLength: '模块名称不能超过32个字符'
}
},
moduleKey: {
type: 'string',
nullable: true,
maxLength: 255,
description: '模块Key精确查询',
errorMessage: {
maxLength: '模块Key不能超过255个字符'
}
},
status: {
type: 'integer',
nullable: true,
enum: [0, 1],
default: 0,
description: '状态过滤0-正常 1-禁用)',
errorMessage: {
type: '状态参数必须是整数',
enum: '状态值只能是0或1'
}
},
sortBy,
sortOrder,
}
},
response: {
200: {
type: 'object',
properties: {
code: { type: 'number', enum: [200] },
data: {
type: 'object',
properties: {
total: {
type: 'integer',
examples: [150],
description: '总记录数(仅分页模式返回)'
},
list: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer', examples: [1] },
version: { type: 'integer', examples: [0] },
name: { type: 'string', examples: ['用户管理模块'] },
moduleKey: { type: 'string', examples: ['user_management'] },
description: { type: ['string', 'null'], examples: ['用户权限管理模块'] },
sort: { type: 'integer', examples: [10] },
status: { type: 'integer', enum: [0, 1] },
createdBy: { type: 'string', examples: [1001] },
updatedBy: { type: ['string', 'null'], examples: [1002] },
createdAt: {
type: 'string',
format: 'date-time',
examples: ['2023-07-15T08:23:45.000Z']
},
updatedAt: {
type: 'string',
format: 'date-time',
examples: ['2023-07-16T09:34:12.000Z']
}
},
required: ['id', 'name', 'moduleKey', 'description']
}
}
},
required: ['list']
},
message: { type: 'string' }
}
},
400: errorAtomSchema['400'],
},
security: [{ apiKey: [] }]
};
// 系统模块创建
export const moduleCreateSchema = {
tags: ['系统管理'],
summary: '创建系统模块',
description: '创建新的系统功能模块',
body: {
type: 'object',
required: ['name', 'moduleKey'],
properties: {
name: module.name,
moduleKey: module.moduleKey,
description,
sort,
status: module.status
}
},
response: {
201: {
type: 'object',
properties: {
code: { type: 'number', enum: [201] },
data: {
type: 'object',
properties: {
id: { type: 'integer', examples: [1] },
version: { type: 'integer', examples: [0] },
name: { type: 'string', examples: ['用户管理模块'] },
moduleKey: { type: 'string', examples: ['user_management'] },
description: { type: ['string', 'null'], examples: ['用户权限管理模块'] },
sort: { type: 'integer', examples: [10] },
status: { type: 'integer', enum: [0, 1] },
createdBy: { type: 'string', examples: [1001] },
updatedBy: { type: ['string', 'null'], examples: [1002] },
createdAt: {
type: 'string',
format: 'date-time',
examples: ['2023-07-15T08:23:45.000Z']
},
updatedAt: {
type: 'string',
format: 'date-time',
examples: ['2023-07-16T09:34:12.000Z']
}
}
}
}
},
400: errorAtomSchema['400'],
409: errorAtomSchema['409']
},
security: [{ apiKey: [] }]
};
// 系统模块修改 schema
export const moduleUpdateSchema = {
tags: ['系统管理'],
summary: '修改系统模块',
description: '修改系统模块信息(支持部分字段更新)',
body: {
type: 'object',
properties: {
name: {
...module.name,
nullable: true // 允许传空值
},
moduleKey: {
...module.moduleKey,
nullable: true
},
description: {
...description,
nullable: true
},
sort: {
...sort,
default: null, // 允许传空值
nullable: true
},
status: {
...module.status,
default: null, // 允许传空值
nullable: true
}
},
anyOf: [ // 至少更新一个字段
{ required: ['name'] },
{ required: ['moduleKey'] },
{ required: ['description'] },
{ required: ['sort'] },
{ required: ['status'] }
]
},
response: {
200: {
type: 'object',
properties: {
code: { type: 'number', enum: [200] },
data: {
type: 'object',
properties: {
id: { type: 'integer', examples: [1] },
version: { type: 'integer', examples: [0] },
name: { type: 'string', examples: ['用户管理模块'] },
moduleKey: { type: 'string', examples: ['user_management'] },
description: { type: ['string', 'null'], examples: ['用户权限管理模块'] },
sort: { type: 'integer', examples: [10] },
status: { type: 'integer', enum: [0, 1] },
createdBy: { type: 'string', examples: [1001] },
updatedBy: { type: ['string', 'null'], examples: [1002] },
createdAt: {
type: 'string',
format: 'date-time',
examples: ['2023-07-15T08:23:45.000Z']
},
updatedAt: {
type: 'string',
format: 'date-time',
examples: ['2023-07-16T09:34:12.000Z']
}
},
},
message: { type:'string' }
}
},
400: errorAtomSchema['400'],
404: errorAtomSchema['404'],
409: errorAtomSchema['409']
},
security: [{ apiKey: [] }]
};

View File

@ -1,4 +1,5 @@
import { email, username, password } from '#schema/atomSchema'; import { email, username, password } from '#schema/atomSchema';
import errorAtomSchema from '#schema/error.atomSchema';
// 默认schema // 默认schema
export const userDefaultSchema = {}; export const userDefaultSchema = {};
@ -83,14 +84,7 @@ export const userRegisterSchema = {
message: { type: 'string' } message: { type: 'string' }
} }
}, },
400: { 400: errorAtomSchema['400'],
description: '请求参数错误',
type: 'object',
properties: {
code: { type: 'number', enum: [400] },
error: { type: 'string' },
},
},
409: { 409: {
description: '用户名/邮箱已存在', description: '用户名/邮箱已存在',
type: 'object', type: 'object',
@ -102,3 +96,134 @@ export const userRegisterSchema = {
}, },
security: [{}], // 不需要认证 security: [{}], // 不需要认证
}; };
// 用户登录
export const userLoginSchema = {
tags: ['用户体系'],
summary: '用户登录',
description: '用户账号登录(用户名/邮箱+密码)',
body: {
type: 'object',
required: ['password'],
anyOf: [
{ required: ['username'] },
{ required: ['email'] }
],
errorMessage: {
required: {
password: '缺少密码'
},
anyOf: '必须提供用户名或邮箱'
},
properties: {
username,
email,
password
},
},
response: {
200: {
description: '登录成功',
type: 'object',
properties: {
code: { type: 'number', enum: [200] },
data: {
type: 'object',
properties: {
accessToken: {
type: 'string',
examples: ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
},
refreshToken: {
type: 'string',
examples: ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
},
userInfo: {
type: 'object',
properties: {
userId: { type: 'string' },
username: { type: 'string' },
email: { type: 'string' },
avatarUrl: { type: ['string', 'null'] }
},
required: ['userId', 'username', 'email'],
additionalProperties: false
}
},
required: ['accessToken', 'refreshToken', 'userInfo'],
additionalProperties: false
},
message: { type: 'string' }
}
},
400: errorAtomSchema['400'],
401: errorAtomSchema['401']
},
security: [{}],
};
// 刷新token
export const userRefreshTokenSchema = {
tags: ['用户体系'],
summary: '刷新访问令牌',
description: '使用refreshToken获取新的accessToken',
body: {
type: 'object',
required: ['refreshToken'],
properties: {
refreshToken: {
type: 'string',
description: '使用refreshToken获取新的accessToken',
examples: ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
}
},
errorMessage: {
required: {
refreshToken: '缺少刷新令牌'
}
},
},
response: {
200: {
description: '令牌刷新成功',
type: 'object',
properties: {
code: { type: 'number', enum: [200] },
data: {
type: 'object',
properties: {
accessToken: {
type: 'string',
examples: ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
},
refreshToken: {
type: 'string',
examples: ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
}
},
required: ['accessToken', 'refreshToken'],
additionalProperties: false
},
message: { type: 'string' }
}
},
400: {
description: '无效的refreshToken',
type: 'object',
properties: {
code: { type: 'number', enum: [400] },
error: { type: 'string' },
}
},
401: {
description: '令牌已过期',
type: 'object',
properties: {
code: { type: 'number', enum: [401] },
error: { type: 'string' },
}
}
},
security: [{}] // 只需要携带refreshToken本身
};

View File

@ -0,0 +1,145 @@
import { sysUser, sysUserAuth, sysUserAuthHistory, sysModule } from '#entity';
import { or, eq, and, asc, desc, count, like, ne } from 'drizzle-orm';
// 获取模块列表和分页
export async function getModuleList(queryData) {
const {
page,
pageSize,
name,
moduleKey,
status,
sortBy = 'sort',
sortOrder = 'desc'
} = queryData;
this.log.info(queryData);
const isGetPage = page && pageSize;
const listSelect = {
id: sysModule.id,
name: sysModule.name,
moduleKey: sysModule.moduleKey,
description: sysModule.description,
}
const pageSelect = {
...listSelect,
version: sysModule.version,
sort: sysModule.sort,
status: sysModule.status,
createdBy: sysModule.createdBy,
updatedBy: sysModule.updatedBy,
createdAt: sysModule.createdAt,
updatedAt: sysModule.updatedAt
}
// 构建基础查询
const db = this.db;
let query = db
.select(isGetPage ? pageSelect : listSelect)
.from(sysModule)
.$dynamic();
// 应用过滤条件
const conditions = [];
// 名称模糊查询
if (name) conditions.push(like(sysModule.name, `%${name}%`));
// 模块Key精确查询
if (moduleKey) conditions.push(eq(sysModule.moduleKey, moduleKey));
// 状态过滤
if (status !== undefined) conditions.push(eq(sysModule.status, status));
if (conditions.length > 0) {
query.where(and(...conditions));
}
// 添加排序
query = query.orderBy(
sortOrder === 'asc'
? asc(sysModule[sortBy])
: desc(sysModule[sortBy])
);
// 分页模式
if (isGetPage) {
const offset = (page - 1) * pageSize;
// 分页数据
const list = await query.limit(pageSize).offset(offset);
// 总数查询
const totalResult = await db
.select({ count: count() })
.from(sysModule)
.where(and(...conditions));
return {
total: totalResult[0].count,
list
}
} else {
// 全量列表模式
return {
list: await query
};
}
}
// 查重模块名称和模块Key
export async function checkExistsNameAndKeyForModule(name, moduleKey) {
const result = await this.db
.select()
.from(sysModule)
.where(
or(
eq(sysModule.name, name), // 明确指定字段比较
eq(sysModule.moduleKey, moduleKey),
),
);
return result.length > 0; // 正确判断结果集是否为空
}
// 新增模块插入方法
export async function insertModule(data) {
return this.db.insert(sysModule).values({
name: data.name,
moduleKey: data.moduleKey,
description: data.description,
sort: data.sort || 0,
status: data.status || 0,
createdAt: new Date(),
updatedAt: new Date(),
createdBy: data.createdBy,
}).execute();
}
// 获取模块详情
export async function getModuleById(id) {
const [result] = await this.db.select().from(sysModule).where(eq(sysModule.id, id)).limit(1);
return result
}
// 检查模块Key冲突排除自身
export async function checkModuleKeyConflict(moduleKey, excludeId) {
const [result] = await this.db.select({ id: sysModule.id })
.from(sysModule)
.where(and(
eq(sysModule.moduleKey, moduleKey),
ne(sysModule.id, excludeId)
));
return result
}
// 执行模块更新
export async function updateModule(id, updateData) {
await this.db.update(sysModule)
.set({
// 动态过滤更新字段
...Object.entries(updateData).reduce((acc, [key, value]) => {
// 只更新有实际值的字段(排除 null 和 undefined
if (value !== null && value !== undefined) {
acc[key] = value
}
return acc
}, {}),
updatedAt: new Date() // 确保更新时间总是被修改
})
.where(and(
eq(sysModule.id, id),
eq(sysModule.version, updateData.version)
));
return await getModuleById.call(this, id)
}

View File

@ -0,0 +1,126 @@
import {
getModuleList,
checkExistsNameAndKeyForModule,
insertModule,
updateModule,
getModuleById,
checkModuleKeyConflict
} from "#services/module/module.db";
// 获取模块分页和列表
export async function moduleListService(request, reply) {
const data = await getModuleList.call(this, request.query);
return {
code: 200,
data,
message: '获取成功'
}
}
// 新增模块服务(带分布式锁)
export async function moduleCreateService(request, reply) {
const { name, moduleKey } = request.body;
const lockKey = `${this.const.DISTRIBUTED_LOCK_PREFIX.CREATE_MODULE}${moduleKey}`;
const lockIdentifier = this.snowflake();
let renewInterval;
try {
// 获取分布式锁
const locked = await this.redis.SET(lockKey, lockIdentifier, { NX: true, EX: 5 });
if (!locked) throw this.httpErrors.tooManyRequests('操作正在进行,请稍后重试');
// 启动锁续期
renewInterval = setInterval(async () => {
if (await this.redis.GET(lockKey) === lockIdentifier) {
await this.redis.EXPIRE(lockKey, 5);
}
}, 3000);
// 检查模块Key是否存在
const exists = await checkExistsNameAndKeyForModule.call(this, name, moduleKey,);
if (exists) throw this.httpErrors.conflict('模块标识已存在');
// 创建模块
const newModule = await insertModule.call(this, {
...request.body,
createdBy: request.user?.userId || 0 // 从JWT获取用户ID
});
reply.code(201).send({
code: 201,
data: {
id: newModule.insertId,
moduleKey,
name
},
message: '模块创建成功'
});
} catch (err) {
if (err.statusCode === 409) {
throw this.httpErrors.conflict('模块标识已存在');
}
throw err;
} finally {
clearInterval(renewInterval);
if (await this.redis.GET(lockKey) === lockIdentifier) {
await this.redis.DEL(lockKey);
}
}
}
// 新增模块修改服务
export async function moduleUpdateService(request, reply) {
const { id } = request.params;
const updateData = request.body;
console.log(id, updateData);
let lockKey = `${this.const.DISTRIBUTED_LOCK_PREFIX.UPDATE_MODULE}${id}`;
if(updateData.moduleKey){
lockKey = `${this.const.DISTRIBUTED_LOCK_PREFIX.CREATE_MODULE}${updateData.moduleKey}`;
}
const lockIdentifier = this.snowflake();
let renewInterval;
try {
// 获取分布式锁
const locked = await this.redis.SET(lockKey, lockIdentifier, { NX: true, EX: 5 });
if (!locked) throw this.httpErrors.tooManyRequests('操作正在进行,请稍后重试');
// 启动锁续期
renewInterval = setInterval(async () => {
if (await this.redis.GET(lockKey) === lockIdentifier) {
await this.redis.EXPIRE(lockKey, 5);
}
}, 3000);
// 检查模块是否存在
const existing = await getModuleById.call(this, id);
if (!existing) throw this.httpErrors.notFound('模块不存在');
// 检查模块Key冲突如果更新了moduleKey
if (updateData.moduleKey && updateData.moduleKey !== existing.moduleKey) {
const keyExists = await checkModuleKeyConflict.call(this, updateData.moduleKey, id);
if (keyExists) throw this.httpErrors.conflict('模块标识已被占用');
}
// 执行更新
const updated = await updateModule.call(this, id, {
...updateData,
updatedBy: request.user?.userId || 0,
version: existing.version // 乐观锁
});
return {
code: 200,
data: updated,
message: '模块更新成功'
};
} catch (err) {
if (err.statusCode === 409) {
throw this.httpErrors.conflict(err.message);
}
throw err;
} finally {
clearInterval(renewInterval);
if (await this.redis.GET(lockKey) === lockIdentifier) {
await this.redis.DEL(lockKey);
}
}
}

View File

@ -1,30 +0,0 @@
export async function userDefaultService() {
return 'user service';
}
// 新用户注册
export async function userRegisterService(request, reply) {
// 1. 获取已验证的参数
const { username, email, password } = req.body;
// 2. 业务逻辑校验
const exists = await fastify.db.users.checkExists({ username, email });
if (exists) {
throw fastify.httpErrors.conflict('用户名或邮箱已存在');
}
// // 3. 调用服务层
// const newUser = await fastify.userService.create({
// username,
// email,
// password: await fastify.bcrypt.hash(password)
// });
// 4. 返回标准响应
return reply.code(201).send({
userId: newUser.id,
username: newUser.username,
email: newUser.email,
createdAt: newUser.created_at.toISOString(),
});
}

View File

@ -1,4 +1,4 @@
import { sysUser } from '#entity'; import { sysUser, sysUserAuth, sysUserAuthHistory } from '#entity';
import { or, eq } from 'drizzle-orm'; import { or, eq } from 'drizzle-orm';
// 查重用户名和邮箱 // 查重用户名和邮箱
export async function checkExistsUsernameAndEmail(username, email) { export async function checkExistsUsernameAndEmail(username, email) {
@ -15,18 +15,66 @@ export async function checkExistsUsernameAndEmail(username, email) {
return result.length > 0; // 正确判断结果集是否为空 return result.length > 0; // 正确判断结果集是否为空
} }
// 个人注册账户 // 个人注册账户
export async function registerAccountForMyself(userData) { export async function registerAccount(userData) {
const result = await this.db.insert(sysUser).values({ const result = await this.db.insert(sysUser).values(userData)
username: userData.username,
email: userData.email,
password: userData.password, // 实际应存储加密后的密码
userId: userData.userId, // 使用雪花算法生成唯一ID
pid: 0, // 个人注册账户父节点ID为0
createdBy: 0, // 个人注册账户创建者ID为0
})
const [newUser] = await this.db const [newUser] = await this.db
.select() .select()
.from(sysUser) .from(sysUser)
.where(eq(sysUser.userId, userData.userId)); .where(eq(sysUser.userId, userData.userId));
console.log('AA')
await setPassword.call(this, userData.userId, userData.password)
return newUser; return newUser;
} }
// 设置密码
async function setPassword(userId, password) {
// 获取用户ID
const result = await this.db
.select()
.from(sysUserAuth)
.where(
eq(sysUserAuth.userId, userId),
);
if (result.length > 0) {
// 密码存在,更新密码
await this.db.update(sysUserAuth).set({ passwordHash: password, passwordExpire: new Date(Date.now() + 2 * 365 * 24 * 60 * 60 * 1000) }).where(eq(sysUser.userId, userId))
await this.db.insert(sysUserAuthHistory).values({ id: this.snowflake(), userId, passwordHash: password })
} else {
// 密码不存在,插入新密码
await this.db.insert(sysUserAuth).values({ id: userId, userId, passwordHash: password, passwordExpire: new Date(Date.now() + 2 * 365 * 24 * 60 * 60 * 1000)})
}
}
// 登录
export async function login(username, email) {
const [user] = await this.db.select({
userId: sysUser.userId,
password: sysUserAuth.passwordHash,
username: sysUser.username,
email: sysUser.email,
avatarUrl: sysUser.avatarUrl,
status: sysUser.status
})
.from(sysUser)
.innerJoin(sysUserAuth, eq(sysUser.userId, sysUserAuth.userId))
.where(or(
username ? eq(sysUser.username, username) : undefined,
email ? eq(sysUser.email, email) : undefined
))
.limit(1)
.execute()
return user;
}
// 获取用户基本信息
export async function getUserInfoByUserId(userId) {
const [user] = await this.db.select({
userId: sysUser.userId,
status: sysUser.status
})
.from(sysUser)
.where(eq(sysUser.userId, userId))
.limit(1)
.execute()
return user;
}

View File

@ -1,4 +1,9 @@
import { checkExistsUsernameAndEmail, registerAccountForMyself } from '#services/user/user.db'; import {
checkExistsUsernameAndEmail,
registerAccount,
login,
getUserInfoByUserId
} from '#services/user/user.db';
// 新用户注册 // 新用户注册
export async function userRegisterService(request, reply) { export async function userRegisterService(request, reply) {
// 1. 获取已验证的参数 // 1. 获取已验证的参数
@ -7,40 +12,42 @@ export async function userRegisterService(request, reply) {
// 2. 分布式锁 // 2. 分布式锁
// 生成分布式锁 key // 生成分布式锁 key
const lockKey = `${this.const.DISTRIBUTED_LOCK_PREFIX.REGISTER_USER}${username}:${email}`; const lockKey = `${this.const.DISTRIBUTED_LOCK_PREFIX.REGISTER_USER}${username}:${email}`;
// 生成唯一标识符例如使用Snowflake算法
const userId = this.snowflake(); const lockIdentifier = this.snowflake();
const lockIdentifier = userId; // 生成新用户插入数据
const newUserInsertData = {
username,
email,
password: await this.hash(password),
userId: lockIdentifier,
pid: 0, // 个人注册账户父节点ID为0
createdBy: 0, // 个人注册账户创建者ID为0
}
// 续锁定时器
let renewInterval; let renewInterval;
try { try {
// 尝试获取分布式锁NX: 仅当key不存在时设置EX: 过期时间5秒 // 3. 尝试获取分布式锁NX: 仅当key不存在时设置EX: 过期时间5秒
const locked = await this.redis.SET(lockKey, lockIdentifier, { NX: true, EX: 5 }); const locked = await this.redis.SET(lockKey, lockIdentifier, { NX: true, EX: 5 });
if (!locked) { if (!locked) throw this.httpErrors.tooManyRequests('操作正在进行,请稍后重试');
throw this.httpErrors.tooManyRequests('操作正在进行,请稍后重试');
}
// 启动续锁定时器每3秒续期一次 // 4. 启动续锁定时器每3秒续期一次
renewInterval = setInterval(async () => { renewInterval = setInterval(async () => {
const currentVal = await this.redis.GET(lockKey); const currentVal = await this.redis.GET(lockKey);
if (currentVal === lockIdentifier) { if (currentVal === lockIdentifier) {
await this.redis.EXPIRE(lockKey, 5); // 重置过期时间 await this.redis.EXPIRE(lockKey, 5); // 重置过期时间
} }
}, 3000); }, 3000);
await new Promise(resolve => setTimeout(resolve, 3000)); // 5. 检测用户名和邮箱是否已存在
// 2. 检测用户名和邮箱是否已存在
const exists = await checkExistsUsernameAndEmail.call(this, username, email); const exists = await checkExistsUsernameAndEmail.call(this, username, email);
if (exists) { if (exists) {
throw this.httpErrors.conflict('用户名或邮箱已存在'); throw this.httpErrors.conflict('用户名或邮箱已存在');
} }
// 3. 注册用户 // 6. 注册用户
const newUser = await registerAccountForMyself.call(this, { const newUser = await registerAccount.call(this, newUserInsertData);
username, reply
email, .code(201)
password: await this.hash(password),
userId
});
reply.code(201) // <-- 关键修改
.send({ .send({
code: 201, code: 201,
data: newUser, data: newUser,
@ -49,11 +56,111 @@ export async function userRegisterService(request, reply) {
} finally { } finally {
if (renewInterval) clearInterval(renewInterval); if (renewInterval) clearInterval(renewInterval);
// 释放锁逻辑移除Lua脚本 // 释放锁逻辑移除Lua脚本
const currentVal = await this.redis.GET(lockKey); if (await this.redis.GET(lockKey) === lockIdentifier) await this.redis.DEL(lockKey);
if (currentVal === lockIdentifier) {
await this.redis.DEL(lockKey);
}
} }
} }
// 用户登录
export async function userLoginService(request, reply) {
// 1. 参数解析 ---------- 获取已验证参数(支持用户名/邮箱登录)
const { username, email, password } = request.body;
try {
// 2. 用户查询 ---------- 查询用户信息
const user = await login.call(this, username, email);
console.log(0, user)
// 3. 状态校验 ---------- 用户不存在或状态异常
if (!user || user.status !== 0) {
throw this.httpErrors.unauthorized('用户不存在或已被禁用');
}
// 4. 密码验证 ---------- 密码验证(使用注册时的哈希算法)
const passwordMatch = await this.compareHash(password, user.password);
if (!passwordMatch) {
throw this.httpErrors.unauthorized('用户名或密码错误');
}
// 5. Token生成 ----------
const accessToken = await this.jwt.sign(
{ userId: user.userId },
{ expiresIn: this.config.JWT_ACCESS_EXPIRES }
);
const refreshToken = await this.jwt.sign(
{ userId: user.userId },
{ expiresIn: this.config.JWT_REFRESH_EXPIRES }
);
// 6. 响应处理 ---------- 返回登录结果
reply.code(200).send({
code: 200,
data: {
accessToken,
refreshToken,
userInfo: {
userId: user.userId,
username: user.username,
email: user.email,
avatarUrl: user.avatarUrl
}
},
message: '登录成功'
});
} catch (err) {
// 错误处理 ---------- 统一处理认证错误
if (err.statusCode === 401) throw err;
this.log.error(`登录失败: ${err.message}`);
throw this.httpErrors.unauthorized('认证失败');
}
}
// 刷新Token
// ... existing code ...
export async function refreshTokenService(request, reply) {
// 1. 参数解析 ---------- 获取已验证参数
const { refreshToken } = request.body;
try {
// 2. Token验证 ---------- 验证refreshToken有效性
const decoded = await this.jwt.verify(refreshToken);
// 3. 用户查询 ---------- 查询用户状态
const user = await getUserInfoByUserId.call(this, decoded.userId);
// 4. 状态校验 ---------- 用户不存在或状态异常
if (!user || user.status !== 0) {
throw this.httpErrors.unauthorized('用户不存在或已被禁用');
}
// 5. 生成新Token ----------
const newAccessToken = await this.jwt.sign(
{ userId: user.userId },
{ expiresIn: this.config.JWT_ACCESS_EXPIRES }
);
const newRefreshToken = await this.jwt.sign(
{ userId: user.userId },
{ expiresIn: this.config.JWT_REFRESH_EXPIRES }
);
// 6. 响应处理 ---------- 返回新Token
reply.code(200).send({
code: 200,
data: {
accessToken: newAccessToken,
refreshToken: newRefreshToken
},
message: '令牌刷新成功'
});
} catch (err) {
// 错误处理 ---------- 区分token验证错误类型
if (err.name === 'JsonWebTokenError') {
throw this.httpErrors.badRequest('无效的令牌格式');
}
if (err.name === 'TokenExpiredError') {
throw this.httpErrors.unauthorized('令牌已过期');
}
this.log.error(`令牌刷新失败: ${err.message}`);
throw err;
}
}

View File

@ -14,67 +14,52 @@ import Ajv from 'ajv';
// ajv错误消息回应 // ajv错误消息回应
import ajvErrors from 'ajv-errors'; import ajvErrors from 'ajv-errors';
// // 将字符串转化为全小写 // 将字符串转化为全小写
// export function isLowerCase(ajv){ export function isLowerCase(ajv){
// ajv.addKeyword({
// keyword: 'isLowerCase',
// // 开启错误收集
// errors: true,
// modifying: true,
// validate: function validateIsEven(schema, data, parentSchema, dataCxt) {
// if(typeof data == 'string'){
// dataCxt.parentData[dataCxt.parentDataProperty] = dataCxt.parentData[dataCxt.parentDataProperty].trim().toLowerCase();
// return true;
// }else{
// validateIsEven.errors = [ {
// message: '错误的数据'
// } ];
// return false;
// }
// }
// });
// }
// // 去除两端空格
// export function isTrim(ajv){
// ajv.addKeyword({
// keyword: 'isTrim',
// type: 'string',
// // 开启错误收集
// errors: true,
// modifying: true,
// validate: function validateIsEven(schema, data, parentSchema, dataCxt) {
// if(typeof data == 'string'){
// dataCxt.parentData[dataCxt.parentDataProperty] = dataCxt.parentData[dataCxt.parentDataProperty].trim();
// return true;
// }else{
// validateIsEven.errors = [ {
// message: 'is note String'
// } ];
// return false;
// }
// }
// });
// }
// 示例插件实现(通常放在 src/utils/validators.js
export function isLowerCase(ajv) {
ajv.addKeyword({ ajv.addKeyword({
keyword: 'isLowerCase', keyword: 'isLowerCase',
validate: (schema, data) => typeof data === 'string' && data === data.toLowerCase(), // 开启错误收集
errors: true,
modifying: true,
validate: function validateIsEven(schema, data, parentSchema, dataCxt) {
if(typeof data == 'string'){
dataCxt.parentData[dataCxt.parentDataProperty] = dataCxt.parentData[dataCxt.parentDataProperty].trim().toLowerCase();
return true;
}else{
validateIsEven.errors = [ {
message: '错误的数据'
} ];
return false;
}
}
}); });
} }
export function isTrim(ajv) { // 去除两端空格
export function isTrim(ajv){
ajv.addKeyword({ ajv.addKeyword({
keyword: 'isTrim', keyword: 'isTrim',
validate: (schema, data) => typeof data === 'string' && data === data.trim(), type: 'string',
// 开启错误收集
errors: true,
modifying: true,
validate: function validateIsEven(schema, data, parentSchema, dataCxt) {
if(typeof data == 'string'){
dataCxt.parentData[dataCxt.parentDataProperty] = dataCxt.parentData[dataCxt.parentDataProperty].trim();
return true;
}else{
validateIsEven.errors = [ {
message: 'is note String'
} ];
return false;
}
}
}); });
} }
// 给fastify添加自定义的参数校验规则 // 给fastify添加自定义的参数校验规则
function customAjv(fastify, options) { function customAjv(fastify, options) {
// 创建一个新的 AJV 实例
// 创建一个新的 AJV 实例 // 创建一个新的 AJV 实例
const ajv = new Ajv({ const ajv = new Ajv({
removeAdditional: true, removeAdditional: true,

View File

@ -1,5 +1,5 @@
CREATE TABLE `sys_dict` ( CREATE TABLE `sys_dict` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID', `id` BIGINT NOT NULL COMMENT 'ID',
`version` INT NOT NULL DEFAULT 0, `version` INT NOT NULL DEFAULT 0,
`pid` BIGINT NOT NULL COMMENT '上级ID', `pid` BIGINT NOT NULL COMMENT '上级ID',
`module` tinyint NULL COMMENT '模块', `module` tinyint NULL COMMENT '模块',
@ -17,7 +17,7 @@ CREATE TABLE `sys_dict` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '字典表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '字典表';
CREATE TABLE `sys_organization` ( CREATE TABLE `sys_organization` (
`org_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '组织ID', `org_id` BIGINT NOT NULL COMMENT '组织ID',
`pid` BIGINT DEFAULT 0 NOT NULL COMMENT '上级组织ID', `pid` BIGINT DEFAULT 0 NOT NULL COMMENT '上级组织ID',
`org_name` varchar(255) NULL COMMENT '组织名称', `org_name` varchar(255) NULL COMMENT '组织名称',
`org_code` varchar(128) NULL COMMENT '组织编码', `org_code` varchar(128) NULL COMMENT '组织编码',
@ -35,7 +35,7 @@ CREATE TABLE `sys_organization` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '组织表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '组织表';
CREATE TABLE `sys_organization_manager` ( CREATE TABLE `sys_organization_manager` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID', `id` BIGINT NOT NULL COMMENT 'ID',
`version` INT NOT NULL DEFAULT 0, `version` INT NOT NULL DEFAULT 0,
`org_id` bigint NOT NULL COMMENT '组织ID', `org_id` bigint NOT NULL COMMENT '组织ID',
`user_id` bigint NOT NULL COMMENT '用户ID', `user_id` bigint NOT NULL COMMENT '用户ID',
@ -51,7 +51,7 @@ CREATE TABLE `sys_organization_manager` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '组织负责人表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '组织负责人表';
CREATE TABLE `sys_permission` ( CREATE TABLE `sys_permission` (
`perm_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '权限ID', `perm_id` BIGINT NOT NULL COMMENT '权限ID',
`pid` BIGINT NOT NULL DEFAULT 0 COMMENT '上级权限ID', `pid` BIGINT NOT NULL DEFAULT 0 COMMENT '上级权限ID',
`perm_name` varchar(255) NOT NULL COMMENT '权限名称', `perm_name` varchar(255) NOT NULL COMMENT '权限名称',
`perm_key` varchar(255) NOT NULL COMMENT '权限标识', `perm_key` varchar(255) NOT NULL COMMENT '权限标识',
@ -73,7 +73,7 @@ CREATE TABLE `sys_permission` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '权限表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '权限表';
CREATE TABLE `sys_re_role_permission` ( CREATE TABLE `sys_re_role_permission` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '关系ID', `id` BIGINT NOT NULL COMMENT '关系ID',
`role_id` BIGINT NOT NULL COMMENT '角色ID', `role_id` BIGINT NOT NULL COMMENT '角色ID',
`perm_id` BIGINT NOT NULL COMMENT '权限ID', `perm_id` BIGINT NOT NULL COMMENT '权限ID',
`created_by` bigint NOT NULL COMMENT '创建人', `created_by` bigint NOT NULL COMMENT '创建人',
@ -85,7 +85,7 @@ CREATE TABLE `sys_re_role_permission` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户组织关系表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户组织关系表';
CREATE TABLE `sys_re_user_organization` ( CREATE TABLE `sys_re_user_organization` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '关系ID', `id` BIGINT NOT NULL COMMENT '关系ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID', `user_id` BIGINT NOT NULL COMMENT '用户ID',
`org_id` BIGINT NOT NULL COMMENT '组织ID', `org_id` BIGINT NOT NULL COMMENT '组织ID',
`version` INT NOT NULL DEFAULT 0, `version` INT NOT NULL DEFAULT 0,
@ -98,7 +98,7 @@ CREATE TABLE `sys_re_user_organization` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户组织关系表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户组织关系表';
CREATE TABLE `sys_re_user_role` ( CREATE TABLE `sys_re_user_role` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '关系ID', `id` BIGINT NOT NULL COMMENT '关系ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID', `user_id` BIGINT NOT NULL COMMENT '用户ID',
`role_id` BIGINT NOT NULL COMMENT '角色ID', `role_id` BIGINT NOT NULL COMMENT '角色ID',
`version` INT NOT NULL DEFAULT 0, `version` INT NOT NULL DEFAULT 0,
@ -111,7 +111,7 @@ CREATE TABLE `sys_re_user_role` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户角色关联表';; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户角色关联表';;
CREATE TABLE `sys_role` ( CREATE TABLE `sys_role` (
`role_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '角色ID', `role_id` BIGINT NOT NULL COMMENT '角色ID',
`pid` BIGINT NOT NULL COMMENT '上级角色ID', `pid` BIGINT NOT NULL COMMENT '上级角色ID',
`role_name` varchar(255) NOT NULL COMMENT '角色名称', `role_name` varchar(255) NOT NULL COMMENT '角色名称',
`role_key` varchar(255) NOT NULL COMMENT '角色表示', `role_key` varchar(255) NOT NULL COMMENT '角色表示',
@ -146,21 +146,21 @@ CREATE TABLE `sys_user` (
CREATE TABLE `sys_user_auth` ( CREATE TABLE `sys_user_auth` (
`user_id` bigint NOT NULL COMMENT '用户ID', `user_id` bigint NOT NULL COMMENT '用户ID',
`password_hash` varchar(255) NOT NULL COMMENT '用户密码', `password_hash` varchar(255) NOT NULL COMMENT '用户密码',
`password_modified` DATETIME NOT NULL COMMENT '上一次修改时间', `password_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '上一次修改时间',
`password_expire` DATETIME NULL DEFAULT NULL COMMENT '过期时间', `password_expire` DATETIME NULL DEFAULT NULL COMMENT '过期时间',
PRIMARY KEY (`user_id`) PRIMARY KEY (`user_id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户密码表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户密码表';
CREATE TABLE `sys_user_auth_history` ( CREATE TABLE `sys_user_auth_history` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '历史记录自增主键', `id` bigint NOT NULL COMMENT '历史记录自增主键',
`user_id` bigint NOT NULL COMMENT '用户id', `user_id` bigint NOT NULL COMMENT '用户id',
`password_hash` varchar(255) NOT NULL COMMENT '历史密码值,注意需要限制密码更改次数', `password_hash` varchar(255) NOT NULL COMMENT '历史密码值,注意需要限制密码更改次数',
`modified_at` varchar(255) NOT NULL COMMENT '上次修改的时间', `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '上次修改的时间',
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '历史密码表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '历史密码表';
CREATE TABLE `sys_user_field_definition` ( CREATE TABLE `sys_user_field_definition` (
`field_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户拓展字段ID', `field_id` BIGINT NOT NULL COMMENT '用户拓展字段ID',
`version` INT NOT NULL DEFAULT 0, `version` INT NOT NULL DEFAULT 0,
`field_name` varchar(255) NOT NULL COMMENT '拓展字段名称', `field_name` varchar(255) NOT NULL COMMENT '拓展字段名称',
`field_key` varchar(255) NOT NULL COMMENT '拓展字段标识', `field_key` varchar(255) NOT NULL COMMENT '拓展字段标识',
@ -183,7 +183,7 @@ CREATE TABLE `sys_user_field_definition` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户拓展字段定义表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户拓展字段定义表';
CREATE TABLE `sys_user_field_value` ( CREATE TABLE `sys_user_field_value` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户拓展字段记录id', `id` bigint NOT NULL COMMENT '用户拓展字段记录id',
`user_id` bigint NOT NULL COMMENT '用户ID', `user_id` bigint NOT NULL COMMENT '用户ID',
`field_id` int NOT NULL COMMENT '字段ID', `field_id` int NOT NULL COMMENT '字段ID',
`value` varchar(4096) NULL COMMENT '用户拓展字段值', `value` varchar(4096) NULL COMMENT '用户拓展字段值',
@ -196,7 +196,7 @@ CREATE TABLE `sys_user_field_value` (
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户拓展字段记录表'; )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户拓展字段记录表';
CREATE TABLE `sys_profile` ( CREATE TABLE `sys_profile` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '系统配置自增主键', `id` BIGINT NOT NULL COMMENT '系统配置自增主键',
`version` INT NOT NULL DEFAULT 0, `version` INT NOT NULL DEFAULT 0,
`name` varchar(32) NOT NULL COMMENT '系统配置名称', `name` varchar(32) NOT NULL COMMENT '系统配置名称',
`profile_key` varchar(255) NOT NULL COMMENT '系统配置记录Key', `profile_key` varchar(255) NOT NULL COMMENT '系统配置记录Key',
@ -212,7 +212,7 @@ CREATE TABLE `sys_profile` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '系统配置'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '系统配置';
CREATE TABLE `sys_module` ( CREATE TABLE `sys_module` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '系统模块自增主键', `id` INT NOT NULL AUTO_INCREMENT COMMENT '系统模块自增主键',
`version` INT NOT NULL DEFAULT 0, `version` INT NOT NULL DEFAULT 0,
`name` varchar(32) NOT NULL COMMENT '系统模块名称', `name` varchar(32) NOT NULL COMMENT '系统模块名称',
`module_key` varchar(255) NOT NULL COMMENT '系统模块记录Key', `module_key` varchar(255) NOT NULL COMMENT '系统模块记录Key',
@ -224,6 +224,5 @@ CREATE TABLE `sys_module` (
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE `uniq_name` (`name`),
UNIQUE `uniq_module_key` (`module_key`) UNIQUE `uniq_module_key` (`module_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '系统模块'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '系统模块';