feat: 添加JWT认证和模块管理功能
- 新增JWT认证插件,支持用户登录、刷新Token功能 - 添加系统模块管理功能,包括模块的创建、更新和查询 - 更新用户服务,支持用户注册、登录和Token刷新 - 修改数据库表结构,添加模块相关表和字段 - 更新文档,添加JWT认证和模块管理相关说明
This commit is contained in:
parent
fdc4d67092
commit
81953dd44c
10
.env
10
.env
@ -20,7 +20,7 @@ DB_PORT=3306
|
||||
|
||||
# redis
|
||||
REDIS_HOST=172.16.1.10
|
||||
REDIS_PORT=16379
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=Hxl1314521
|
||||
REDIS_DB=9
|
||||
|
||||
@ -29,3 +29,11 @@ DATACENTER_ID=1
|
||||
|
||||
# 机器ID
|
||||
MACHINE_ID=1
|
||||
|
||||
# JWT配置
|
||||
# 过期时间
|
||||
JWT_ACCESS_EXPIRES=20m
|
||||
# 刷新时间
|
||||
JWT_REFRESH_EXPIRES=14d
|
||||
# 密钥
|
||||
JWT_SECRET=your_secure_secret_key
|
||||
|
@ -1,2 +1,3 @@
|
||||
import { relations } from 'drizzle-orm/relations';
|
||||
import {} from './schema';
|
||||
import { relations } from "drizzle-orm/relations";
|
||||
import { } from "./schema";
|
||||
|
||||
|
491
SQL/schema.ts
491
SQL/schema.ts
@ -1,339 +1,262 @@
|
||||
import {
|
||||
mysqlTable,
|
||||
mysqlSchema,
|
||||
AnyMySqlColumn,
|
||||
primaryKey,
|
||||
unique,
|
||||
bigint,
|
||||
int,
|
||||
tinyint,
|
||||
varchar,
|
||||
datetime,
|
||||
} from 'drizzle-orm/mysql-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { mysqlTable, mysqlSchema, AnyMySqlColumn, primaryKey, unique, bigint, int, tinyint, varchar, datetime } from "drizzle-orm/mysql-core"
|
||||
import { sql } from "drizzle-orm"
|
||||
|
||||
export const sysDict = mysqlTable(
|
||||
'sys_dict',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).autoincrement().notNull(),
|
||||
export const sysDict = mysqlTable("sys_dict", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
module: tinyint(),
|
||||
dictKey: varchar('dict_key', { length: 255 }),
|
||||
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(),
|
||||
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_dict_id' }),
|
||||
unique('uniq_dict_key').on(table.dictKey, table.pid),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_dict_id"}),
|
||||
unique("uniq_dict_key").on(table.dictKey, table.pid),
|
||||
]);
|
||||
|
||||
export const sysOrganization = mysqlTable(
|
||||
'sys_organization',
|
||||
{
|
||||
orgId: bigint('org_id', { mode: 'number' }).autoincrement().notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
orgName: varchar('org_name', { length: 255 }),
|
||||
orgCode: varchar('org_code', { length: 128 }),
|
||||
orgType: int('org_type').notNull(),
|
||||
export const sysModule = mysqlTable("sys_module", {
|
||||
id: int().autoincrement().notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
name: varchar({ length: 32 }).notNull(),
|
||||
moduleKey: varchar("module_key", { length: 255 }).notNull(),
|
||||
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(),
|
||||
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.orgId], name: 'sys_organization_org_id' }),
|
||||
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_module_id"}),
|
||||
unique("uniq_name").on(table.name),
|
||||
unique("uniq_module_key").on(table.moduleKey),
|
||||
]);
|
||||
|
||||
export const sysOrganizationManager = mysqlTable(
|
||||
'sys_organization_manager',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).autoincrement().notNull(),
|
||||
export const sysOrganization = mysqlTable("sys_organization", {
|
||||
orgId: bigint("org_id", { mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
orgName: varchar("org_name", { length: 255 }),
|
||||
orgCode: varchar("org_code", { length: 128 }),
|
||||
orgType: int("org_type").notNull(),
|
||||
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" }),
|
||||
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.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("sys_organization_manager", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
orgId: bigint('org_id', { mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
orgId: bigint("org_id", { mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
rank: int().notNull(),
|
||||
status: int().notNull(),
|
||||
description: varchar({ length: 255 }),
|
||||
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(),
|
||||
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_organization_manager_id' }),
|
||||
unique('uniq_org_user').on(table.orgId, table.userId),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_organization_manager_id"}),
|
||||
unique("uniq_org_user").on(table.orgId, table.userId),
|
||||
]);
|
||||
|
||||
export const sysPermission = mysqlTable(
|
||||
'sys_permission',
|
||||
{
|
||||
permId: bigint('perm_id', { mode: 'number' }).autoincrement().notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
permName: varchar('perm_name', { length: 255 }).notNull(),
|
||||
permKey: varchar('perm_key', { length: 255 }).notNull(),
|
||||
export const sysPermission = mysqlTable("sys_permission", {
|
||||
permId: bigint("perm_id", { mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
permName: varchar("perm_name", { length: 255 }).notNull(),
|
||||
permKey: varchar("perm_key", { length: 255 }).notNull(),
|
||||
url: varchar({ length: 255 }),
|
||||
avatarUrl: varchar('avatar_url', { length: 255 }),
|
||||
avatarUrl: varchar("avatar_url", { length: 255 }),
|
||||
description: varchar({ length: 255 }),
|
||||
permType: int('perm_type').notNull(),
|
||||
isVisible: int('is_visible').default(0).notNull(),
|
||||
permType: int("perm_type").notNull(),
|
||||
isVisible: int("is_visible").default(0).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
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(),
|
||||
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.permId], name: 'sys_permission_perm_id' }),
|
||||
unique('uniq_pid_name').on(table.permName, table.pid),
|
||||
unique('uniq_perm_key').on(table.permKey),
|
||||
],
|
||||
);
|
||||
(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(
|
||||
'sys_re_role_permission',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).autoincrement().notNull(),
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
permId: bigint('perm_id', { mode: 'number' }).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_re_role_permission_id' }),
|
||||
unique('uniq_perm_role').on(table.roleId, table.permId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysReUserOrganization = mysqlTable(
|
||||
'sys_re_user_organization',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).autoincrement().notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
orgId: bigint('org_id', { mode: 'number' }).notNull(),
|
||||
export const sysProfile = mysqlTable("sys_profile", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
version: int().default(0).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(),
|
||||
name: varchar({ length: 32 }).notNull(),
|
||||
profileKey: varchar("profile_key", { length: 255 }).notNull(),
|
||||
description: varchar({ length: 255 }),
|
||||
content: varchar({ length: 255 }),
|
||||
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_re_user_organization_id' }),
|
||||
unique('uniq_user_org').on(table.userId, table.orgId),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_profile_id"}),
|
||||
unique("uniq_name").on(table.name),
|
||||
unique("uniq_profile_key").on(table.profileKey),
|
||||
]);
|
||||
|
||||
export const sysReUserRole = mysqlTable(
|
||||
'sys_re_user_role',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).autoincrement().notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
export const sysReRolePermission = mysqlTable("sys_re_role_permission", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
permId: bigint("perm_id", { mode: "number" }).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.id], name: "sys_re_role_permission_id"}),
|
||||
unique("uniq_perm_role").on(table.roleId, table.permId),
|
||||
]);
|
||||
|
||||
export const sysReUserOrganization = mysqlTable("sys_re_user_organization", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
orgId: bigint("org_id", { mode: "number" }).notNull(),
|
||||
version: int().default(0).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(),
|
||||
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_re_user_role_id' }),
|
||||
unique('uniq_user_role').on(table.userId, table.roleId),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_re_user_organization_id"}),
|
||||
unique("uniq_user_org").on(table.userId, table.orgId),
|
||||
]);
|
||||
|
||||
export const sysRole = mysqlTable(
|
||||
'sys_role',
|
||||
{
|
||||
roleId: bigint('role_id', { mode: 'number' }).autoincrement().notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
roleName: varchar('role_name', { length: 255 }).notNull(),
|
||||
roleKey: varchar('role_key', { length: 255 }).notNull(),
|
||||
export const sysReUserRole = mysqlTable("sys_re_user_role", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
version: int().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.id], name: "sys_re_user_role_id"}),
|
||||
unique("uniq_user_role").on(table.userId, table.roleId),
|
||||
]);
|
||||
|
||||
export const sysRole = mysqlTable("sys_role", {
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
roleName: varchar("role_name", { length: 255 }).notNull(),
|
||||
roleKey: varchar("role_key", { length: 255 }).notNull(),
|
||||
description: varchar({ length: 255 }),
|
||||
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(),
|
||||
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.roleId], name: 'sys_role_role_id' }),
|
||||
unique('uniq_role_pid').on(table.roleName, table.pid),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.roleId], name: "sys_role_role_id"}),
|
||||
unique("uniq_role_pid").on(table.roleName, table.pid),
|
||||
]);
|
||||
|
||||
export const sysUser = mysqlTable(
|
||||
'sys_user',
|
||||
{
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
export const sysUser = mysqlTable("sys_user", {
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
username: varchar({ length: 255 }).notNull(),
|
||||
email: varchar({ length: 255 }).notNull(),
|
||||
phone: varchar({ length: 255 }),
|
||||
avatarUrl: varchar('avatar_url', { length: 255 }),
|
||||
userType: tinyint('user_type'),
|
||||
avatarUrl: varchar("avatar_url", { length: 255 }),
|
||||
userType: tinyint("user_type"),
|
||||
status: tinyint().default(0).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(),
|
||||
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),
|
||||
],
|
||||
);
|
||||
(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(
|
||||
'sys_user_auth',
|
||||
{
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
|
||||
passwordModified: datetime('password_modified', { mode: 'string' }).notNull(),
|
||||
passwordExpire: datetime('password_expire', { mode: 'string' }),
|
||||
export const sysUserAuth = mysqlTable("sys_user_auth", {
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
passwordHash: varchar("password_hash", { length: 255 }).notNull(),
|
||||
passwordModified: datetime("password_modified", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
passwordExpire: datetime("password_expire", { mode: 'string'}),
|
||||
},
|
||||
table => [primaryKey({ columns: [table.userId], name: 'sys_user_auth_user_id' })],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.userId], name: "sys_user_auth_user_id"}),
|
||||
]);
|
||||
|
||||
export const sysUserAuthHistory = mysqlTable(
|
||||
'sys_user_auth_history',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).autoincrement().notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
|
||||
modifiedAt: varchar('modified_at', { length: 255 }).notNull(),
|
||||
export const sysUserAuthHistory = mysqlTable("sys_user_auth_history", {
|
||||
id: bigint({ mode: "number" }).autoincrement().notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
passwordHash: varchar("password_hash", { length: 255 }).notNull(),
|
||||
modifiedAt: datetime("modified_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
table => [primaryKey({ columns: [table.id], name: 'sys_user_auth_history_id' })],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_user_auth_history_id"}),
|
||||
]);
|
||||
|
||||
export const sysUserFieldDefinition = mysqlTable(
|
||||
'sys_user_field_definition',
|
||||
{
|
||||
fieldId: bigint('field_id', { mode: 'number' }).autoincrement().notNull(),
|
||||
export const sysUserFieldDefinition = mysqlTable("sys_user_field_definition", {
|
||||
fieldId: bigint("field_id", { mode: "number" }).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
fieldName: varchar('field_name', { length: 255 }).notNull(),
|
||||
fieldKey: varchar('field_key', { length: 255 }).notNull(),
|
||||
fieldType: tinyint('field_type').notNull(),
|
||||
dictModule: int('dict_module'),
|
||||
isRequired: tinyint('is_required').default(0).notNull(),
|
||||
fieldName: varchar("field_name", { length: 255 }).notNull(),
|
||||
fieldKey: varchar("field_key", { length: 255 }).notNull(),
|
||||
fieldType: tinyint("field_type").notNull(),
|
||||
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 }),
|
||||
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(),
|
||||
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),
|
||||
],
|
||||
);
|
||||
(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' }).autoincrement().notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
fieldId: int('field_id').notNull(),
|
||||
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' }).notNull(),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.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" }),
|
||||
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(
|
||||
'sys_user_profile',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).autoincrement().notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
name: varchar({ length: 32 }).notNull(),
|
||||
profileKey: varchar('profile_key', { length: 255 }).notNull(),
|
||||
description: varchar({ length: 255 }),
|
||||
content: varchar({ length: 255 }),
|
||||
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_user_profile_id' }),
|
||||
unique('uniq_name').on(table.name),
|
||||
unique('uniq_profile_key').on(table.profileKey),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_user_field_value_id"}),
|
||||
unique("uniq_user_field").on(table.userId, table.fieldId),
|
||||
]);
|
||||
|
@ -58,7 +58,21 @@ const baseConfig = {
|
||||
database: process.env.REDIS_DB || 9,
|
||||
username: 'default',
|
||||
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'
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
// 环境特定配置
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
2. 用户登录
|
||||
|
||||
2.1 刷新Token
|
||||
|
||||
3. 用户登出
|
||||
|
||||
4. 用户注销
|
||||
|
141
package-lock.json
generated
141
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fastify/jwt": "^9.1.0",
|
||||
"@fastify/sensible": "^6.0.3",
|
||||
"@fastify/swagger": "^9.4.2",
|
||||
"@fastify/swagger-ui": "^5.2.2",
|
||||
@ -1043,6 +1044,29 @@
|
||||
"integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==",
|
||||
"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": {
|
||||
"version": "0.2.1",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz",
|
||||
@ -1759,6 +1795,12 @@
|
||||
"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": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -2390,6 +2432,15 @@
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"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": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
@ -3016,6 +3067,21 @@
|
||||
"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": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
@ -3063,6 +3129,18 @@
|
||||
],
|
||||
"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": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/fastify/-/fastify-5.2.1.tgz",
|
||||
@ -3102,6 +3180,16 @@
|
||||
"integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==",
|
||||
"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": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz",
|
||||
@ -3111,6 +3199,16 @@
|
||||
"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": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@ -4549,6 +4647,12 @@
|
||||
"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": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -4616,6 +4720,15 @@
|
||||
"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": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||
@ -4860,6 +4973,12 @@
|
||||
"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": {
|
||||
"version": "2.1.2",
|
||||
"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_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": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz",
|
||||
@ -6631,6 +6763,15 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
|
||||
|
@ -35,6 +35,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fastify/jwt": "^9.1.0",
|
||||
"@fastify/sensible": "^6.0.3",
|
||||
"@fastify/swagger": "^9.4.2",
|
||||
"@fastify/swagger-ui": "^5.2.2",
|
||||
|
@ -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';
|
||||
const bigint = bigintString;
|
||||
|
||||
export const sysDict = mysqlTable(
|
||||
'sys_dict',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
import { mysqlTable, primaryKey, unique, int, tinyint, varchar, datetime } from "drizzle-orm/mysql-core"
|
||||
import { sql } from "drizzle-orm"
|
||||
|
||||
export const sysDict = mysqlTable("sys_dict", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
module: tinyint(),
|
||||
dictKey: varchar('dict_key', { length: 255 }),
|
||||
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(),
|
||||
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_dict_id' }),
|
||||
unique('uniq_dict_key').on(table.dictKey, table.pid),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_dict_id"}),
|
||||
unique("uniq_dict_key").on(table.dictKey, table.pid),
|
||||
]);
|
||||
|
||||
export const sysOrganization = mysqlTable(
|
||||
'sys_organization',
|
||||
{
|
||||
orgId: bigint('org_id', { mode: 'number' }).notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
orgName: varchar('org_name', { length: 255 }),
|
||||
orgCode: varchar('org_code', { length: 128 }),
|
||||
orgType: int('org_type').notNull(),
|
||||
export const sysModule = mysqlTable("sys_module", {
|
||||
id: int().autoincrement().notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
name: varchar({ length: 32 }).notNull(),
|
||||
moduleKey: varchar("module_key", { length: 255 }).notNull(),
|
||||
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(),
|
||||
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.orgId], name: 'sys_organization_org_id' }),
|
||||
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_module_id"}),
|
||||
unique("uniq_name").on(table.name),
|
||||
unique("uniq_module_key").on(table.moduleKey),
|
||||
]);
|
||||
|
||||
export const sysOrganizationManager = mysqlTable(
|
||||
'sys_organization_manager',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
export const sysOrganization = mysqlTable("sys_organization", {
|
||||
orgId: bigint("org_id", { mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
orgName: varchar("org_name", { length: 255 }),
|
||||
orgCode: varchar("org_code", { length: 128 }),
|
||||
orgType: int("org_type").notNull(),
|
||||
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" }),
|
||||
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.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("sys_organization_manager", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
orgId: bigint('org_id', { mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
orgId: bigint("org_id", { mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
rank: int().notNull(),
|
||||
status: int().notNull(),
|
||||
description: varchar({ length: 255 }),
|
||||
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(),
|
||||
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_organization_manager_id' }),
|
||||
unique('uniq_org_user').on(table.orgId, table.userId),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_organization_manager_id"}),
|
||||
unique("uniq_org_user").on(table.orgId, table.userId),
|
||||
]);
|
||||
|
||||
export const sysPermission = mysqlTable(
|
||||
'sys_permission',
|
||||
{
|
||||
permId: bigint('perm_id', { mode: 'number' }).notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
permName: varchar('perm_name', { length: 255 }).notNull(),
|
||||
permKey: varchar('perm_key', { length: 255 }).notNull(),
|
||||
export const sysPermission = mysqlTable("sys_permission", {
|
||||
permId: bigint("perm_id", { mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
permName: varchar("perm_name", { length: 255 }).notNull(),
|
||||
permKey: varchar("perm_key", { length: 255 }).notNull(),
|
||||
url: varchar({ length: 255 }),
|
||||
avatarUrl: varchar('avatar_url', { length: 255 }),
|
||||
avatarUrl: varchar("avatar_url", { length: 255 }),
|
||||
description: varchar({ length: 255 }),
|
||||
permType: int('perm_type').notNull(),
|
||||
isVisible: int('is_visible').default(0).notNull(),
|
||||
permType: int("perm_type").notNull(),
|
||||
isVisible: int("is_visible").default(0).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
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(),
|
||||
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.permId], name: 'sys_permission_perm_id' }),
|
||||
unique('uniq_pid_name').on(table.permName, table.pid),
|
||||
unique('uniq_perm_key').on(table.permKey),
|
||||
],
|
||||
);
|
||||
(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(
|
||||
'sys_re_role_permission',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
permId: bigint('perm_id', { mode: 'number' }).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_re_role_permission_id' }),
|
||||
unique('uniq_perm_role').on(table.roleId, table.permId),
|
||||
],
|
||||
);
|
||||
|
||||
export const sysReUserOrganization = mysqlTable(
|
||||
'sys_re_user_organization',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
orgId: bigint('org_id', { mode: 'number' }).notNull(),
|
||||
export const sysProfile = mysqlTable("sys_profile", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
version: int().default(0).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(),
|
||||
name: varchar({ length: 32 }).notNull(),
|
||||
profileKey: varchar("profile_key", { length: 255 }).notNull(),
|
||||
description: varchar({ length: 255 }),
|
||||
content: varchar({ length: 255 }),
|
||||
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_re_user_organization_id' }),
|
||||
unique('uniq_user_org').on(table.userId, table.orgId),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_profile_id"}),
|
||||
unique("uniq_name").on(table.name),
|
||||
unique("uniq_profile_key").on(table.profileKey),
|
||||
]);
|
||||
|
||||
export const sysReUserRole = mysqlTable(
|
||||
'sys_re_user_role',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
export const sysReRolePermission = mysqlTable("sys_re_role_permission", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
permId: bigint("perm_id", { mode: "number" }).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.id], name: "sys_re_role_permission_id"}),
|
||||
unique("uniq_perm_role").on(table.roleId, table.permId),
|
||||
]);
|
||||
|
||||
export const sysReUserOrganization = mysqlTable("sys_re_user_organization", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
orgId: bigint("org_id", { mode: "number" }).notNull(),
|
||||
version: int().default(0).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(),
|
||||
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_re_user_role_id' }),
|
||||
unique('uniq_user_role').on(table.userId, table.roleId),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_re_user_organization_id"}),
|
||||
unique("uniq_user_org").on(table.userId, table.orgId),
|
||||
]);
|
||||
|
||||
export const sysRole = mysqlTable(
|
||||
'sys_role',
|
||||
{
|
||||
roleId: bigint('role_id', { mode: 'number' }).notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
roleName: varchar('role_name', { length: 255 }).notNull(),
|
||||
roleKey: varchar('role_key', { length: 255 }).notNull(),
|
||||
export const sysReUserRole = mysqlTable("sys_re_user_role", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
version: int().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.id], name: "sys_re_user_role_id"}),
|
||||
unique("uniq_user_role").on(table.userId, table.roleId),
|
||||
]);
|
||||
|
||||
export const sysRole = mysqlTable("sys_role", {
|
||||
roleId: bigint("role_id", { mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
roleName: varchar("role_name", { length: 255 }).notNull(),
|
||||
roleKey: varchar("role_key", { length: 255 }).notNull(),
|
||||
description: varchar({ length: 255 }),
|
||||
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(),
|
||||
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.roleId], name: 'sys_role_role_id' }),
|
||||
unique('uniq_role_pid').on(table.roleName, table.pid),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.roleId], name: "sys_role_role_id"}),
|
||||
unique("uniq_role_pid").on(table.roleName, table.pid),
|
||||
]);
|
||||
|
||||
export const sysUser = mysqlTable(
|
||||
'sys_user',
|
||||
{
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
pid: bigint({ mode: 'number' }).notNull(),
|
||||
export const sysUser = mysqlTable("sys_user", {
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
pid: bigint({ mode: "number" }).notNull(),
|
||||
username: varchar({ length: 255 }).notNull(),
|
||||
email: varchar({ length: 255 }).notNull(),
|
||||
phone: varchar({ length: 255 }),
|
||||
avatarUrl: varchar('avatar_url', { length: 255 }),
|
||||
userType: tinyint('user_type'),
|
||||
avatarUrl: varchar("avatar_url", { length: 255 }),
|
||||
userType: tinyint("user_type"),
|
||||
status: tinyint().default(0).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(),
|
||||
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),
|
||||
],
|
||||
);
|
||||
(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(
|
||||
'sys_user_auth',
|
||||
{
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
|
||||
passwordModified: datetime('password_modified', { mode: 'string' }).notNull(),
|
||||
passwordExpire: datetime('password_expire', { mode: 'string' }),
|
||||
export const sysUserAuth = mysqlTable("sys_user_auth", {
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
passwordHash: varchar("password_hash", { length: 255 }).notNull(),
|
||||
passwordModified: datetime("password_modified", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
passwordExpire: datetime("password_expire", { mode: 'string'}),
|
||||
},
|
||||
table => [primaryKey({ columns: [table.userId], name: 'sys_user_auth_user_id' })],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.userId], name: "sys_user_auth_user_id"}),
|
||||
]);
|
||||
|
||||
export const sysUserAuthHistory = mysqlTable(
|
||||
'sys_user_auth_history',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
passwordHash: varchar('password_hash', { length: 255 }).notNull(),
|
||||
modifiedAt: varchar('modified_at', { length: 255 }).notNull(),
|
||||
export const sysUserAuthHistory = mysqlTable("sys_user_auth_history", {
|
||||
id: bigint({ mode: "number" }).notNull(),
|
||||
userId: bigint("user_id", { mode: "number" }).notNull(),
|
||||
passwordHash: varchar("password_hash", { length: 255 }).notNull(),
|
||||
modifiedAt: datetime("modified_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(),
|
||||
},
|
||||
table => [primaryKey({ columns: [table.id], name: 'sys_user_auth_history_id' })],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_user_auth_history_id"}),
|
||||
]);
|
||||
|
||||
export const sysUserFieldDefinition = mysqlTable(
|
||||
'sys_user_field_definition',
|
||||
{
|
||||
fieldId: bigint('field_id', { mode: 'number' }).notNull(),
|
||||
export const sysUserFieldDefinition = mysqlTable("sys_user_field_definition", {
|
||||
fieldId: bigint("field_id", { mode: "number" }).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
fieldName: varchar('field_name', { length: 255 }).notNull(),
|
||||
fieldKey: varchar('field_key', { length: 255 }).notNull(),
|
||||
fieldType: tinyint('field_type').notNull(),
|
||||
dictModule: int('dict_module'),
|
||||
isRequired: tinyint('is_required').default(0).notNull(),
|
||||
fieldName: varchar("field_name", { length: 255 }).notNull(),
|
||||
fieldKey: varchar("field_key", { length: 255 }).notNull(),
|
||||
fieldType: tinyint("field_type").notNull(),
|
||||
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 }),
|
||||
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(),
|
||||
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),
|
||||
],
|
||||
);
|
||||
(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(),
|
||||
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' }).notNull(),
|
||||
createdAt: datetime('created_at', { mode: 'string' })
|
||||
.default(sql`(CURRENT_TIMESTAMP)`)
|
||||
.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" }),
|
||||
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(
|
||||
'sys_user_profile',
|
||||
{
|
||||
id: bigint({ mode: 'number' }).notNull(),
|
||||
version: int().default(0).notNull(),
|
||||
name: varchar({ length: 32 }).notNull(),
|
||||
profileKey: varchar('profile_key', { length: 255 }).notNull(),
|
||||
description: varchar({ length: 255 }),
|
||||
content: varchar({ length: 255 }),
|
||||
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_user_profile_id' }),
|
||||
unique('uniq_name').on(table.name),
|
||||
unique('uniq_profile_key').on(table.profileKey),
|
||||
],
|
||||
);
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.id], name: "sys_user_field_value_id"}),
|
||||
unique("uniq_user_field").on(table.userId, table.fieldId),
|
||||
]);
|
||||
|
@ -10,6 +10,10 @@ async function constData(fastify, options) {
|
||||
DISTRIBUTED_LOCK_PREFIX: {
|
||||
// 注册用户
|
||||
REGISTER_USER: 'REGISTER_USER:',
|
||||
// 新增模块
|
||||
CREATE_MODULE: 'CREATE_MODULE:',
|
||||
// 编辑模块
|
||||
UPDATE_MODULE: 'UPDATE_MODULE:',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { hash } from 'bcrypt';
|
||||
import { hash, compare } from 'bcrypt';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
// 加密
|
||||
async function encryption(fastify, options) {
|
||||
// 注册swagger插件(新版配置方式)
|
||||
fastify.log.warn('Register Encryption Plugin!');
|
||||
// 初始化时注册插件
|
||||
// 注册hash插件(新版配置方式)
|
||||
// 相同密码不同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!');
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ function errorHandler(fastify) {
|
||||
|
||||
fastify.setErrorHandler((error, request, reply) => {
|
||||
// 记录原始错误
|
||||
// fastify.log.error(`[ErrorHandler] ${error.message}`, error);
|
||||
fastify.log.trace(error);
|
||||
|
||||
console.log('AAAA1validation', error.validation);
|
||||
console.log('AAAA2statusCode', error.statusCode);
|
||||
@ -22,7 +22,7 @@ function errorHandler(fastify) {
|
||||
// 没有请求体
|
||||
const bodyError = error.validation.find(
|
||||
e =>
|
||||
e.keyword === 'type' ||
|
||||
e.keyword === 'type' && e.validationContext == 'body' ||
|
||||
(e.keyword === 'required' && e.validationContext == 'body'),
|
||||
);
|
||||
|
||||
|
@ -6,6 +6,7 @@ import encryption from '#plugins/encryption/index';
|
||||
import snowflake from '#plugins/snowflake/index';
|
||||
import redis from '#plugins/redis/index';
|
||||
import conseData from '#plugins/const/index';
|
||||
import jwt from '#plugins/jwt/index';
|
||||
|
||||
async function plugin(fastify, opts) {
|
||||
fastify.log.warn('Register Global Plugin!');
|
||||
@ -26,6 +27,8 @@ async function plugin(fastify, opts) {
|
||||
// 注册错误处理工具
|
||||
await fastify.register(import('@fastify/sensible'));
|
||||
fastify.register(errorHandler);
|
||||
// jwt
|
||||
await fastify.register(jwt);
|
||||
|
||||
fastify.log.warn('Register Global Plugin complete!');
|
||||
}
|
||||
|
48
src/plugins/jwt/index.js
Normal file
48
src/plugins/jwt/index.js
Normal 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);
|
@ -11,6 +11,18 @@ async function swagger(fastify, options) {
|
||||
version: fastify.config.version,
|
||||
description: fastify.config.description,
|
||||
},
|
||||
// 新增全局安全配置
|
||||
components: {
|
||||
securitySchemes: {
|
||||
apiKey: {
|
||||
type: 'apiKey',
|
||||
name: 'x-api-key',
|
||||
in: 'header'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 应用安全到所有路由
|
||||
security: [{ apiKey: [] }]
|
||||
},
|
||||
// staticCSP: true, // 启用安全策略
|
||||
exposeRoute: true, // 自动暴露路由
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { testSchema } from '#src/schema/test.schema';
|
||||
import { testService } from '#src/services/test.service';
|
||||
import userRoute from '#routes/user.route';
|
||||
import moduleRoute from '#routes/module.route';
|
||||
|
||||
export default async function routes(fastify, options) {
|
||||
// 定义一个GET请求的路由,路径为根路径
|
||||
@ -10,8 +11,13 @@ export default async function routes(fastify, options) {
|
||||
});
|
||||
fastify.get('/test', testSchema, testService);
|
||||
|
||||
// 注册子路由
|
||||
// 注册子路由 -------- 用户
|
||||
fastify.register(userRoute, { prefix: '/user' });
|
||||
// 注册子路由 -------- 模块
|
||||
fastify.register(moduleRoute, { prefix: '/module' });
|
||||
// 注册子路由 -------- 权限
|
||||
|
||||
// 注册子路由 -------- 角色
|
||||
|
||||
fastify.route({
|
||||
method: 'POST',
|
||||
|
18
src/routes/module.route.js
Normal file
18
src/routes/module.route.js
Normal 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. 排序
|
||||
}
|
@ -1,55 +1,11 @@
|
||||
import { userDefaultSchema, userRegisterSchema } from '#src/schema/user.schema';
|
||||
import { userDefaultService } from '#src/services/user.service';
|
||||
import { userRegisterService } from '#src/services/user/user.service';
|
||||
import { userDefaultSchema, userRegisterSchema, userLoginSchema, userRefreshTokenSchema } from '#src/schema/user.schema';
|
||||
import { userRegisterService, userLoginService, refreshTokenService } from '#src/services/user/user.service';
|
||||
|
||||
export default async function userRoute(fastify, options) {
|
||||
// 新用户注册
|
||||
// 1. 新用户注册 25-03/25
|
||||
fastify.post('/register', { schema: userRegisterSchema }, userRegisterService);
|
||||
|
||||
fastify.get('/', userDefaultSchema, userDefaultService);
|
||||
/**
|
||||
*
|
||||
* 个人接口
|
||||
*
|
||||
*/
|
||||
// 注册
|
||||
// 登录
|
||||
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);
|
||||
/**
|
||||
*
|
||||
* 管理接口
|
||||
*
|
||||
*/
|
||||
|
||||
// 获取用户列表
|
||||
|
||||
// 获取用户详细信息
|
||||
|
||||
// 创建用户
|
||||
|
||||
// 更新用户信息
|
||||
|
||||
// 批量删除用户
|
||||
|
||||
// 批量重置用户密码
|
||||
|
||||
// 批量启用用户
|
||||
|
||||
// 批量禁用用户
|
||||
|
||||
/**
|
||||
*
|
||||
* 拓展字段
|
||||
*
|
||||
*/
|
||||
|
||||
// 获取拓展字段列表
|
||||
// 2. 用户登录 25-03/26
|
||||
fastify.post('/login', { schema: userLoginSchema }, userLoginService);
|
||||
// 3. 刷新token 25-03/27
|
||||
fastify.post('/refreshToken', { schema: userRefreshTokenSchema }, refreshTokenService);
|
||||
}
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { isTrim } from '#utils/ajv/method';
|
||||
import { isLowerCase, isTrim } from '#utils/ajv/method';
|
||||
import { is } from 'drizzle-orm';
|
||||
|
||||
// 原子schema定义
|
||||
export const username = {
|
||||
type: 'string',
|
||||
minLength: 4,
|
||||
maxLength: 20,
|
||||
maxLength: 32,
|
||||
pattern: '^[a-z][a-z0-9_]*$',
|
||||
isLowerCase: true,
|
||||
isTrim: true,
|
||||
description: '用户名(4-32位小写字母开头,允许数字和下划线)',
|
||||
errorMessage: {
|
||||
minLength: '用户名至少需要4个字符',
|
||||
maxLength: '用户名不能超过20个字符',
|
||||
maxLength: '用户名不能超过32个字符',
|
||||
pattern: '必须全部小写,且只能包含字母、数字和下划线,并以字母开头',
|
||||
},
|
||||
};
|
||||
@ -20,6 +21,7 @@ export const email = {
|
||||
type: 'string',
|
||||
format: 'email',
|
||||
maxLength: 128,
|
||||
description: '用户电子邮箱地址',
|
||||
errorMessage: {
|
||||
format: '邮箱格式不正确',
|
||||
maxLength: '邮箱不能超过128个字符',
|
||||
@ -31,9 +33,102 @@ export const password = {
|
||||
minLength: 8,
|
||||
maxLength: 128,
|
||||
pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$',
|
||||
description: '密码需包含大小写字母、数字和特殊符号,8-128位',
|
||||
errorMessage: {
|
||||
minLength: '密码长度至少8位',
|
||||
maxLength: '密码不能超过128个字符',
|
||||
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(禁用)'
|
||||
}
|
||||
}
|
||||
}
|
61
src/schema/error.atomSchema.js
Normal file
61
src/schema/error.atomSchema.js
Normal 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
234
src/schema/module.schema.js
Normal 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: [] }]
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import { email, username, password } from '#schema/atomSchema';
|
||||
import errorAtomSchema from '#schema/error.atomSchema';
|
||||
// 默认schema
|
||||
export const userDefaultSchema = {};
|
||||
|
||||
@ -83,14 +84,7 @@ export const userRegisterSchema = {
|
||||
message: { type: 'string' }
|
||||
}
|
||||
},
|
||||
400: {
|
||||
description: '请求参数错误',
|
||||
type: 'object',
|
||||
properties: {
|
||||
code: { type: 'number', enum: [400] },
|
||||
error: { type: 'string' },
|
||||
},
|
||||
},
|
||||
400: errorAtomSchema['400'],
|
||||
409: {
|
||||
description: '用户名/邮箱已存在',
|
||||
type: 'object',
|
||||
@ -102,3 +96,134 @@ export const userRegisterSchema = {
|
||||
},
|
||||
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本身
|
||||
};
|
||||
|
||||
|
145
src/services/module/module.db.js
Normal file
145
src/services/module/module.db.js
Normal 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)
|
||||
}
|
126
src/services/module/module.service.js
Normal file
126
src/services/module/module.service.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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(),
|
||||
});
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { sysUser } from '#entity';
|
||||
import { sysUser, sysUserAuth, sysUserAuthHistory } from '#entity';
|
||||
import { or, eq } from 'drizzle-orm';
|
||||
// 查重用户名和邮箱
|
||||
export async function checkExistsUsernameAndEmail(username, email) {
|
||||
@ -15,18 +15,66 @@ export async function checkExistsUsernameAndEmail(username, email) {
|
||||
return result.length > 0; // 正确判断结果集是否为空
|
||||
}
|
||||
// 个人注册账户
|
||||
export async function registerAccountForMyself(userData) {
|
||||
const result = await this.db.insert(sysUser).values({
|
||||
username: userData.username,
|
||||
email: userData.email,
|
||||
password: userData.password, // 实际应存储加密后的密码
|
||||
userId: userData.userId, // 使用雪花算法生成唯一ID
|
||||
pid: 0, // 个人注册账户父节点ID为0
|
||||
createdBy: 0, // 个人注册账户创建者ID为0
|
||||
})
|
||||
export async function registerAccount(userData) {
|
||||
const result = await this.db.insert(sysUser).values(userData)
|
||||
const [newUser] = await this.db
|
||||
.select()
|
||||
.from(sysUser)
|
||||
.where(eq(sysUser.userId, userData.userId));
|
||||
console.log('AA')
|
||||
await setPassword.call(this, userData.userId, userData.password)
|
||||
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;
|
||||
}
|
||||
|
@ -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) {
|
||||
// 1. 获取已验证的参数
|
||||
@ -7,40 +12,42 @@ export async function userRegisterService(request, reply) {
|
||||
// 2. 分布式锁
|
||||
// 生成分布式锁 key
|
||||
const lockKey = `${this.const.DISTRIBUTED_LOCK_PREFIX.REGISTER_USER}${username}:${email}`;
|
||||
|
||||
const userId = this.snowflake();
|
||||
const lockIdentifier = userId;
|
||||
// 生成唯一标识符(例如,使用Snowflake算法)
|
||||
const lockIdentifier = this.snowflake();
|
||||
// 生成新用户插入数据
|
||||
const newUserInsertData = {
|
||||
username,
|
||||
email,
|
||||
password: await this.hash(password),
|
||||
userId: lockIdentifier,
|
||||
pid: 0, // 个人注册账户父节点ID为0
|
||||
createdBy: 0, // 个人注册账户创建者ID为0
|
||||
}
|
||||
// 续锁定时器
|
||||
let renewInterval;
|
||||
|
||||
try {
|
||||
// 尝试获取分布式锁(NX: 仅当key不存在时设置,EX: 过期时间5秒)
|
||||
// 3. 尝试获取分布式锁(NX: 仅当key不存在时设置,EX: 过期时间5秒)
|
||||
const locked = await this.redis.SET(lockKey, lockIdentifier, { NX: true, EX: 5 });
|
||||
if (!locked) {
|
||||
throw this.httpErrors.tooManyRequests('操作正在进行,请稍后重试');
|
||||
}
|
||||
if (!locked) throw this.httpErrors.tooManyRequests('操作正在进行,请稍后重试');
|
||||
|
||||
// 启动续锁定时器(每3秒续期一次)
|
||||
// 4. 启动续锁定时器(每3秒续期一次)
|
||||
renewInterval = setInterval(async () => {
|
||||
const currentVal = await this.redis.GET(lockKey);
|
||||
if (currentVal === lockIdentifier) {
|
||||
await this.redis.EXPIRE(lockKey, 5); // 重置过期时间
|
||||
}
|
||||
}, 3000);
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
// 2. 检测用户名和邮箱是否已存在
|
||||
// 5. 检测用户名和邮箱是否已存在
|
||||
const exists = await checkExistsUsernameAndEmail.call(this, username, email);
|
||||
if (exists) {
|
||||
throw this.httpErrors.conflict('用户名或邮箱已存在');
|
||||
}
|
||||
|
||||
// 3. 注册用户
|
||||
const newUser = await registerAccountForMyself.call(this, {
|
||||
username,
|
||||
email,
|
||||
password: await this.hash(password),
|
||||
userId
|
||||
});
|
||||
reply.code(201) // <-- 关键修改
|
||||
// 6. 注册用户
|
||||
const newUser = await registerAccount.call(this, newUserInsertData);
|
||||
reply
|
||||
.code(201)
|
||||
.send({
|
||||
code: 201,
|
||||
data: newUser,
|
||||
@ -49,11 +56,111 @@ export async function userRegisterService(request, reply) {
|
||||
} finally {
|
||||
if (renewInterval) clearInterval(renewInterval);
|
||||
// 释放锁逻辑(移除Lua脚本)
|
||||
const currentVal = await this.redis.GET(lockKey);
|
||||
if (currentVal === lockIdentifier) {
|
||||
await this.redis.DEL(lockKey);
|
||||
if (await this.redis.GET(lockKey) === 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;
|
||||
}
|
||||
}
|
@ -14,67 +14,52 @@ import Ajv from 'ajv';
|
||||
// ajv错误消息回应
|
||||
import ajvErrors from 'ajv-errors';
|
||||
|
||||
// // 将字符串转化为全小写
|
||||
// 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({
|
||||
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){
|
||||
ajv.addKeyword({
|
||||
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添加自定义的参数校验规则
|
||||
function customAjv(fastify, options) {
|
||||
// 创建一个新的 AJV 实例
|
||||
// 创建一个新的 AJV 实例
|
||||
const ajv = new Ajv({
|
||||
removeAdditional: true,
|
||||
|
31
yuheng.sql
31
yuheng.sql
@ -1,5 +1,5 @@
|
||||
CREATE TABLE `sys_dict` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`id` BIGINT NOT NULL COMMENT 'ID',
|
||||
`version` INT NOT NULL DEFAULT 0,
|
||||
`pid` BIGINT NOT NULL COMMENT '上级ID',
|
||||
`module` tinyint NULL COMMENT '模块',
|
||||
@ -17,7 +17,7 @@ CREATE TABLE `sys_dict` (
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '字典表';
|
||||
|
||||
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',
|
||||
`org_name` varchar(255) NULL COMMENT '组织名称',
|
||||
`org_code` varchar(128) NULL COMMENT '组织编码',
|
||||
@ -35,7 +35,7 @@ CREATE TABLE `sys_organization` (
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '组织表';
|
||||
|
||||
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,
|
||||
`org_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 '组织负责人表';
|
||||
|
||||
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',
|
||||
`perm_name` 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 '权限表';
|
||||
|
||||
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',
|
||||
`perm_id` BIGINT NOT NULL COMMENT '权限ID',
|
||||
`created_by` bigint NOT NULL COMMENT '创建人',
|
||||
@ -85,7 +85,7 @@ CREATE TABLE `sys_re_role_permission` (
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户组织关系表';
|
||||
|
||||
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',
|
||||
`org_id` BIGINT NOT NULL COMMENT '组织ID',
|
||||
`version` INT NOT NULL DEFAULT 0,
|
||||
@ -98,7 +98,7 @@ CREATE TABLE `sys_re_user_organization` (
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户组织关系表';
|
||||
|
||||
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',
|
||||
`role_id` BIGINT NOT NULL COMMENT '角色ID',
|
||||
`version` INT NOT NULL DEFAULT 0,
|
||||
@ -111,7 +111,7 @@ CREATE TABLE `sys_re_user_role` (
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户角色关联表';;
|
||||
|
||||
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',
|
||||
`role_name` 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` (
|
||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
||||
`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 '过期时间',
|
||||
PRIMARY KEY (`user_id`)
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户密码表';
|
||||
|
||||
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',
|
||||
`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`)
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '历史密码表';
|
||||
|
||||
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,
|
||||
`field_name` 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 '用户拓展字段定义表';
|
||||
|
||||
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',
|
||||
`field_id` int NOT NULL COMMENT '字段ID',
|
||||
`value` varchar(4096) NULL COMMENT '用户拓展字段值',
|
||||
@ -196,7 +196,7 @@ CREATE TABLE `sys_user_field_value` (
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户拓展字段记录表';
|
||||
|
||||
CREATE TABLE `sys_profile` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '系统配置自增主键',
|
||||
`id` BIGINT NOT NULL COMMENT '系统配置自增主键',
|
||||
`version` INT NOT NULL DEFAULT 0,
|
||||
`name` varchar(32) NOT NULL COMMENT '系统配置名称',
|
||||
`profile_key` varchar(255) NOT NULL COMMENT '系统配置记录Key',
|
||||
@ -212,7 +212,7 @@ CREATE TABLE `sys_profile` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '系统配置';
|
||||
|
||||
CREATE TABLE `sys_module` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '系统模块自增主键',
|
||||
`id` INT NOT NULL AUTO_INCREMENT COMMENT '系统模块自增主键',
|
||||
`version` INT NOT NULL DEFAULT 0,
|
||||
`name` varchar(32) NOT NULL COMMENT '系统模块名称',
|
||||
`module_key` varchar(255) NOT NULL COMMENT '系统模块记录Key',
|
||||
@ -224,6 +224,5 @@ CREATE TABLE `sys_module` (
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE `uniq_name` (`name`),
|
||||
UNIQUE `uniq_module_key` (`module_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '系统模块';
|
Loading…
Reference in New Issue
Block a user