From 81953dd44c478562e2433a7bfd96fa7b244d6af5 Mon Sep 17 00:00:00 2001 From: expressgy Date: Wed, 26 Mar 2025 19:07:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0JWT=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E5=92=8C=E6=A8=A1=E5=9D=97=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增JWT认证插件,支持用户登录、刷新Token功能 - 添加系统模块管理功能,包括模块的创建、更新和查询 - 更新用户服务,支持用户注册、登录和Token刷新 - 修改数据库表结构,添加模块相关表和字段 - 更新文档,添加JWT认证和模块管理相关说明 --- .env | 10 +- SQL/relations.ts | 5 +- SQL/schema.ts | 573 +++++++++++-------------- config/index.js | 16 +- doc/routerDocs/router.md | 2 + package-lock.json | 141 +++++++ package.json | 1 + src/entities/schema.js | 574 +++++++++++--------------- src/plugins/const/index.js | 4 + src/plugins/encryption/index.js | 8 +- src/plugins/error/errorHandler.js | 4 +- src/plugins/index.js | 3 + src/plugins/jwt/index.js | 48 +++ src/plugins/swagger/index.js | 12 + src/routes/index.js | 8 +- src/routes/module.route.js | 18 + src/routes/user.route.js | 58 +-- src/schema/atomSchema.js | 101 ++++- src/schema/error.atomSchema.js | 61 +++ src/schema/module.schema.js | 234 +++++++++++ src/schema/user.schema.js | 141 ++++++- src/services/module/module.db.js | 145 +++++++ src/services/module/module.service.js | 126 ++++++ src/services/user.service.js | 30 -- src/services/user/user.db.js | 68 ++- src/services/user/user.service.js | 157 +++++-- src/utils/ajv/method.js | 83 ++-- yuheng.sql | 31 +- 28 files changed, 1811 insertions(+), 851 deletions(-) create mode 100644 src/plugins/jwt/index.js create mode 100644 src/routes/module.route.js create mode 100644 src/schema/error.atomSchema.js create mode 100644 src/schema/module.schema.js create mode 100644 src/services/module/module.db.js create mode 100644 src/services/module/module.service.js delete mode 100644 src/services/user.service.js diff --git a/.env b/.env index 49a821e..da16a63 100644 --- a/.env +++ b/.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 diff --git a/SQL/relations.ts b/SQL/relations.ts index 34c5368..80768e2 100644 --- a/SQL/relations.ts +++ b/SQL/relations.ts @@ -1,2 +1,3 @@ -import { relations } from 'drizzle-orm/relations'; -import {} from './schema'; +import { relations } from "drizzle-orm/relations"; +import { } from "./schema"; + diff --git a/SQL/schema.ts b/SQL/schema.ts index dfefdf3..5d8f415 100644 --- a/SQL/schema.ts +++ b/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(), - version: int().default(0).notNull(), - pid: bigint({ mode: 'number' }).notNull(), - module: tinyint(), - dictKey: varchar('dict_key', { length: 255 }), - value: varchar({ length: 255 }), - description: varchar({ length: 255 }), - sort: int().default(0).notNull(), - status: int().notNull(), - createdBy: bigint('created_by', { mode: 'number' }).notNull(), - updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), - createdAt: datetime('created_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - updatedAt: datetime('updated_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_dict_id' }), - unique('uniq_dict_key').on(table.dictKey, table.pid), - ], -); +export const sysDict = mysqlTable("sys_dict", { + id: bigint({ mode: "number" }).notNull(), + version: int().default(0).notNull(), + pid: bigint({ mode: "number" }).notNull(), + module: tinyint(), + dictKey: varchar("dict_key", { length: 255 }), + value: varchar({ length: 255 }), + description: varchar({ length: 255 }), + sort: int().default(0).notNull(), + status: int().notNull(), + createdBy: bigint("created_by", { mode: "number" }).notNull(), + updatedBy: bigint("updated_by", { mode: "number" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.id], name: "sys_dict_id"}), + unique("uniq_dict_key").on(table.dictKey, table.pid), +]); -export const sysOrganization = mysqlTable( - '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(), - description: varchar({ length: 255 }), - sort: int().default(0).notNull(), - status: int().notNull(), - createdBy: bigint('created_by', { mode: 'number' }).notNull(), - updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), - createdAt: datetime('created_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - updatedAt: datetime('updated_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - }, - table => [ - primaryKey({ columns: [table.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 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" }), + 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_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(), - version: int().default(0).notNull(), - orgId: bigint('org_id', { mode: 'number' }).notNull(), - userId: bigint('user_id', { mode: 'number' }).notNull(), - rank: 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(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_organization_manager_id' }), - unique('uniq_org_user').on(table.orgId, table.userId), - ], -); +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 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(), - url: varchar({ length: 255 }), - avatarUrl: varchar('avatar_url', { length: 255 }), - description: varchar({ length: 255 }), - 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(), - }, - 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 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(), + rank: int().notNull(), + status: int().notNull(), + description: 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_organization_manager_id"}), + unique("uniq_org_user").on(table.orgId, table.userId), +]); -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 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 }), + description: varchar({ length: 255 }), + 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" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.permId], name: "sys_permission_perm_id"}), + unique("uniq_pid_name").on(table.permName, table.pid), + unique("uniq_perm_key").on(table.permKey), +]); -export const 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(), - 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(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_re_user_organization_id' }), - unique('uniq_user_org').on(table.userId, table.orgId), - ], -); +export const sysProfile = mysqlTable("sys_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" }), + 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_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(), - 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(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_re_user_role_id' }), - unique('uniq_user_role').on(table.userId, table.roleId), - ], -); +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 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(), - 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(), - }, - table => [ - primaryKey({ columns: [table.roleId], name: 'sys_role_role_id' }), - unique('uniq_role_pid').on(table.roleName, table.pid), - ], -); +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" }), + 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), +]); -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'), - 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(), - }, - table => [ - primaryKey({ columns: [table.userId], name: 'sys_user_user_id' }), - unique('uniq_username').on(table.username), - unique('uniq_email').on(table.email), - ], -); +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 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' }), - }, - table => [primaryKey({ columns: [table.userId], name: 'sys_user_auth_user_id' })], -); +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" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.roleId], name: "sys_role_role_id"}), + unique("uniq_role_pid").on(table.roleName, table.pid), +]); -export const sysUserAuthHistory = mysqlTable( - '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(), - }, - table => [primaryKey({ columns: [table.id], name: 'sys_user_auth_history_id' })], -); +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"), + status: tinyint().default(0).notNull(), + createdBy: bigint("created_by", { mode: "number" }).notNull(), + updatedBy: bigint("updated_by", { mode: "number" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.userId], name: "sys_user_user_id"}), + unique("uniq_username").on(table.username), + unique("uniq_email").on(table.email), +]); -export const sysUserFieldDefinition = mysqlTable( - 'sys_user_field_definition', - { - fieldId: bigint('field_id', { mode: 'number' }).autoincrement().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(), - limit: int(), - description: varchar({ length: 255 }), - defaultValue: varchar('default_value', { length: 255 }), - defaultOptions: varchar('default_options', { length: 255 }), - sort: int().default(0).notNull(), - status: int().notNull(), - createdBy: bigint('created_by', { mode: 'number' }).notNull(), - updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), - createdAt: datetime('created_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - updatedAt: datetime('updated_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - }, - table => [ - primaryKey({ columns: [table.fieldId], name: 'sys_user_field_definition_field_id' }), - unique('uniq_field_name').on(table.fieldName), - unique('uniq_field_key').on(table.fieldKey), - ], -); +export const 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"}), +]); -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(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_user_field_value_id' }), - unique('uniq_user_field').on(table.userId, table.fieldId), - ], -); +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"}), +]); -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), - ], -); +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(), + limit: int(), + description: varchar({ length: 255 }), + defaultValue: varchar("default_value", { length: 255 }), + defaultOptions: varchar("default_options", { length: 255 }), + sort: int().default(0).notNull(), + status: int().notNull(), + createdBy: bigint("created_by", { mode: "number" }).notNull(), + updatedBy: bigint("updated_by", { mode: "number" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.fieldId], name: "sys_user_field_definition_field_id"}), + unique("uniq_field_name").on(table.fieldName), + unique("uniq_field_key").on(table.fieldKey), +]); + +export const sysUserFieldValue = mysqlTable("sys_user_field_value", { + id: bigint({ mode: "number" }).autoincrement().notNull(), + userId: bigint("user_id", { mode: "number" }).notNull(), + fieldId: int("field_id").notNull(), + value: varchar({ length: 4096 }), + createdBy: bigint("created_by", { mode: "number" }).notNull(), + updatedBy: bigint("updated_by", { mode: "number" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.id], name: "sys_user_field_value_id"}), + unique("uniq_user_field").on(table.userId, table.fieldId), +]); diff --git a/config/index.js b/config/index.js index aaae49f..0d8527a 100644 --- a/config/index.js +++ b/config/index.js @@ -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' + ], + }, }; // 环境特定配置 diff --git a/doc/routerDocs/router.md b/doc/routerDocs/router.md index dc95231..4d766e5 100644 --- a/doc/routerDocs/router.md +++ b/doc/routerDocs/router.md @@ -12,6 +12,8 @@ 2. 用户登录 + 2.1 刷新Token + 3. 用户登出 4. 用户注销 diff --git a/package-lock.json b/package-lock.json index dedca1b..cf78279 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 4d57de1..91e8859 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/entities/schema.js b/src/entities/schema.js index e3cbf82..f20feff 100644 --- a/src/entities/schema.js +++ b/src/entities/schema.js @@ -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(), - version: int().default(0).notNull(), - pid: bigint({ mode: 'number' }).notNull(), - module: tinyint(), - dictKey: varchar('dict_key', { length: 255 }), - value: varchar({ length: 255 }), - description: varchar({ length: 255 }), - sort: int().default(0).notNull(), - status: int().notNull(), - createdBy: bigint('created_by', { mode: 'number' }).notNull(), - updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), - createdAt: datetime('created_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - updatedAt: datetime('updated_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_dict_id' }), - unique('uniq_dict_key').on(table.dictKey, table.pid), - ], -); +import { mysqlTable, primaryKey, unique, int, tinyint, varchar, datetime } from "drizzle-orm/mysql-core" +import { sql } from "drizzle-orm" -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' }).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.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 sysDict = mysqlTable("sys_dict", { + id: bigint({ mode: "number" }).notNull(), + version: int().default(0).notNull(), + pid: bigint({ mode: "number" }).notNull(), + module: tinyint(), + dictKey: varchar("dict_key", { length: 255 }), + value: varchar({ length: 255 }), + description: varchar({ length: 255 }), + sort: int().default(0).notNull(), + status: int().notNull(), + createdBy: bigint("created_by", { mode: "number" }).notNull(), + updatedBy: bigint("updated_by", { mode: "number" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.id], name: "sys_dict_id"}), + unique("uniq_dict_key").on(table.dictKey, table.pid), +]); -export const 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(), - rank: 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(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_organization_manager_id' }), - unique('uniq_org_user').on(table.orgId, table.userId), - ], -); +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" }), + 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_module_id"}), + unique("uniq_name").on(table.name), + unique("uniq_module_key").on(table.moduleKey), +]); -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 }), - description: varchar({ length: 255 }), - 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(), - }, - 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 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 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 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(), + rank: int().notNull(), + status: int().notNull(), + description: 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_organization_manager_id"}), + unique("uniq_org_user").on(table.orgId, table.userId), +]); -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(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_re_user_organization_id' }), - unique('uniq_user_org').on(table.userId, table.orgId), - ], -); +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 }), + description: varchar({ length: 255 }), + 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" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.permId], name: "sys_permission_perm_id"}), + unique("uniq_pid_name").on(table.permName, table.pid), + unique("uniq_perm_key").on(table.permKey), +]); -export const 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' }).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_user_role_id' }), - unique('uniq_user_role').on(table.userId, table.roleId), - ], -); +export const sysProfile = mysqlTable("sys_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" }), + 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_profile_id"}), + unique("uniq_name").on(table.name), + unique("uniq_profile_key").on(table.profileKey), +]); -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(), - }, - table => [ - primaryKey({ columns: [table.roleId], name: 'sys_role_role_id' }), - unique('uniq_role_pid').on(table.roleName, table.pid), - ], -); +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 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'), - 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(), - }, - table => [ - primaryKey({ columns: [table.userId], name: 'sys_user_user_id' }), - unique('uniq_username').on(table.username), - unique('uniq_email').on(table.email), - ], -); +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" }), + 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), +]); -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' }), - }, - table => [primaryKey({ columns: [table.userId], name: 'sys_user_auth_user_id' })], -); +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 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(), - }, - table => [primaryKey({ columns: [table.id], name: 'sys_user_auth_history_id' })], -); +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" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.roleId], name: "sys_role_role_id"}), + unique("uniq_role_pid").on(table.roleName, table.pid), +]); -export const sysUserFieldDefinition = mysqlTable( - '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(), - limit: int(), - description: varchar({ length: 255 }), - defaultValue: varchar('default_value', { length: 255 }), - defaultOptions: varchar('default_options', { length: 255 }), - sort: int().default(0).notNull(), - status: int().notNull(), - createdBy: bigint('created_by', { mode: 'number' }).notNull(), - updatedBy: bigint('updated_by', { mode: 'number' }).notNull(), - createdAt: datetime('created_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - updatedAt: datetime('updated_at', { mode: 'string' }) - .default(sql`(CURRENT_TIMESTAMP)`) - .notNull(), - }, - table => [ - primaryKey({ columns: [table.fieldId], name: 'sys_user_field_definition_field_id' }), - unique('uniq_field_name').on(table.fieldName), - unique('uniq_field_key').on(table.fieldKey), - ], -); +export const 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"), + status: tinyint().default(0).notNull(), + createdBy: bigint("created_by", { mode: "number" }).notNull(), + updatedBy: bigint("updated_by", { mode: "number" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.userId], name: "sys_user_user_id"}), + unique("uniq_username").on(table.username), + unique("uniq_email").on(table.email), +]); -export const 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(), - }, - table => [ - primaryKey({ columns: [table.id], name: 'sys_user_field_value_id' }), - unique('uniq_user_field').on(table.userId, table.fieldId), - ], -); +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"}), +]); -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), - ], -); +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"}), +]); + +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(), + limit: int(), + description: varchar({ length: 255 }), + defaultValue: varchar("default_value", { length: 255 }), + defaultOptions: varchar("default_options", { length: 255 }), + sort: int().default(0).notNull(), + status: int().notNull(), + createdBy: bigint("created_by", { mode: "number" }).notNull(), + updatedBy: bigint("updated_by", { mode: "number" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.fieldId], name: "sys_user_field_definition_field_id"}), + unique("uniq_field_name").on(table.fieldName), + unique("uniq_field_key").on(table.fieldKey), +]); + +export const sysUserFieldValue = mysqlTable("sys_user_field_value", { + id: bigint({ mode: "number" }).notNull(), + userId: bigint("user_id", { mode: "number" }).notNull(), + fieldId: int("field_id").notNull(), + value: varchar({ length: 4096 }), + createdBy: bigint("created_by", { mode: "number" }).notNull(), + updatedBy: bigint("updated_by", { mode: "number" }), + createdAt: datetime("created_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), + updatedAt: datetime("updated_at", { mode: 'string'}).default(sql`(CURRENT_TIMESTAMP)`).notNull(), +}, +(table) => [ + primaryKey({ columns: [table.id], name: "sys_user_field_value_id"}), + unique("uniq_user_field").on(table.userId, table.fieldId), +]); diff --git a/src/plugins/const/index.js b/src/plugins/const/index.js index c73a07c..604c497 100644 --- a/src/plugins/const/index.js +++ b/src/plugins/const/index.js @@ -10,6 +10,10 @@ async function constData(fastify, options) { DISTRIBUTED_LOCK_PREFIX: { // 注册用户 REGISTER_USER: 'REGISTER_USER:', + // 新增模块 + CREATE_MODULE: 'CREATE_MODULE:', + // 编辑模块 + UPDATE_MODULE: 'UPDATE_MODULE:', } } diff --git a/src/plugins/encryption/index.js b/src/plugins/encryption/index.js index e938beb..0ea5e17 100644 --- a/src/plugins/encryption/index.js +++ b/src/plugins/encryption/index.js @@ -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!'); } diff --git a/src/plugins/error/errorHandler.js b/src/plugins/error/errorHandler.js index da7987b..511f42c 100644 --- a/src/plugins/error/errorHandler.js +++ b/src/plugins/error/errorHandler.js @@ -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'), ); diff --git a/src/plugins/index.js b/src/plugins/index.js index a996e38..d169702 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -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!'); } diff --git a/src/plugins/jwt/index.js b/src/plugins/jwt/index.js new file mode 100644 index 0000000..0f7ca08 --- /dev/null +++ b/src/plugins/jwt/index.js @@ -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); \ No newline at end of file diff --git a/src/plugins/swagger/index.js b/src/plugins/swagger/index.js index 275d96a..cefd99d 100644 --- a/src/plugins/swagger/index.js +++ b/src/plugins/swagger/index.js @@ -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, // 自动暴露路由 diff --git a/src/routes/index.js b/src/routes/index.js index 1290235..80e6fc6 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -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', diff --git a/src/routes/module.route.js b/src/routes/module.route.js new file mode 100644 index 0000000..c5528b0 --- /dev/null +++ b/src/routes/module.route.js @@ -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. 排序 +} \ No newline at end of file diff --git a/src/routes/user.route.js b/src/routes/user.route.js index 1efaae6..1e407fd 100644 --- a/src/routes/user.route.js +++ b/src/routes/user.route.js @@ -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); } diff --git a/src/schema/atomSchema.js b/src/schema/atomSchema.js index 6900899..ba05d88 100644 --- a/src/schema/atomSchema.js +++ b/src/schema/atomSchema.js @@ -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(禁用)' + } + } +} \ No newline at end of file diff --git a/src/schema/error.atomSchema.js b/src/schema/error.atomSchema.js new file mode 100644 index 0000000..45ce5e7 --- /dev/null +++ b/src/schema/error.atomSchema.js @@ -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' } + } + } +}; \ No newline at end of file diff --git a/src/schema/module.schema.js b/src/schema/module.schema.js new file mode 100644 index 0000000..d95e02c --- /dev/null +++ b/src/schema/module.schema.js @@ -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: [] }] +}; \ No newline at end of file diff --git a/src/schema/user.schema.js b/src/schema/user.schema.js index eb85268..2b82c15 100644 --- a/src/schema/user.schema.js +++ b/src/schema/user.schema.js @@ -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本身 +}; + diff --git a/src/services/module/module.db.js b/src/services/module/module.db.js new file mode 100644 index 0000000..a8e311d --- /dev/null +++ b/src/services/module/module.db.js @@ -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) +} \ No newline at end of file diff --git a/src/services/module/module.service.js b/src/services/module/module.service.js new file mode 100644 index 0000000..f130a16 --- /dev/null +++ b/src/services/module/module.service.js @@ -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); + } + } +} \ No newline at end of file diff --git a/src/services/user.service.js b/src/services/user.service.js deleted file mode 100644 index 11b665c..0000000 --- a/src/services/user.service.js +++ /dev/null @@ -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(), - }); -} diff --git a/src/services/user/user.db.js b/src/services/user/user.db.js index 41c2250..288faa5 100644 --- a/src/services/user/user.db.js +++ b/src/services/user/user.db.js @@ -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; +} diff --git a/src/services/user/user.service.js b/src/services/user/user.service.js index 59fcf8e..41a6006 100644 --- a/src/services/user/user.service.js +++ b/src/services/user/user.service.js @@ -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; + } +} \ No newline at end of file diff --git a/src/utils/ajv/method.js b/src/utils/ajv/method.js index cca6b8f..4796c25 100644 --- a/src/utils/ajv/method.js +++ b/src/utils/ajv/method.js @@ -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) { +// 将字符串转化为全小写 +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) { +// 去除两端空格 +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, diff --git a/yuheng.sql b/yuheng.sql index a06f004..a0a991a 100644 --- a/yuheng.sql +++ b/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 '系统模块'; \ No newline at end of file