├── .dockerignore ├── prisma ├── migrations │ ├── 20231001074426_file │ │ └── migration.sql │ ├── 20231001034533_ │ │ └── migration.sql │ ├── 20231001041208_ │ │ └── migration.sql │ ├── 20231008085428_ │ │ └── migration.sql │ ├── 20231008085537_ │ │ └── migration.sql │ ├── 20231105152609_ │ │ └── migration.sql │ ├── 20231031131339_ │ │ └── migration.sql │ ├── 20231108150118_file_change │ │ └── migration.sql │ ├── 20231018133005_ │ │ └── migration.sql │ ├── 20231028142009_ │ │ └── migration.sql │ ├── 20231031131110_ │ │ └── migration.sql │ ├── 20231028143801_ │ │ └── migration.sql │ ├── migration_lock.toml │ ├── 20231002145741_ │ │ └── migration.sql │ ├── 20231001034449_ │ │ └── migration.sql │ ├── 20231012071747_ │ │ └── migration.sql │ ├── 20231002080515_ │ │ └── migration.sql │ ├── 20231031130254_ │ │ └── migration.sql │ ├── 20231008085656_ │ │ └── migration.sql │ ├── 20231105154011_ │ │ └── migration.sql │ ├── 20230923085258_edit_sex │ │ └── migration.sql │ ├── 20231012070141_ │ │ └── migration.sql │ ├── 20231028142702_edit_login_log_status │ │ └── migration.sql │ ├── 20231018133030_ │ │ └── migration.sql │ ├── 20230921091715_init │ │ └── migration.sql │ ├── 20231012071604_ │ │ └── migration.sql │ ├── 20231001075129_ │ │ └── migration.sql │ ├── 20231028141149_set_table_login_log │ │ └── migration.sql │ ├── 20231001041129_ │ │ └── migration.sql │ ├── 20231031112046_ │ │ └── migration.sql │ ├── 20231001034056_ │ │ └── migration.sql │ ├── 20231001075022_ │ │ └── migration.sql │ ├── 20231020045510_user_mount_role │ │ └── migration.sql │ ├── 20231031122453_reset_table │ │ └── migration.sql │ ├── 20231001074327_file │ │ └── migration.sql │ ├── 20231018131728_reset │ │ └── migration.sql │ ├── 20230924053039_add_ctime_and_utime │ │ └── migration.sql │ ├── 20231020050501_add_table_user_role │ │ └── migration.sql │ ├── 20230923085000_enrich_user_model │ │ └── migration.sql │ ├── 20231018132847_update_id │ │ └── migration.sql │ ├── 20231002075603_user_include_file │ │ └── migration.sql │ ├── 20231012063134_ │ │ └── migration.sql │ └── 20231007142628_ │ │ └── migration.sql └── schema.prisma ├── src ├── app.service.ts ├── i18n │ ├── en │ │ └── test.json │ └── zh-CN │ │ └── test.json ├── module │ ├── auth │ │ ├── interface │ │ │ └── token.d.ts │ │ ├── dto │ │ │ ├── refreshToken.dto.ts │ │ │ ├── token.dto.ts │ │ │ ├── resetPassword.dto.ts │ │ │ └── login.dto.ts │ │ ├── entities │ │ │ └── captcha.entity.ts │ │ ├── auth.module.ts │ │ ├── auth.controller.ts │ │ └── auth.service.ts │ ├── upload │ │ ├── interface │ │ │ └── fileEntity.d.ts │ │ ├── dto │ │ │ ├── update-upload.dto.ts │ │ │ └── create-upload.dto.ts │ │ ├── upload.module.ts │ │ ├── upload.controller.ts │ │ └── upload.service.ts │ ├── menu │ │ ├── dto │ │ │ ├── update-menu.dto.ts │ │ │ ├── page.dto.ts │ │ │ └── create-menu.dto.ts │ │ ├── menu.module.ts │ │ ├── menu.controller.ts │ │ └── menu.service.ts │ ├── role │ │ ├── dto │ │ │ ├── update-role.dto.ts │ │ │ ├── set-role-menu.dto.ts │ │ │ ├── role.page.dto.ts │ │ │ └── create-role.dto.ts │ │ ├── role.module.ts │ │ ├── role.controller.ts │ │ └── role.service.ts │ ├── login-log │ │ ├── dto │ │ │ └── login-login-page.dto.ts │ │ ├── login-log.module.ts │ │ ├── login-log.controller.ts │ │ └── login-log.service.ts │ ├── user │ │ ├── dto │ │ │ ├── user-vo.dto.ts │ │ │ ├── page.dto.ts │ │ │ ├── update_user.dto.ts │ │ │ └── user.dto.ts │ │ ├── user.module.ts │ │ ├── user.controller.ts │ │ └── user.service.ts │ └── cache │ │ └── cache.module.ts ├── config │ ├── token.config.ts │ ├── index.ts │ ├── mail.config.ts │ ├── minio.config.ts │ └── redis.config.ts ├── common │ ├── decorator │ │ ├── role.decorator.ts │ │ ├── not-login.decorator.ts │ │ └── auth-role.decorator.ts │ ├── middware │ │ └── logger.middleware.ts │ ├── filter │ │ └── validate.filter.ts │ ├── interceptor │ │ └── auth.interceptor.ts │ └── guard │ │ └── auth.guard.ts ├── services │ ├── prisma.service.ts │ ├── logger.service.ts │ ├── mail.service.ts │ └── rsa.service.ts ├── app.controller.ts ├── socket │ ├── socket.module.ts │ ├── interface │ │ └── message.ts │ └── socket.gateway.ts ├── utils │ ├── common │ │ ├── uuid.ts │ │ ├── error.ts │ │ ├── ip.ts │ │ └── snow-flake.ts │ └── user │ │ └── is_exist.ts ├── main.ts └── app.module.ts ├── tsconfig.build.json ├── test ├── jest-e2e.json └── app.e2e-spec.ts ├── nest-cli.json ├── .gitignore ├── .prettierrc ├── tsconfig.json ├── .vscode └── launch.json ├── .eslintrc.js ├── Dockerfile ├── LICENSE ├── docker-compose.yml ├── .github └── workflows │ └── docker-publish.yml ├── package.json ├── README.md └── meng_admin.sql /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | *.md 4 | dist -------------------------------------------------------------------------------- /prisma/migrations/20231001074426_file/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX `user_id_key` ON `user`; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231001034533_/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `user` ADD PRIMARY KEY (`id`); 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231001041208_/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `user` ADD PRIMARY KEY (`id`); 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231008085428_/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX `File_fileName_key` ON `File`; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231008085537_/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX `File_filePath_key` ON `File`; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231105152609_/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX `File_filePath_key` ON `File`; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231031131339_/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `File` MODIFY `userId` VARCHAR(191) NULL; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231108150118_file_change/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX `File_filePath_key` ON `File`; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231018133005_/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `Menu` MODIFY `parentId` VARCHAR(191) NULL; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231028142009_/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `Login_Log` MODIFY `status` VARCHAR(191) NULL; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231031131110_/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE `File` DROP FOREIGN KEY `File_userId_fkey`; 3 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | @Injectable() 3 | export class AppService { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/i18n/en/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "hello", 3 | "animals": [ 4 | "cat", 5 | "dog", 6 | "elephant" 7 | ] 8 | } -------------------------------------------------------------------------------- /src/module/auth/interface/token.d.ts: -------------------------------------------------------------------------------- 1 | export interface TokenConfig { 2 | expire: number; 3 | refreshExpire: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/i18n/zh-CN/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "你好", 3 | "animals": [ 4 | "猫", 5 | "狗", 6 | "大象" 7 | ] 8 | } -------------------------------------------------------------------------------- /prisma/migrations/20231028143801_/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `Login_Log` ADD COLUMN `createAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3); 3 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "mysql" -------------------------------------------------------------------------------- /src/config/token.config.ts: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | token: { 3 | expire: 60 * 60 * 2, 4 | refreshExpire: 60 * 60 * 24 * 7, 5 | } 6 | }) -------------------------------------------------------------------------------- /prisma/migrations/20231002145741_/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX `File_fileName_key` ON `File`; 3 | 4 | -- DropIndex 5 | DROP INDEX `File_filePath_key` ON `File`; 6 | -------------------------------------------------------------------------------- /src/common/decorator/role.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const Role = (...args: string[]) => SetMetadata('userName', args); 4 | -------------------------------------------------------------------------------- /src/common/decorator/not-login.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const NotLogin = (...args: string[]) => SetMetadata('not-login', args); 4 | -------------------------------------------------------------------------------- /src/module/upload/interface/fileEntity.d.ts: -------------------------------------------------------------------------------- 1 | export interface fileEntity { 2 | id: string 3 | filePath: string 4 | fileName: string 5 | userId: string | null 6 | createdAt: Date 7 | } -------------------------------------------------------------------------------- /src/services/prisma.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PrismaClient } from '@prisma/client'; 3 | @Injectable() 4 | export class PrismaService extends PrismaClient{ 5 | } 6 | -------------------------------------------------------------------------------- /src/module/menu/dto/update-menu.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/swagger'; 2 | import { CreateMenuDto } from './create-menu.dto'; 3 | 4 | export class UpdateMenuDto extends PartialType(CreateMenuDto) {} 5 | -------------------------------------------------------------------------------- /src/module/role/dto/update-role.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/swagger'; 2 | import { CreateRoleDto } from './create-role.dto'; 3 | 4 | export class UpdateRoleDto extends PartialType(CreateRoleDto) {} 5 | -------------------------------------------------------------------------------- /src/module/upload/dto/update-upload.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/swagger'; 2 | import { CreateUploadDto } from './create-upload.dto'; 3 | 4 | export class UpdateUploadDto extends PartialType(CreateUploadDto) {} 5 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | } 9 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/socket/socket.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { SocketGateway } from './socket.gateway'; 3 | 4 | @Module({ 5 | providers: [SocketGateway], 6 | exports: [SocketGateway] 7 | }) 8 | export class SocketModule {} 9 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import minioConfig from "./minio.config"; 2 | import redisConfig from "./redis.config"; 3 | import mailConfig from "./mail.config"; 4 | import tokenConfig from "./token.config"; 5 | export default [redisConfig, minioConfig,mailConfig,tokenConfig]; 6 | -------------------------------------------------------------------------------- /src/module/upload/dto/create-upload.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger" 2 | 3 | export class CreateUploadDto { 4 | @ApiProperty({description: '文件名'}) 5 | fileName?: string 6 | @ApiProperty({description: '文件路径'}) 7 | filePath?: string 8 | } 9 | -------------------------------------------------------------------------------- /src/module/auth/dto/refreshToken.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import {} from 'class-validator' 3 | 4 | export class RefreshTokenDTO { 5 | @ApiProperty({ 6 | description: '刷新token', 7 | }) 8 | refreshToken?: string; 9 | } 10 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "assets": [ 7 | { "include": "i18n/**/*", "watchAssets": true } 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /prisma/migrations/20231001034449_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `user` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `user` DROP PRIMARY KEY; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20231012071747_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Made the column `type` on table `Menu` required. This step will fail if there are existing NULL values in that column. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `Menu` MODIFY `type` INTEGER NOT NULL; 9 | -------------------------------------------------------------------------------- /src/module/role/dto/set-role-menu.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger" 2 | 3 | export class SetRoleMenuDto { 4 | @ApiProperty({ description: '选中的菜单id' }) 5 | checkedKeys: string[] 6 | @ApiProperty({ description: '角色id' }) 7 | roleId: string 8 | } -------------------------------------------------------------------------------- /prisma/migrations/20231002080515_/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE `File` DROP FOREIGN KEY `File_userId_fkey`; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE `File` ADD CONSTRAINT `File_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20231031130254_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[userId]` on the table `File` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX `File_userId_key` ON `File`(`userId`); 9 | -------------------------------------------------------------------------------- /prisma/migrations/20231008085656_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[filePath]` on the table `File` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX `File_filePath_key` ON `File`(`filePath`); 9 | -------------------------------------------------------------------------------- /prisma/migrations/20231105154011_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[filePath]` on the table `File` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX `File_filePath_key` ON `File`(`filePath`); 9 | -------------------------------------------------------------------------------- /prisma/migrations/20230923085258_edit_sex/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to alter the column `sex` on the `user` table. The data in that column could be lost. The data in that column will be cast from `VarChar(191)` to `Int`. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `user` MODIFY `sex` INTEGER NULL; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20231012070141_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to alter the column `parentId` on the `Menu` table. The data in that column could be lost. The data in that column will be cast from `VarChar(191)` to `Int`. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `Menu` MODIFY `parentId` INTEGER NULL; 9 | -------------------------------------------------------------------------------- /src/module/login-log/dto/login-login-page.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger" 2 | 3 | export class LoginLogDto { 4 | @ApiProperty({description:'页码'}) 5 | page: string 6 | @ApiProperty({description:'每页数量'}) 7 | size: string 8 | @ApiProperty({description:'用户名'}) 9 | userName?: string 10 | } -------------------------------------------------------------------------------- /src/module/user/dto/user-vo.dto.ts: -------------------------------------------------------------------------------- 1 | import { OmitType } from "@nestjs/mapped-types"; 2 | import { ApiProperty } from "@nestjs/swagger"; 3 | import { UserDto } from "../dto/user.dto"; 4 | export class UserVo extends OmitType(UserDto, ['password', 'emailCaptcha', 'avatar'] ){ 5 | @ApiProperty({description: '图片路径'}) 6 | avatar?: string; 7 | } -------------------------------------------------------------------------------- /src/config/mail.config.ts: -------------------------------------------------------------------------------- 1 | export default () =>({ 2 | mail: { 3 | host: 'smtp.qq.com', 4 | port: 465, 5 | secure: true, 6 | auth: { 7 | user: '1374744754@qq.com', 8 | pass: '' 9 | } 10 | }, 11 | mailCaptchaExpire: { 12 | expire: 60 * 30 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /src/module/auth/entities/captcha.entity.ts: -------------------------------------------------------------------------------- 1 | import { Exclude } from "class-transformer"; 2 | import { CaptchaType } from "../dto/login.dto"; 3 | 4 | export class CaptchaEntity { 5 | @Exclude() 6 | text: string; 7 | constructor(partial: Partial) { 8 | Object.assign(this, partial); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /prisma/migrations/20231028142702_edit_login_log_status/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to alter the column `status` on the `Login_Log` table. The data in that column could be lost. The data in that column will be cast from `VarChar(191)` to `TinyInt`. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `Login_Log` MODIFY `status` BOOLEAN NULL; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20231018133030_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `Role_Menu` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `Role_Menu` DROP PRIMARY KEY, 9 | MODIFY `id` VARCHAR(191) NOT NULL, 10 | ADD PRIMARY KEY (`id`); 11 | -------------------------------------------------------------------------------- /src/module/menu/dto/page.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import {} from 'class-validator' 3 | 4 | export class pageDto { 5 | @ApiProperty({description:'页码'}) 6 | page: string | number 7 | @ApiProperty({description:'每页数量'}) 8 | size: string | number 9 | @ApiProperty({description:'路由'}) 10 | route?: string 11 | } -------------------------------------------------------------------------------- /prisma/migrations/20230921091715_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE `user` ( 3 | `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, 4 | `username` VARCHAR(191) NOT NULL, 5 | `password` VARCHAR(191) NOT NULL, 6 | 7 | UNIQUE INDEX `user_username_key`(`username`), 8 | PRIMARY KEY (`id`) 9 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 10 | -------------------------------------------------------------------------------- /src/common/decorator/auth-role.decorator.ts: -------------------------------------------------------------------------------- 1 | import { UseGuards, applyDecorators } from '@nestjs/common'; 2 | import { AuthGuard } from '../guard/auth.guard'; 3 | import { Role } from './role.decorator'; 4 | 5 | export const RoleAuth = (...args: string[]) => { 6 | return applyDecorators( 7 | Role(...args), 8 | UseGuards(AuthGuard), 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /src/module/menu/menu.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MenuController } from './menu.controller'; 3 | import { MenuService } from './menu.service' 4 | import { PrismaService } from 'src/services/prisma.service'; 5 | 6 | @Module({ 7 | controllers: [MenuController], 8 | providers: [MenuService, PrismaService,], 9 | }) 10 | export class MenuModule {} 11 | -------------------------------------------------------------------------------- /prisma/migrations/20231012071604_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `updateAt` to the `Menu` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE `Menu` ADD COLUMN `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 9 | ADD COLUMN `updateAt` DATETIME(3) NOT NULL; 10 | -------------------------------------------------------------------------------- /src/module/role/dto/role.page.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger" 2 | 3 | export class RolePageDto { 4 | @ApiProperty({description: '页码'}) 5 | page: string 6 | @ApiProperty({description: '当前页数'}) 7 | size: string 8 | @ApiProperty({description: '角色名称'}) 9 | name?: string 10 | @ApiProperty({description: '角色代码'}) 11 | code?: string 12 | } -------------------------------------------------------------------------------- /src/utils/common/uuid.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | /** 3 | * @description 生成nanoid 4 | * @date 10/29/2023 5 | */ 6 | export const uuid = () => { 7 | return nanoid(); 8 | }; 9 | 10 | /** 11 | * @description 生成随机四位数 12 | * @date 10/25/2023 13 | */ 14 | export const generateRandomCode = () => { 15 | return Math.floor(Math.random() * 9000) + 1000; 16 | }; 17 | -------------------------------------------------------------------------------- /src/module/user/dto/page.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | export class pageDto { 3 | @ApiProperty({description: '页码'}) 4 | page: string | number 5 | @ApiProperty({description: '每页条数'}) 6 | size: string | number 7 | @ApiProperty({description: '昵称'}) 8 | nickName? :string 9 | @ApiProperty({description: '手机号'}) 10 | phoneNumber?: string 11 | } -------------------------------------------------------------------------------- /src/module/login-log/login-log.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { LoginLogController } from './login-log.controller'; 3 | import { LoginLogService } from './login-log.service'; 4 | import { PrismaService } from 'src/services/prisma.service'; 5 | 6 | @Module({ 7 | controllers: [LoginLogController], 8 | providers: [LoginLogService, PrismaService], 9 | }) 10 | export class LoginLogModule {} 11 | -------------------------------------------------------------------------------- /src/module/auth/dto/token.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class TokenVO { 4 | @ApiProperty({ description: 'token的过期时间' }) 5 | expire: number; 6 | @ApiProperty({ description: 'token' }) 7 | token: string; 8 | @ApiProperty({ description: '刷新token的过期时间' }) 9 | refreshExpire: number; 10 | @ApiProperty({ description: '刷新token' }) 11 | refreshToken: string; 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /prisma/migrations/20231001075129_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `pkNmae` on the `file` table. All the data in the column will be lost. 5 | - Added the required column `pkName` to the `file` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE `file` DROP COLUMN `pkNmae`, 10 | ADD COLUMN `pkName` VARCHAR(191) NOT NULL; 11 | -------------------------------------------------------------------------------- /src/module/role/role.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RoleController } from './role.controller'; 3 | import { RoleService } from './role.service'; 4 | import { PrismaService } from 'src/services/prisma.service'; 5 | import { SocketGateway } from 'src/socket/socket.gateway'; 6 | 7 | 8 | @Module({ 9 | controllers: [RoleController], 10 | providers: [RoleService, PrismaService, SocketGateway], 11 | 12 | }) 13 | export class RoleModule {} 14 | -------------------------------------------------------------------------------- /src/module/role/dto/create-role.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { IsNotEmpty } from "class-validator"; 3 | 4 | export class CreateRoleDto { 5 | @ApiProperty({description:'角色名称'}) 6 | @IsNotEmpty({message: '角色名称不能为空'}) 7 | name: string 8 | @ApiProperty({description:'角色代码'}) 9 | @IsNotEmpty({message: '角色代码不能为空'}) 10 | code: string 11 | 12 | @ApiProperty({description:'角色分配的菜单id'}) 13 | menuIds: string[] 14 | } 15 | -------------------------------------------------------------------------------- /prisma/migrations/20231028141149_set_table_login_log/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE `Login_Log` ( 3 | `id` VARCHAR(191) NOT NULL, 4 | `userName` VARCHAR(191) NULL, 5 | `ip` VARCHAR(191) NULL, 6 | `address` VARCHAR(191) NULL, 7 | `browser` VARCHAR(191) NULL, 8 | `os` VARCHAR(191) NULL, 9 | `status` VARCHAR(191) NOT NULL, 10 | `message` VARCHAR(191) NULL, 11 | 12 | PRIMARY KEY (`id`) 13 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 14 | -------------------------------------------------------------------------------- /src/config/minio.config.ts: -------------------------------------------------------------------------------- 1 | import {env} from 'process' 2 | export default () => ({ 3 | minio: { 4 | isGlobal: true, 5 | endPoint: env.MINIO_HOST || 'localhost', 6 | port: env.MINIO_PORT ? Number(env.MINIO_PORT) : 9002, 7 | useSSL: false, 8 | accessKey: env.MINIO_ROOT_USER || 'minio', 9 | secretKey: env.MINIO_ROOT_PASSWORD || 'minio@123', 10 | }, 11 | bucket: { 12 | name: 'meng-admin' 13 | } 14 | }) -------------------------------------------------------------------------------- /prisma/migrations/20231001041129_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `user` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | - You are about to alter the column `id` on the `user` table. The data in that column could be lost. The data in that column will be cast from `UnsignedBigInt` to `VarChar(191)`. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE `user` DROP PRIMARY KEY, 10 | MODIFY `id` VARCHAR(191) NOT NULL; 11 | -------------------------------------------------------------------------------- /prisma/migrations/20231031112046_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[pkId]` on the table `File` will be added. If there are existing duplicate values, this will fail. 5 | - Added the required column `pkId` to the `File` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE `File` ADD COLUMN `pkId` VARCHAR(191) NOT NULL; 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX `File_pkId_key` ON `File`(`pkId`); 13 | -------------------------------------------------------------------------------- /prisma/migrations/20231001034056_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `user` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | - You are about to alter the column `id` on the `user` table. The data in that column could be lost. The data in that column will be cast from `UnsignedInt` to `UnsignedBigInt`. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE `user` DROP PRIMARY KEY, 10 | MODIFY `id` BIGINT UNSIGNED NOT NULL, 11 | ADD PRIMARY KEY (`id`); 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": true, 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "htmlWhitespaceSensitivity": "css", 7 | "insertPragma": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 120, 10 | "proseWrap": "never", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": false, 14 | "singleQuote": true, 15 | "tabWidth": 2, 16 | "trailingComma": "all", 17 | "useTabs": false, 18 | "vueIndentScriptAndStyle": false, 19 | "singleAttributePerLine": false 20 | } -------------------------------------------------------------------------------- /prisma/migrations/20231001075022_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `lePath` on the `file` table. All the data in the column will be lost. 5 | - A unique constraint covering the columns `[filePath]` on the table `file` will be added. If there are existing duplicate values, this will fail. 6 | 7 | */ 8 | -- DropIndex 9 | DROP INDEX `file_lePath_key` ON `file`; 10 | 11 | -- AlterTable 12 | ALTER TABLE `file` DROP COLUMN `lePath`, 13 | ADD COLUMN `filePath` VARCHAR(191) NULL; 14 | 15 | -- CreateIndex 16 | CREATE UNIQUE INDEX `file_filePath_key` ON `file`(`filePath`); 17 | -------------------------------------------------------------------------------- /src/common/middware/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import { LoggerService } from '../../services/logger.service' 4 | 5 | @Injectable() 6 | export class LoggerMiddleware implements NestMiddleware { 7 | constructor(private readonly loggerService: LoggerService) {} 8 | 9 | use(req: Request, res: Response, next: NextFunction) { 10 | const { method, originalUrl, ip } = req; 11 | this.loggerService.log(`[${method}] ${originalUrl} from ${ip}`); 12 | next(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/common/error.ts: -------------------------------------------------------------------------------- 1 | import {HttpException, HttpStatus} from '@nestjs/common' 2 | 3 | 4 | export class R { 5 | static error(message: string) { 6 | return new HttpException(message, HttpStatus.BAD_REQUEST); 7 | } 8 | 9 | static validateError(message: string) { 10 | return new HttpException(message, HttpStatus.UNPROCESSABLE_ENTITY); 11 | } 12 | 13 | static unauthorizedError(message: string) { 14 | return new HttpException(message, HttpStatus.UNAUTHORIZED); 15 | } 16 | 17 | static forbiddenError(message: string) { 18 | return new HttpException(message, HttpStatus.FORBIDDEN); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/module/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UserController } from './user.controller'; 3 | import { UserService } from './user.service'; 4 | import { PrismaService } from 'src/services/prisma.service'; 5 | import { EmailService } from 'src/services/mail.service'; 6 | import { SocketGateway } from 'src/socket/socket.gateway'; 7 | import { UploadModule } from '../upload/upload.module'; 8 | 9 | @Module({ 10 | imports: [UploadModule], 11 | controllers: [UserController], 12 | providers: [UserService, PrismaService, EmailService, SocketGateway], 13 | exports: [UserService] 14 | }) 15 | export class UserModule {} 16 | -------------------------------------------------------------------------------- /src/config/redis.config.ts: -------------------------------------------------------------------------------- 1 | import { env } from "process"; 2 | 3 | export default () => ({ 4 | redis: { 5 | socket: { 6 | host: env.REDIS_HOST || 'localhost', 7 | port: 6379, 8 | }, 9 | database: 0 10 | }, 11 | publish: { 12 | socket: { 13 | host: env.REDIS_HOST || 'localhost', 14 | port: 6379, 15 | }, 16 | database: 1 17 | }, 18 | subscribe: { 19 | socket: { 20 | host: env.REDIS_HOST|| 'localhost', 21 | port: 6379, 22 | }, 23 | database: 2 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "debug nest", 11 | "runtimeExecutable": "pnpm", 12 | "args": [ 13 | "run", 14 | "dev", 15 | ], 16 | "skipFiles": [ 17 | "/**" 18 | ], 19 | "console": "integratedTerminal", 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /src/module/auth/dto/resetPassword.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import {IsEmail, IsNotEmpty} from 'class-validator' 3 | 4 | export class ResetPasswordDto { 5 | @ApiProperty({ description: '密码' }) 6 | @IsNotEmpty({message: '密码不能为空'}) 7 | password: string; 8 | 9 | @ApiProperty({ description: '邮箱' }) 10 | @IsEmail({},{message: '无效的邮箱'}) 11 | email: string; 12 | 13 | @ApiProperty({ description: '邮箱验证码' }) 14 | @IsNotEmpty({message: '邮箱验证码不能为空'}) 15 | emailCaptcha: string; 16 | 17 | @ApiProperty({ description: '公钥' }) 18 | @IsNotEmpty({message: '公钥不能为空'}) 19 | publicKey: string; 20 | } -------------------------------------------------------------------------------- /src/module/login-log/login-log.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Query } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | import { LoginLogService } from './login-log.service'; 4 | import { LoginLogDto } from './dto/login-login-page.dto'; 5 | 6 | 7 | @Controller('login-log') 8 | export class LoginLogController { 9 | constructor( 10 | private readonly loginLogService: LoginLogService, 11 | ) {} 12 | 13 | @ApiOperation({ 14 | summary: '获取登录信息', 15 | }) 16 | @Get() 17 | async getLoginLogByPage(@Query() loginLogDto: LoginLogDto){ 18 | return this.loginLogService.getLoginLogByPage(loginLogDto) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/socket/interface/message.ts: -------------------------------------------------------------------------------- 1 | export enum SocketMessageType { 2 | /** 3 | * 权限变更 4 | */ 5 | PermissionChange = 'PermissionChange', 6 | /** 7 | * 密码重置 8 | */ 9 | PasswordChange = 'PasswordChange', 10 | /** 11 | * token过期 12 | */ 13 | TokenExpire = 'TokenExpire', 14 | } 15 | 16 | export class SocketMessage { 17 | /** 18 | * 消息类型 19 | */ 20 | type: SocketMessageType; 21 | /** 22 | * 消息内容 23 | */ 24 | data?: T; 25 | constructor(type: SocketMessageType, data?: T) { 26 | this.type = type; 27 | this.data = data; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /prisma/migrations/20231020045510_user_mount_role/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE `_RoleToUser` ( 3 | `A` VARCHAR(191) NOT NULL, 4 | `B` VARCHAR(191) NOT NULL, 5 | 6 | UNIQUE INDEX `_RoleToUser_AB_unique`(`A`, `B`), 7 | INDEX `_RoleToUser_B_index`(`B`) 8 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE `_RoleToUser` ADD CONSTRAINT `_RoleToUser_A_fkey` FOREIGN KEY (`A`) REFERENCES `Role`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; 12 | 13 | -- AddForeignKey 14 | ALTER TABLE `_RoleToUser` ADD CONSTRAINT `_RoleToUser_B_fkey` FOREIGN KEY (`B`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /prisma/migrations/20231031122453_reset_table/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `pkId` on the `File` table. All the data in the column will be lost. 5 | - Added the required column `userId` to the `File` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- DropIndex 9 | DROP INDEX `File_pkId_key` ON `File`; 10 | 11 | -- AlterTable 12 | ALTER TABLE `File` DROP COLUMN `pkId`, 13 | ADD COLUMN `userId` VARCHAR(191) NOT NULL; 14 | 15 | -- AddForeignKey 16 | ALTER TABLE `File` ADD CONSTRAINT `File_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /prisma/migrations/20231001074327_file/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[id]` on the table `user` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateTable 8 | CREATE TABLE `file` ( 9 | `pkValue` VARCHAR(191) NOT NULL, 10 | `pkNmae` VARCHAR(191) NOT NULL, 11 | `fileName` VARCHAR(191) NULL, 12 | `lePath` VARCHAR(191) NULL, 13 | 14 | UNIQUE INDEX `file_fileName_key`(`fileName`), 15 | UNIQUE INDEX `file_lePath_key`(`lePath`), 16 | PRIMARY KEY (`pkValue`) 17 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 18 | 19 | -- CreateIndex 20 | CREATE UNIQUE INDEX `user_id_key` ON `user`(`id`); 21 | -------------------------------------------------------------------------------- /prisma/migrations/20231018131728_reset/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `userId` on the `Role` table. All the data in the column will be lost. 5 | - A unique constraint covering the columns `[code]` on the table `Role` will be added. If there are existing duplicate values, this will fail. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE `Role` DROP FOREIGN KEY `Role_userId_fkey`; 10 | 11 | -- AlterTable 12 | ALTER TABLE `File` ADD COLUMN `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3); 13 | 14 | -- AlterTable 15 | ALTER TABLE `Role` DROP COLUMN `userId`, 16 | MODIFY `code` VARCHAR(191) NOT NULL; 17 | 18 | -- CreateIndex 19 | CREATE UNIQUE INDEX `Role_code_key` ON `Role`(`code`); 20 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/services/logger.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { createLogger, format, transports } from 'winston'; 3 | 4 | @Injectable() 5 | export class LoggerService { 6 | private readonly logger = createLogger({ 7 | level: 'info', 8 | format: format.combine( 9 | format.timestamp(), 10 | format.json() 11 | ), 12 | transports: [ 13 | new transports.Console(),// 控制台输出 14 | new transports.File({ filename: 'logs/error.log', level: 'error' }),// 错误日志文件 15 | new transports.File({ filename: 'logs/combined.log' }),// 综合日志文件 16 | ], 17 | }); 18 | 19 | log(message: string) { 20 | this.logger.info(message); 21 | } 22 | 23 | error(message: string) { 24 | this.logger.error(message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/module/menu/dto/create-menu.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger" 2 | 3 | export class CreateMenuDto { 4 | @ApiProperty({description:'图标'}) 5 | icon: string 6 | @ApiProperty({description:'名称'}) 7 | name: string 8 | @ApiProperty({description:'父级id'}) 9 | parentId: string 10 | @ApiProperty({description:'类型'}) 11 | type: number 12 | @ApiProperty({description:'路由'}) 13 | route: string 14 | @ApiProperty({description:'排序号'}) 15 | orderNumber?:number 16 | @ApiProperty({description:'url地址'}) 17 | url?: string 18 | @ApiProperty({description:'按钮权限码'}) 19 | authCode?: string 20 | @ApiProperty({description:'显示与否'}) 21 | show?: boolean 22 | @ApiProperty({description:'文件地址'}) 23 | filePath?: string 24 | } 25 | -------------------------------------------------------------------------------- /src/module/upload/upload.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { NestMinioModule } from 'nestjs-minio'; 4 | import { UploadController } from './upload.controller'; 5 | import { UploadService } from './upload.service'; 6 | import { PrismaService } from 'src/services/prisma.service'; 7 | 8 | @Global() 9 | @Module({ 10 | imports: [ 11 | NestMinioModule.registerAsync({ 12 | useFactory: (configService:ConfigService) => ({ 13 | ...configService.get('minio'), 14 | }), 15 | inject: [ConfigService], 16 | }) 17 | 18 | ], 19 | controllers: [UploadController], 20 | providers: [UploadService, PrismaService], 21 | exports: [UploadService] 22 | }) 23 | export class UploadModule {} 24 | -------------------------------------------------------------------------------- /src/module/upload/upload.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller,Post, UploadedFile, UseInterceptors } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | import { FileInterceptor } from '@nestjs/platform-express'; 4 | import { UploadService } from './upload.service'; 5 | import { NotLogin } from 'src/common/decorator/not-login.decorator'; 6 | 7 | 8 | @Controller('file') 9 | export class UploadController { 10 | constructor( 11 | private readonly uploadService: UploadService, 12 | ) {} 13 | 14 | @ApiOperation({ 15 | summary: '上传文件' 16 | }) 17 | @Post('upload') 18 | @UseInterceptors(FileInterceptor('file')) 19 | @NotLogin() 20 | async uploadFile(@UploadedFile() file: Express.Multer.File){ 21 | return await this.uploadService.uploadFile(file); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/module/user/dto/update_user.dto.ts: -------------------------------------------------------------------------------- 1 | import {PartialType} from '@nestjs/mapped-types' 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { IsEmail, IsNotEmpty, IsPhoneNumber } from 'class-validator'; 4 | import { UserDto } from './user.dto'; 5 | import { fileEntity } from 'src/module/upload/interface/fileEntity'; 6 | 7 | 8 | export class UpdateUserDto extends PartialType(UserDto){ 9 | @ApiProperty({ description: '用户名称' }) 10 | @IsNotEmpty({message: '用户名不能为空'}) 11 | userName: string 12 | 13 | @ApiProperty({ description: '手机号' }) 14 | @IsPhoneNumber('CN', { 15 | message: '无效的手机号', 16 | }) 17 | phoneNumber: string 18 | 19 | @ApiProperty({ description: '邮箱' }) 20 | @IsEmail({},{message: '无效的邮箱'}) 21 | email: string 22 | 23 | fileEntity: fileEntity[] 24 | } -------------------------------------------------------------------------------- /src/module/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CacheModule } from 'src/module/cache/cache.module'; 3 | import { UserModule } from 'src/module/user/user.module'; 4 | import { AuthController } from './auth.controller'; 5 | import { PrismaService } from 'src/services/prisma.service'; 6 | import { AuthService } from './auth.service'; 7 | import { UserService } from 'src/module/user/user.service'; 8 | import { EmailService } from 'src/services/mail.service'; 9 | import { RsaService } from 'src/services/rsa.service'; 10 | import { SocketGateway } from 'src/socket/socket.gateway'; 11 | 12 | 13 | @Module({ 14 | imports: [CacheModule, UserModule], 15 | controllers: [AuthController], 16 | providers: [AuthService, PrismaService, UserService, EmailService, RsaService, SocketGateway], 17 | }) 18 | export class AuthModule {} 19 | -------------------------------------------------------------------------------- /src/module/auth/dto/login.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty } from 'class-validator' 3 | export class LoginDto { 4 | @ApiProperty({description:'用户名'}) 5 | @IsNotEmpty({message:'用户名不能为空'}) 6 | accountNumber: string 7 | 8 | @ApiProperty({description: '密码'}) 9 | @IsNotEmpty({message: '密码不能为空'}) 10 | password: string 11 | 12 | @ApiProperty({description: '验证码'}) 13 | @IsNotEmpty({ message: '验证码不能为空' }) 14 | captcha: string 15 | 16 | @ApiProperty({description: '公钥'}) 17 | @IsNotEmpty({message:'公钥不能为空'}) 18 | publicKey: string 19 | } 20 | 21 | export class CaptchaType { 22 | @ApiProperty() 23 | id: string; 24 | @ApiProperty() 25 | text: string; 26 | @ApiProperty() 27 | data: string; 28 | @ApiProperty() 29 | time: Date; 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import {SwaggerModule, DocumentBuilder} from '@nestjs/swagger' 3 | import { ValidationPipe } from '@nestjs/common'; 4 | import { WsAdapter } from '@nestjs/platform-ws'; 5 | import { AppModule } from './app.module' 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | app.useWebSocketAdapter(new WsAdapter(app)); 9 | app.setGlobalPrefix('api') 10 | 11 | app.useGlobalPipes(new ValidationPipe()); 12 | app.enableCors() 13 | const config = new DocumentBuilder() 14 | .setTitle('Meng Admin') 15 | .setDescription('Meng Admin的后台接口') 16 | .setVersion('1.0') 17 | .addTag('meng') 18 | .build(); 19 | const document = SwaggerModule.createDocument(app, config); 20 | SwaggerModule.setup('swagger', app, document); 21 | await app.listen(3000); 22 | } 23 | bootstrap(); 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | # 因为我们项目使用的是pnpm安装依赖,所以找了个支持pnpm的基础镜像,如果你们使用npm,这里可以替换成node镜像 3 | # FROM nginx:alpine 4 | FROM gplane/pnpm:8 as builder 5 | 6 | # 设置时区 7 | ENV TZ=Asia/Shanghai \ 8 | DEBIAN_FRONTEND=noninteractive 9 | RUN ln -fs /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && dpkg-reconfigure --frontend noninteractive tzdata && rm -rf /var/lib/apt/lists/* 10 | 11 | # 创建工作目录 12 | RUN mkdir -p /app 13 | 14 | # 指定工作目录 15 | WORKDIR /app 16 | 17 | # 复制当前代码到/app工作目录 18 | COPY . ./ 19 | 20 | RUN npm config set registry https://registry.npm.taobao.org/ 21 | # npm 安装依赖 22 | COPY package.json /app/package.json 23 | 24 | RUN rm -rf /app/package-lock.json 25 | RUN cd /app && rm -rf /app/node_modules && pnpm install 26 | 27 | RUN cd /app pnpm run prisma migrate dev 28 | RUN cd /app && rm -rf /app/dist && pnpm build 29 | 30 | 31 | EXPOSE 3000 32 | # 启动服务 33 | 34 | CMD pnpm run start:prod 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/module/login-log/login-log.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PrismaService } from 'src/services/prisma.service'; 3 | import { LoginLogDto } from './dto/login-login-page.dto'; 4 | 5 | 6 | @Injectable() 7 | export class LoginLogService { 8 | constructor( 9 | private readonly prisma: PrismaService 10 | ){} 11 | 12 | /** 13 | * @description 登录信息分页数据 14 | * @date 11/01/2023 15 | * @returns 登录信息 16 | */ 17 | async getLoginLogByPage(loginLogDto:LoginLogDto) { 18 | const {page, size, userName} = loginLogDto 19 | const where = { 20 | userName: { 21 | contains: userName 22 | } 23 | } 24 | const [data, total] = await Promise.all([ 25 | this.prisma.login_Log.findMany({ 26 | skip: (+page - 1) * +size, 27 | take: +size, 28 | where, 29 | }), 30 | this.prisma.login_Log.count({where}) 31 | ]) 32 | return { 33 | data, 34 | total 35 | } 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /prisma/migrations/20230924053039_add_ctime_and_utime/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `username` on the `user` table. All the data in the column will be lost. 5 | - A unique constraint covering the columns `[userName]` on the table `user` will be added. If there are existing duplicate values, this will fail. 6 | - Added the required column `updateAt` to the `user` table without a default value. This is not possible if the table is not empty. 7 | - Added the required column `userName` to the `user` table without a default value. This is not possible if the table is not empty. 8 | 9 | */ 10 | -- DropIndex 11 | DROP INDEX `user_username_key` ON `user`; 12 | 13 | -- AlterTable 14 | ALTER TABLE `user` DROP COLUMN `username`, 15 | ADD COLUMN `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 16 | ADD COLUMN `updateAt` DATETIME(3) NOT NULL, 17 | ADD COLUMN `userName` VARCHAR(191) NOT NULL; 18 | 19 | -- CreateIndex 20 | CREATE UNIQUE INDEX `user_userName_key` ON `user`(`userName`); 21 | -------------------------------------------------------------------------------- /src/services/mail.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import * as nodemailer from 'nodemailer'; 4 | interface MailInfo { 5 | // 目标邮箱 6 | to: string; 7 | // 标题 8 | subject: string; 9 | // 文本 10 | text?: string; 11 | // 富文本,如果文本和富文本同时设置,富文本生效。 12 | html?: string; 13 | } 14 | @Injectable() 15 | export class EmailService { 16 | private transporter: nodemailer.Transporter; 17 | constructor( 18 | private readonly configService:ConfigService 19 | ) { 20 | this.transporter = nodemailer.createTransport(this.configService.get('mail')); 21 | } 22 | 23 | /**ConfigService 24 | * @description 发送邮箱 25 | * @date 10/16/2023 26 | * @param mailInfo 27 | */ 28 | async sendEmail(mailInfo: MailInfo) { 29 | const info = await this.transporter.sendMail({ 30 | from: this.configService.get('mail').auth.user, //发送方邮箱 31 | ...mailInfo 32 | }) 33 | return info 34 | } 35 | } 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 meng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /prisma/migrations/20231020050501_add_table_user_role/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `_RoleToUser` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE `_RoleToUser` DROP FOREIGN KEY `_RoleToUser_A_fkey`; 9 | 10 | -- DropForeignKey 11 | ALTER TABLE `_RoleToUser` DROP FOREIGN KEY `_RoleToUser_B_fkey`; 12 | 13 | -- DropTable 14 | DROP TABLE `_RoleToUser`; 15 | 16 | -- CreateTable 17 | CREATE TABLE `User_Role` ( 18 | `id` VARCHAR(191) NOT NULL, 19 | `roleId` VARCHAR(191) NOT NULL, 20 | `userId` VARCHAR(191) NOT NULL, 21 | 22 | UNIQUE INDEX `User_Role_roleId_userId_key`(`roleId`, `userId`), 23 | PRIMARY KEY (`id`) 24 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 25 | 26 | -- AddForeignKey 27 | ALTER TABLE `User_Role` ADD CONSTRAINT `User_Role_roleId_fkey` FOREIGN KEY (`roleId`) REFERENCES `Role`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 28 | 29 | -- AddForeignKey 30 | ALTER TABLE `User_Role` ADD CONSTRAINT `User_Role_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 31 | -------------------------------------------------------------------------------- /src/utils/user/is_exist.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | import { 3 | registerDecorator, 4 | ValidationOptions, 5 | ValidationArguments, 6 | } from 'class-validator'; 7 | 8 | export function IsExistRule( 9 | table: string, 10 | validationOptions?: ValidationOptions, 11 | ) { 12 | return function (object: Record, propertyName: string) { 13 | registerDecorator({ 14 | name: 'isLongerThan', 15 | target: object.constructor, 16 | propertyName: propertyName, 17 | constraints: [table], 18 | options: validationOptions, 19 | validator: { 20 | async validate(value: any, args: ValidationArguments) { 21 | const prisma = new PrismaClient() 22 | const result = await prisma[table].findUnique({ 23 | where: { 24 | [propertyName]: args.value 25 | } 26 | }) 27 | return !Boolean(result) 28 | }, 29 | }, 30 | }); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/module/cache/cache.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { createClient } from 'redis'; 4 | 5 | @Global() 6 | @Module({ 7 | providers:[{ 8 | provide: 'DEFAULT', 9 | async useFactory(configService: ConfigService){ 10 | const client = createClient({ 11 | ...configService.get('redis') 12 | }) 13 | await client.connect(); 14 | return client; 15 | }, 16 | inject:[ConfigService] 17 | }, 18 | { 19 | provide: 'PUBLISH', 20 | async useFactory(configService: ConfigService){ 21 | const client = createClient({ 22 | ...configService.get('publish') 23 | }) 24 | await client.connect() 25 | return client; 26 | }, 27 | inject:[ConfigService] 28 | }, 29 | { 30 | provide: 'SUBSCRIBE', 31 | async useFactory(configService: ConfigService){ 32 | const client = createClient({ 33 | ...configService.get('subscribe') 34 | }) 35 | await client.connect() 36 | return client; 37 | }, 38 | inject:[ConfigService] 39 | } 40 | 41 | ], 42 | exports: ['DEFAULT', 'PUBLISH', 'SUBSCRIBE'] 43 | }, 44 | 45 | ) 46 | export class CacheModule {} -------------------------------------------------------------------------------- /src/common/filter/validate.filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter, HttpCode, HttpException, HttpStatus } from '@nestjs/common' 2 | import { Response } from 'express' 3 | import { LoggerService } from 'src/services/logger.service' 4 | 5 | 6 | @Catch(HttpException) 7 | export class ValidateExceptionFilter implements ExceptionFilter { 8 | constructor(private readonly loggerService: LoggerService){} 9 | catch(exception: HttpException, host: ArgumentsHost) { 10 | const ctx = host.switchToHttp() 11 | const response = ctx.getResponse() 12 | const request = ctx.getRequest() 13 | const status = exception.getStatus() 14 | //自定义异常处理 15 | 16 | this.loggerService.error(JSON.stringify(exception.getResponse())) 17 | 18 | if (exception instanceof BadRequestException) { 19 | 20 | return response.json(exception.getResponse()) 21 | // throw new HttpException(exception.getResponse(), HttpStatus.BAD_REQUEST, ) 22 | } 23 | 24 | return response 25 | .status(status) 26 | .json({ 27 | statusCode: status, 28 | timestamp: new Date().toISOString(), 29 | path: request.url, 30 | }); 31 | } 32 | } -------------------------------------------------------------------------------- /prisma/migrations/20230923085000_enrich_user_model/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[phoneNumber]` on the table `user` will be added. If there are existing duplicate values, this will fail. 5 | - A unique constraint covering the columns `[email]` on the table `user` will be added. If there are existing duplicate values, this will fail. 6 | - Added the required column `email` to the `user` table without a default value. This is not possible if the table is not empty. 7 | - Added the required column `nickName` to the `user` table without a default value. This is not possible if the table is not empty. 8 | - Added the required column `phoneNumber` to the `user` table without a default value. This is not possible if the table is not empty. 9 | 10 | */ 11 | -- AlterTable 12 | ALTER TABLE `user` ADD COLUMN `avatar` VARCHAR(191) NULL, 13 | ADD COLUMN `email` VARCHAR(191) NOT NULL, 14 | ADD COLUMN `nickName` VARCHAR(191) NOT NULL, 15 | ADD COLUMN `phoneNumber` VARCHAR(191) NOT NULL, 16 | ADD COLUMN `sex` VARCHAR(191) NULL; 17 | 18 | -- CreateIndex 19 | CREATE UNIQUE INDEX `user_phoneNumber_key` ON `user`(`phoneNumber`); 20 | 21 | -- CreateIndex 22 | CREATE UNIQUE INDEX `user_email_key` ON `user`(`email`); 23 | -------------------------------------------------------------------------------- /prisma/migrations/20231018132847_update_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `Menu` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | - The primary key for the `Role` table will be changed. If it partially fails, the table could be left without primary key constraint. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE `Role_Menu` DROP FOREIGN KEY `Role_Menu_menuId_fkey`; 10 | 11 | -- DropForeignKey 12 | ALTER TABLE `Role_Menu` DROP FOREIGN KEY `Role_Menu_roleId_fkey`; 13 | 14 | -- AlterTable 15 | ALTER TABLE `Menu` DROP PRIMARY KEY, 16 | MODIFY `id` VARCHAR(191) NOT NULL, 17 | ADD PRIMARY KEY (`id`); 18 | 19 | -- AlterTable 20 | ALTER TABLE `Role` DROP PRIMARY KEY, 21 | MODIFY `id` VARCHAR(191) NOT NULL, 22 | ADD PRIMARY KEY (`id`); 23 | 24 | -- AlterTable 25 | ALTER TABLE `Role_Menu` MODIFY `roleId` VARCHAR(191) NOT NULL, 26 | MODIFY `menuId` VARCHAR(191) NOT NULL; 27 | 28 | -- AddForeignKey 29 | ALTER TABLE `Role_Menu` ADD CONSTRAINT `Role_Menu_roleId_fkey` FOREIGN KEY (`roleId`) REFERENCES `Role`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 30 | 31 | -- AddForeignKey 32 | ALTER TABLE `Role_Menu` ADD CONSTRAINT `Role_Menu_menuId_fkey` FOREIGN KEY (`menuId`) REFERENCES `Menu`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 33 | -------------------------------------------------------------------------------- /src/module/user/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import {IsNotEmpty, IsPhoneNumber, IsEmail} from 'class-validator' 3 | import { IsExistRule } from 'src/utils/user/is_exist' 4 | export class UserDto { 5 | @ApiProperty({ description: 'id' }) 6 | id: string 7 | 8 | @IsExistRule('user', { message: '用户名已存在' }) 9 | @ApiProperty({ description: '用户名称' }) 10 | @IsNotEmpty({message: '用户名不能为空'}) 11 | userName: string 12 | 13 | @ApiProperty({ description: '用户昵称' }) 14 | @IsNotEmpty({message: '用户昵称不能为空'}) 15 | nickName: string 16 | 17 | @IsExistRule('user', { message: '手机号已注册' }) 18 | @ApiProperty({ description: '手机号' }) 19 | @IsPhoneNumber('CN', { 20 | message: '无效的手机号', 21 | }) 22 | phoneNumber: string 23 | 24 | @IsExistRule('user', { message: '邮箱已注册' }) 25 | @ApiProperty({ description: '邮箱' }) 26 | @IsEmail({},{message: '无效的邮箱'}) 27 | email: string 28 | 29 | @ApiProperty({ description: '邮箱验证码' }) 30 | emailCaptcha: string 31 | 32 | @ApiProperty({ description: '密码' }) 33 | password: string 34 | 35 | @ApiProperty({ description: '头像', nullable:true }) 36 | avatar?: string 37 | 38 | @ApiProperty({ description: '性别(0:女, 1:男)', nullable:true }) 39 | sex?: number; 40 | 41 | user_Role?: string[] 42 | } 43 | -------------------------------------------------------------------------------- /src/services/rsa.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from "@nestjs/common"; 2 | import { RedisClientType } from "redis"; 3 | import * as NodeRSA from 'node-rsa' 4 | import { R } from "src/utils/common/error"; 5 | 6 | 7 | @Injectable() 8 | export class RsaService { 9 | 10 | constructor(@Inject('DEFAULT') readonly redisClient: RedisClientType ){} 11 | 12 | /** 13 | * @description 获取公钥 14 | * @date 10/12/2023 15 | */ 16 | public async getPublicKey(): Promise{ 17 | const key = new NodeRSA({b: 512}); 18 | const publicKey = key.exportKey('public') 19 | const privateKey = key.exportKey('private') 20 | await this.redisClient.set(`publicKey:${publicKey}`, privateKey); 21 | return publicKey 22 | } 23 | /** 24 | * @description 解密 25 | * @param publicKey 26 | * @param data 27 | * @date 10/12/2023 28 | */ 29 | public async decrypt(publicKey:string, data: string) { 30 | const privateKey = await this.redisClient.get(`publicKey:${publicKey}`) 31 | if(!privateKey) { 32 | throw R.error('私钥解密失败或已失效') 33 | } 34 | 35 | const decrypt = new NodeRSA(privateKey) 36 | decrypt.setOptions({encryptionScheme: 'pkcs1'}) 37 | return decrypt.decrypt(data, 'utf8') 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/common/interceptor/auth.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | import { Request } from 'express'; 4 | import { RedisClientType } from 'redis'; 5 | import { Observable } from 'rxjs'; 6 | import { R } from 'src/utils/common/error'; 7 | import { getIp } from 'src/utils/common/ip'; 8 | 9 | @Injectable() 10 | export class AuthInterceptor implements NestInterceptor { 11 | constructor( 12 | @Inject('DEFAULT') private readonly redisClient: RedisClientType, 13 | private readonly reflector: Reflector 14 | ){} 15 | async intercept(context: ExecutionContext, next: CallHandler): Promise> { 16 | 17 | const req = context.switchToHttp().getRequest() 18 | 19 | const isNotLogin = this.reflector.get('not-login', context.getHandler()) 20 | 21 | if(isNotLogin){ 22 | return next.handle() 23 | } 24 | 25 | const token = req.headers['authorization']?.replace('Bearer ', '') 26 | if(!token) { 27 | throw R.unauthorizedError('未授权') 28 | } 29 | 30 | const userInfoStr = await this.redisClient.get(`token:${token}`) 31 | 32 | 33 | if(!userInfoStr) { 34 | throw R.unauthorizedError('未授权') 35 | } 36 | 37 | const userInfo = JSON.parse(userInfoStr) 38 | req['userInfo'] = userInfo 39 | req['token'] = token 40 | 41 | return next.handle(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/common/ip.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | import IP2Region from "ip2region"; 3 | import * as useragent from 'useragent'; 4 | 5 | 6 | /** 7 | * @description 获取ip 8 | * @date 10/28/2023 9 | * @param req 10 | * @returns 11 | */ 12 | export const getIp = (req: Request) => { 13 | const ips = (req.headers['x-forwarded-for'] as string) || 14 | (req.headers['X-Real-IP'] as string) || 15 | (req.ip.replace('::ffff:', '') as string).replace(':ffff:', '') 16 | return ips.split(',')?.[0]; 17 | } 18 | 19 | /** 20 | * @description 获取ip所在地区 21 | * @param ip 22 | * @date 10/28/2023 23 | */ 24 | export const getAdressByIp = (ip:string):string => { 25 | if(!ip) return '' 26 | 27 | const query = new IP2Region() 28 | const res = query.search(ip) 29 | return [res.province, res.city].join(' ') 30 | } 31 | 32 | /** 33 | * @description 获取客户端信息 34 | * @date 10/28/2023 35 | * @param req 36 | */ 37 | export const getUserAgent = (req:Request):useragent.Agent => { 38 | return useragent.parse(req.headers['user-agent'] as string) 39 | } 40 | 41 | /** 42 | * @description 获取不包含前缀的api 43 | * @date 10/28/2023 44 | * @param globalPrefix 前缀 45 | * @param url url 46 | * @returns url 47 | */ 48 | export const getExcludeGlobalPrefixUrl = ( 49 | globalFix: string, 50 | url:string 51 | ) => { 52 | if(url.startsWith(globalFix)){ 53 | return url.substring(globalFix.length) 54 | } 55 | 56 | return url 57 | } -------------------------------------------------------------------------------- /src/common/guard/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { Reflector } from '@nestjs/core'; 4 | import { PrismaService } from 'src/services/prisma.service'; 5 | import { RedisClientType } from 'redis'; 6 | 7 | @Injectable() 8 | export class AuthGuard implements CanActivate { 9 | private result:boolean | Promise | Observable 10 | constructor( 11 | private reflector: Reflector, 12 | private readonly prisma:PrismaService, 13 | @Inject('DEFAULT') private readonly redisClient: RedisClientType, 14 | ) { } 15 | async canActivate(context: ExecutionContext,): Promise { 16 | const roles = this.reflector.get('userName', context.getHandler()) 17 | if (!roles) { 18 | return true 19 | } 20 | //获取http请求对象 21 | const req = context.switchToHttp().getRequest() 22 | const token = req.headers['authorization']?.replace('Bearer ', '') 23 | try { 24 | const res = await this.redisClient.get(`token:${token}`); 25 | const userInfo = JSON.parse(res); 26 | const user = await this.prisma.user.findUnique({ where: { id: userInfo.userId } }); 27 | this.result = matchRoles(roles, user.userName); 28 | return this.result; 29 | } catch (err) { 30 | console.log(err); 31 | return false; 32 | } 33 | } 34 | } 35 | 36 | function matchRoles(roles: string[], roles1: string): Promise | boolean { 37 | return roles.includes(roles1) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /prisma/migrations/20231002075603_user_include_file/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `file` table. If the table is not empty, all the data it contains will be lost. 5 | - You are about to drop the `user` table. If the table is not empty, all the data it contains will be lost. 6 | 7 | */ 8 | -- DropTable 9 | DROP TABLE `file`; 10 | 11 | -- DropTable 12 | DROP TABLE `user`; 13 | 14 | -- CreateTable 15 | CREATE TABLE `User` ( 16 | `id` VARCHAR(191) NOT NULL, 17 | `userName` VARCHAR(191) NOT NULL, 18 | `nickName` VARCHAR(191) NOT NULL, 19 | `password` VARCHAR(191) NOT NULL, 20 | `phoneNumber` VARCHAR(191) NOT NULL, 21 | `email` VARCHAR(191) NOT NULL, 22 | `avatar` VARCHAR(191) NULL, 23 | `sex` INTEGER NULL, 24 | `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 25 | `updateAt` DATETIME(3) NOT NULL, 26 | 27 | UNIQUE INDEX `User_userName_key`(`userName`), 28 | UNIQUE INDEX `User_phoneNumber_key`(`phoneNumber`), 29 | UNIQUE INDEX `User_email_key`(`email`), 30 | PRIMARY KEY (`id`) 31 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 32 | 33 | -- CreateTable 34 | CREATE TABLE `File` ( 35 | `pkValue` VARCHAR(191) NOT NULL, 36 | `pkName` VARCHAR(191) NOT NULL, 37 | `fileName` VARCHAR(191) NULL, 38 | `filePath` VARCHAR(191) NULL, 39 | `userId` VARCHAR(191) NOT NULL, 40 | 41 | UNIQUE INDEX `File_fileName_key`(`fileName`), 42 | UNIQUE INDEX `File_filePath_key`(`filePath`), 43 | PRIMARY KEY (`pkValue`) 44 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 45 | 46 | -- AddForeignKey 47 | ALTER TABLE `File` ADD CONSTRAINT `File_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 48 | -------------------------------------------------------------------------------- /prisma/migrations/20231012063134_/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE `Role` ( 3 | `id` INTEGER NOT NULL AUTO_INCREMENT, 4 | `name` VARCHAR(191) NOT NULL, 5 | `code` INTEGER NOT NULL, 6 | `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 7 | `userId` VARCHAR(191) NOT NULL, 8 | 9 | PRIMARY KEY (`id`) 10 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 11 | 12 | -- CreateTable 13 | CREATE TABLE `Menu` ( 14 | `id` INTEGER NOT NULL AUTO_INCREMENT, 15 | `name` VARCHAR(191) NULL, 16 | `parentId` VARCHAR(191) NULL, 17 | `icon` VARCHAR(191) NULL, 18 | `type` VARCHAR(191) NULL, 19 | `route` VARCHAR(191) NULL, 20 | `filePath` VARCHAR(191) NULL, 21 | `orderNumber` INTEGER NULL, 22 | `url` VARCHAR(191) NULL, 23 | `show` BOOLEAN NULL, 24 | `authCode` VARCHAR(191) NULL, 25 | 26 | PRIMARY KEY (`id`) 27 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 28 | 29 | -- CreateTable 30 | CREATE TABLE `Role_Menu` ( 31 | `id` INTEGER NOT NULL AUTO_INCREMENT, 32 | `roleId` INTEGER NOT NULL, 33 | `menuId` INTEGER NOT NULL, 34 | 35 | UNIQUE INDEX `Role_Menu_roleId_menuId_key`(`roleId`, `menuId`), 36 | PRIMARY KEY (`id`) 37 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 38 | 39 | -- AddForeignKey 40 | ALTER TABLE `Role` ADD CONSTRAINT `Role_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 41 | 42 | -- AddForeignKey 43 | ALTER TABLE `Role_Menu` ADD CONSTRAINT `Role_Menu_roleId_fkey` FOREIGN KEY (`roleId`) REFERENCES `Role`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 44 | 45 | -- AddForeignKey 46 | ALTER TABLE `Role_Menu` ADD CONSTRAINT `Role_Menu_menuId_fkey` FOREIGN KEY (`menuId`) REFERENCES `Menu`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 47 | -------------------------------------------------------------------------------- /src/module/menu/menu.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Put,Param, Delete, Query } from '@nestjs/common'; 2 | import { ApiOperation } from '@nestjs/swagger'; 3 | import { MenuService } from './menu.service'; 4 | import { CreateMenuDto } from './dto/create-menu.dto'; 5 | import { UpdateMenuDto } from './dto/update-menu.dto'; 6 | import { pageDto } from './dto/page.dto'; 7 | import { RoleAuth } from 'src/common/decorator/auth-role.decorator'; 8 | 9 | 10 | @Controller('menu') 11 | export class MenuController { 12 | constructor(private readonly menuService: MenuService) {} 13 | 14 | @ApiOperation({ 15 | summary: '菜单列表' 16 | }) 17 | @Get('page') 18 | async findByPage(@Query() query: pageDto) { 19 | return this.menuService.findByPage(query); 20 | } 21 | 22 | @ApiOperation({ 23 | summary: '菜单列表' 24 | }) 25 | @Get() 26 | async findAllMenus() { 27 | return this.menuService.findAllMenus() 28 | } 29 | 30 | @ApiOperation({ 31 | summary: '获取菜单子项' 32 | }) 33 | @Get('children') 34 | async getChildren(@Query('parentId') parentId:string ) { 35 | return this.menuService.getChildren(parentId) 36 | } 37 | 38 | 39 | @ApiOperation({ 40 | summary: '创建菜单' 41 | }) 42 | @Post() 43 | async create(@Body() createMenuDto: CreateMenuDto) { 44 | return this.menuService.create(createMenuDto); 45 | } 46 | 47 | @ApiOperation({ 48 | summary: '更新菜单' 49 | }) 50 | @Put() 51 | async update(@Body('id') id: string ,@Body() updateMenuDto: UpdateMenuDto) { 52 | return this.menuService.update(id, updateMenuDto); 53 | } 54 | 55 | @ApiOperation({ 56 | summary: '删除菜单', 57 | description: '需要管理员权限' 58 | 59 | }) 60 | @RoleAuth('admin') 61 | @Delete(':id') 62 | async remove(@Param('id') id: string) { 63 | return this.menuService.remove(id); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /prisma/migrations/20231007142628_/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `File` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | - You are about to drop the column `pkName` on the `File` table. All the data in the column will be lost. 6 | - You are about to drop the column `pkValue` on the `File` table. All the data in the column will be lost. 7 | - You are about to drop the column `userId` on the `File` table. All the data in the column will be lost. 8 | - A unique constraint covering the columns `[fileName]` on the table `File` will be added. If there are existing duplicate values, this will fail. 9 | - A unique constraint covering the columns `[filePath]` on the table `File` will be added. If there are existing duplicate values, this will fail. 10 | - The required column `id` was added to the `File` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required. 11 | - Made the column `fileName` on table `File` required. This step will fail if there are existing NULL values in that column. 12 | - Made the column `filePath` on table `File` required. This step will fail if there are existing NULL values in that column. 13 | 14 | */ 15 | -- DropForeignKey 16 | ALTER TABLE `File` DROP FOREIGN KEY `File_userId_fkey`; 17 | 18 | -- AlterTable 19 | ALTER TABLE `File` DROP PRIMARY KEY, 20 | DROP COLUMN `pkName`, 21 | DROP COLUMN `pkValue`, 22 | DROP COLUMN `userId`, 23 | ADD COLUMN `id` VARCHAR(191) NOT NULL, 24 | MODIFY `fileName` VARCHAR(191) NOT NULL, 25 | MODIFY `filePath` VARCHAR(191) NOT NULL, 26 | ADD PRIMARY KEY (`id`); 27 | 28 | -- CreateIndex 29 | CREATE UNIQUE INDEX `File_fileName_key` ON `File`(`fileName`); 30 | 31 | -- CreateIndex 32 | CREATE UNIQUE INDEX `File_filePath_key` ON `File`(`filePath`); 33 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | mysql: 5 | image: mysql:8.0 6 | container_name: my_mysql_container 7 | ports: 8 | - '3306:3306' 9 | restart: on-failure 10 | environment: 11 | MYSQL_DATABASE: meng_admin 12 | MYSQL_ROOT_PASSWORD: admin123 13 | 14 | volumes: 15 | - mysql_data:/var/lib/mysql 16 | networks: 17 | - my_network 18 | 19 | redis: 20 | image: redis:latest 21 | container_name: my_redis_container 22 | command: redis-server --appendonly yes 23 | restart: unless-stopped 24 | ports: 25 | - '6379:6379' 26 | volumes: 27 | - redis_data:/data 28 | networks: 29 | - my_network 30 | 31 | minio: 32 | image: bitnami/minio:latest 33 | container_name: my-minio 34 | ports: 35 | - '9000:9000' 36 | - '9001:9001' 37 | environment: 38 | MINIO_ROOT_USER: minio 39 | MINIO_ROOT_PASSWORD: minio@123 40 | volumes: 41 | - 'minio_data:/bitnami/minio/data' 42 | networks: 43 | - my_network 44 | 45 | server: 46 | image: server:1.0.0 47 | container_name: my-server 48 | environment: 49 | DATABASE_URL: 'mysql://root:admin123@my_mysql_container:3306/meng_admin' 50 | REDIS_HOST: my_redis_container 51 | MINIO_HOST: my-minio 52 | MINIO_PORT: 9000 53 | 54 | ports: 55 | - '3000:3000' 56 | - '3001:3001' 57 | networks: 58 | - my_network 59 | depends_on: 60 | - mysql 61 | - redis 62 | - minio 63 | 64 | web: 65 | image: web:1.0.0 66 | container_name: my-web-app 67 | environment: 68 | SERVER_URL: http://server:3000 69 | FILE_URL: http://minio:9000/ 70 | WS_URL: http://server:3001 71 | ports: 72 | - '5713:80' 73 | networks: 74 | - my_network 75 | 76 | networks: 77 | my_network: 78 | driver: bridge 79 | 80 | volumes: 81 | mysql_data: 82 | redis_data: 83 | minio_data: 84 | driver: local 85 | -------------------------------------------------------------------------------- /src/module/upload/upload.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common'; 2 | import {MINIO_CONNECTION} from 'nestjs-minio' 3 | import {Client} from 'minio' 4 | import { Cron } from '@nestjs/schedule'; 5 | import { PrismaService } from 'src/services/prisma.service'; 6 | import { ConfigService } from '@nestjs/config'; 7 | 8 | 9 | @Injectable() 10 | export class UploadService { 11 | 12 | constructor( 13 | @Inject(MINIO_CONNECTION) private readonly minioClient: Client, 14 | private readonly prisma: PrismaService, 15 | private readonly configService: ConfigService 16 | ) { 17 | } 18 | /** 19 | * @description 上传文件 20 | * @date 10/18/2023 21 | * @param file 22 | */ 23 | async uploadFile(file:Express.Multer.File){ 24 | const fileName = `${Date.now() + '-' + Math.round(Math.random() * 10)}_${file.originalname}`; 25 | const filePath = `/file/${this.configService.get('bucket').name}/${fileName}` 26 | // 上传文件到minio服务器 27 | const fileEntity = await this.createFile(fileName, filePath) 28 | await this.minioClient.putObject(this.configService.get('bucket').name, fileName, file.buffer) 29 | return fileEntity; 30 | } 31 | async createFile(fileName: string,filePath:string){ 32 | const fileEntity = await this.prisma.file.create({ 33 | data: { 34 | fileName, 35 | filePath, 36 | userId: null 37 | } 38 | }) 39 | return fileEntity 40 | } 41 | 42 | /** 43 | * @description 执行定时任务,清除脏数据文件 44 | */ 45 | @Cron('0 0 0 * * *') 46 | async clearEmptyUserIdFiles(){ 47 | const fileRecordToDelete = await this.prisma.file.findMany({ 48 | where: { 49 | userId: null 50 | } 51 | }) 52 | await this.prisma.$transaction(async(prisma) => { 53 | await Promise.all([ 54 | fileRecordToDelete.map(record => { 55 | this.minioClient.removeObject(this.configService.get('bucket').name, record.fileName) 56 | }), 57 | prisma.file.deleteMany({ 58 | where: { 59 | userId: null 60 | } 61 | }) 62 | 63 | ]) 64 | }) 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; 2 | import { APP_INTERCEPTOR } from '@nestjs/core'; 3 | import {ConfigModule, ConfigService} from '@nestjs/config' 4 | import * as path from 'path'; 5 | import { ScheduleModule } from '@nestjs/schedule' 6 | import { 7 | AcceptLanguageResolver, 8 | I18nModule, 9 | QueryResolver, 10 | } from 'nestjs-i18n'; 11 | import config from './config'; 12 | import { UserModule } from './module/user/user.module'; 13 | import { AuthModule } from './module/auth/auth.module'; 14 | import { MenuModule } from './module/menu/menu.module'; 15 | import { RoleModule } from './module/role/role.module'; 16 | import { SocketModule } from './socket/socket.module'; 17 | import { LoginLogModule } from './module/login-log/login-log.module'; 18 | import { CacheModule } from './module/cache/cache.module'; 19 | import { UploadModule } from './module/upload/upload.module'; 20 | import { AppController } from './app.controller'; 21 | import { AppService } from './app.service'; 22 | import { PrismaService } from './services/prisma.service'; 23 | import { LoggerService } from './services/logger.service'; 24 | import { EmailService } from './services/mail.service'; 25 | import {LoggerMiddleware} from "./common/middware/logger.middleware"; 26 | import { AuthInterceptor } from './common/interceptor/auth.interceptor'; 27 | 28 | @Module({ 29 | imports: [ 30 | UserModule, 31 | I18nModule.forRoot({ 32 | fallbackLanguage: 'zh-CN', 33 | loaderOptions: { 34 | path: path.join(__dirname, '/i18n/'), 35 | watch: true, 36 | }, 37 | resolvers: [ 38 | { use: QueryResolver, options: ['lang'] }, 39 | AcceptLanguageResolver, 40 | ] 41 | }), 42 | ScheduleModule.forRoot(), 43 | AuthModule, 44 | ConfigModule.forRoot({ 45 | isGlobal: true, 46 | load: [...config] 47 | }), CacheModule, UploadModule, MenuModule, RoleModule, SocketModule, LoginLogModule], 48 | controllers: [AppController], 49 | providers: [ 50 | AppService, PrismaService, LoggerService, ConfigService, EmailService, 51 | { 52 | provide: APP_INTERCEPTOR, 53 | useClass: AuthInterceptor, 54 | } 55 | ], 56 | }) 57 | export class AppModule implements NestModule { 58 | configure(consumer: MiddlewareConsumer) { 59 | consumer.apply(LoggerMiddleware).forRoutes('*'); 60 | } 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/module/role/role.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Param, Delete, Query, Put } from '@nestjs/common'; 2 | import { RoleService } from './role.service'; 3 | import { CreateRoleDto } from './dto/create-role.dto'; 4 | import { UpdateRoleDto } from './dto/update-role.dto'; 5 | import { RolePageDto } from './dto/role.page.dto'; 6 | import { SetRoleMenuDto } from './dto/set-role-menu.dto'; 7 | import { RoleAuth } from 'src/common/decorator/auth-role.decorator'; 8 | import { ApiOperation } from '@nestjs/swagger'; 9 | 10 | @Controller('role') 11 | export class RoleController { 12 | constructor(private readonly roleService: RoleService) {} 13 | 14 | @ApiOperation({ 15 | summary: '获取全部角色' 16 | }) 17 | @Get() 18 | async getAllRoles(){ 19 | return this.roleService.getAllRoles() 20 | } 21 | 22 | @ApiOperation({ 23 | summary: '分页获取角色列表' 24 | }) 25 | @Get('list') 26 | async getRoleListByPage(@Query() query: RolePageDto) { 27 | return this.roleService.getRoleListByPage(query); 28 | } 29 | 30 | @ApiOperation({ 31 | summary: '获取单个角色' 32 | }) 33 | @Get(':id') 34 | async getSingleRole(@Param('id') id: string) { 35 | return this.roleService.getSingleRole(id); 36 | } 37 | 38 | @ApiOperation({ 39 | summary: '获取角色对应的菜单' 40 | }) 41 | @Get('menu/list/:roleId') 42 | async getRoleMenus(@Param('roleId') roleId: string) { 43 | return this.roleService.getMenusByRoleId(roleId) 44 | } 45 | 46 | @ApiOperation({ 47 | summary: '创建角色' 48 | }) 49 | @Post() 50 | async create(@Body() createRoleDto: CreateRoleDto) { 51 | return this.roleService.create(createRoleDto); 52 | } 53 | 54 | @ApiOperation({ 55 | summary: '为角色分配菜单' 56 | }) 57 | @Post('alloc/menu') 58 | async setRoleMenus(@Body() setRoleMenuDto: SetRoleMenuDto) { 59 | return this.roleService.setRoleMenu(setRoleMenuDto) 60 | } 61 | 62 | 63 | @ApiOperation({ 64 | summary: '更新角色' 65 | }) 66 | @Put() 67 | async updateRole(@Body('id') id: string, @Body() updateRoleDto: UpdateRoleDto) { 68 | return this.roleService.updateRole(id, updateRoleDto); 69 | } 70 | 71 | @ApiOperation({ 72 | summary: '删除角色', 73 | description: '需要管理员权限' 74 | }) 75 | @RoleAuth('admin') 76 | @Delete(':id') 77 | async removeRole(@Param('id') id: string) { 78 | return this.roleService.removeRole(id); 79 | } 80 | 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "mysql" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model User { 14 | id String @id 15 | userName String @unique 16 | nickName String 17 | password String 18 | phoneNumber String @unique 19 | email String @unique 20 | avatar String? 21 | sex Int? 22 | createdAt DateTime @default(now()) 23 | updateAt DateTime @updatedAt() 24 | user_Role User_Role[] 25 | 26 | } 27 | 28 | model File { 29 | id String @id @default(uuid()) 30 | fileName String 31 | filePath String 32 | userId String? 33 | createdAt DateTime @default(now()) 34 | 35 | @@unique([userId]) 36 | } 37 | 38 | model Role { 39 | id String @id @default(uuid()) 40 | name String 41 | code String @unique 42 | createdAt DateTime @default(now()) 43 | Role_Menu Role_Menu[] 44 | User_Role User_Role[] 45 | } 46 | model Menu { 47 | id String @id @default(uuid()) 48 | name String? 49 | parentId String? 50 | icon String? 51 | type Int 52 | route String? 53 | filePath String? 54 | orderNumber Int? 55 | url String? 56 | show Boolean? 57 | authCode String? 58 | Role_Menu Role_Menu[] 59 | createdAt DateTime @default(now()) 60 | updateAt DateTime @updatedAt() 61 | } 62 | 63 | model Role_Menu { 64 | id String @id @default(uuid()) 65 | roleId String 66 | menuId String 67 | role Role @relation(fields: [roleId], references: [id]) 68 | menu Menu @relation(fields: [menuId], references: [id]) 69 | @@unique([roleId, menuId]) 70 | } 71 | 72 | model User_Role { 73 | id String @id @default(uuid()) 74 | roleId String 75 | role Role @relation(fields: [roleId], references: [id]) 76 | userId String 77 | user User @relation(fields: [userId], references: [id]) 78 | 79 | @@unique([roleId, userId]) 80 | } 81 | 82 | model Login_Log { 83 | id String @id @default(uuid()) 84 | userName String? 85 | ip String? 86 | address String? 87 | browser String? 88 | os String? 89 | status Boolean? 90 | message String? 91 | createAt DateTime @default(now()) 92 | } 93 | 94 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | 7 | env: 8 | REGISTRY: ghcr.io 9 | IMAGE_NAME: ${{ github.repository }} 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | id-token: write 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | 23 | - name: Setup Docker buildx 24 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf 25 | 26 | - name: Cache Docker layers 27 | uses: actions/cache@v2 28 | with: 29 | path: /tmp/.buildx-cache 30 | key: ${{ runner.os }}-buildx-${{ github.sha }} 31 | restore-keys: | 32 | ${{ runner.os }}-buildx- 33 | 34 | - name: Log into registry ${{ env.REGISTRY }} 35 | if: github.event_name != 'pull_request' 36 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 37 | with: 38 | registry: ${{ env.REGISTRY }} 39 | username: ${{ github.actor }} 40 | password: ${{ secrets.GITHUB_TOKEN }} 41 | 42 | - name: Extract Docker metadata 43 | id: meta 44 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 45 | with: 46 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 47 | 48 | - name: Build and push Docker image 49 | id: build-and-push 50 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 51 | with: 52 | context: . 53 | push: ${{ github.event_name != 'pull_request' }} 54 | tags: ${{ steps.meta.outputs.tags }} 55 | labels: ${{ steps.meta.outputs.labels }} 56 | cache-from: type=local,src=/tmp/.buildx-cache 57 | cache-to: type=local,dest=/tmp/.buildx-cache-new 58 | 59 | - name: Move cache 60 | run: | 61 | rm -rf /tmp/.buildx-cache 62 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache 63 | 64 | - name: SSH Command 65 | uses: D3rHase/ssh-command-action@v0.2.1 66 | with: 67 | HOST: ${{ secrets.SERVER_IP }} 68 | PORT: 22 69 | USER: root 70 | PRIVATE_SSH_KEY: ${{ secrets.SERVER_KEY }} 71 | # COMMAND: cd /root && ./run.sh 72 | 73 | -------------------------------------------------------------------------------- /src/module/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Param, Delete, Query, Put, Inject } from '@nestjs/common'; 2 | import { RedisClientType } from 'redis'; 3 | import { ApiOperation } from '@nestjs/swagger'; 4 | import { UserService } from './user.service'; 5 | import { EmailService } from 'src/services/mail.service'; 6 | import { UserDto } from './dto/user.dto'; 7 | import { UpdateUserDto } from './dto/update_user.dto'; 8 | import { pageDto } from './dto/page.dto'; 9 | import { R } from 'src/utils/common/error'; 10 | import { generateRandomCode } from 'src/utils/common/uuid'; 11 | import { RoleAuth } from 'src/common/decorator/auth-role.decorator'; 12 | 13 | 14 | 15 | @Controller('user') 16 | export class UserController { 17 | constructor( 18 | private readonly userService: UserService, 19 | @Inject('DEFAULT') private readonly redisClient: RedisClientType, 20 | private readonly emailService: EmailService 21 | ) {} 22 | 23 | @ApiOperation({ 24 | summary:'分页获取用户信息' 25 | }) 26 | @Get('list') 27 | async findByPage(@Query() query: pageDto) { 28 | return this.userService.findByPage(query) 29 | } 30 | 31 | @ApiOperation({ 32 | summary: '创建用户' 33 | }) 34 | @Post() 35 | async create(@Body() createUserDto: UserDto) { 36 | return this.userService.createUser(createUserDto); 37 | } 38 | 39 | @ApiOperation({ 40 | summary: '发送邮箱验证码' 41 | }) 42 | @Post('/send/email/captcha') 43 | async sendEmailCaptcha(@Body() emailInfo: {email: string}){ 44 | if(!emailInfo) { 45 | throw R.error('邮箱不能为空'); 46 | } 47 | //生成随机四位数 48 | const emailCaptcha = generateRandomCode() 49 | //生成的数据存在redis中,后面添加用户做验证 50 | await this.redisClient 51 | .multi() 52 | .set( 53 | `emailCaptcha:${emailInfo.email}`, 54 | emailCaptcha, 55 | ) 56 | .expire( `emailCaptcha:${emailInfo.email}`, 60 * 30) //30min 57 | .exec() 58 | 59 | this.emailService.sendEmail({ 60 | to: emailInfo.email, 61 | html: `
62 | 您本次的验证码是${emailCaptcha}, 验证码有效期是30分钟
`, 63 | subject: 'meng-admin平台邮箱检验提醒' 64 | }) 65 | } 66 | 67 | @ApiOperation({ 68 | summary: '更新用户信息' 69 | }) 70 | 71 | @Put() 72 | async update(@Body('id') id:string, @Body() body:UpdateUserDto){ 73 | return this.userService.updateUser(id, body) 74 | } 75 | 76 | @ApiOperation({ 77 | summary: '删除用户', 78 | description: '需要管理员权限' 79 | }) 80 | @RoleAuth('admin') 81 | @Delete(':id') 82 | async delete(@Param('id') id:string){ 83 | return this.userService.deleteUser(id) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/module/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, ClassSerializerInterceptor, UseInterceptors, Req } from '@nestjs/common' 2 | import { ApiOperation } from '@nestjs/swagger' 3 | import { Request } from 'express' 4 | import { UserService } from 'src/module/user/user.service' 5 | import { AuthService } from './auth.service' 6 | import { RsaService } from 'src/services/rsa.service' 7 | import { LoginDto } from './dto/login.dto' 8 | import { RefreshTokenDTO } from './dto/refreshToken.dto' 9 | import { ResetPasswordDto } from './dto/resetPassword.dto' 10 | import { UserVo } from 'src/module/user/dto/user-vo.dto' 11 | import { CaptchaEntity } from './entities/captcha.entity' 12 | import { NotLogin } from 'src/common/decorator/not-login.decorator' 13 | 14 | @Controller('auth') 15 | export class AuthController { 16 | constructor( 17 | private readonly authService: AuthService, 18 | private readonly userService: UserService, 19 | private readonly RsaService: RsaService, 20 | ) {} 21 | 22 | @ApiOperation({ 23 | summary: '登录', 24 | }) 25 | @Post('login') 26 | @NotLogin() 27 | async login(@Body() loginDto: LoginDto, @Req() req: Request) { 28 | return this.authService.login(loginDto, req) 29 | } 30 | 31 | @ApiOperation({ 32 | summary: '发送验证码', 33 | }) 34 | @Get('captcha') 35 | @UseInterceptors(ClassSerializerInterceptor) 36 | @NotLogin() 37 | async getCaptcha(): Promise { 38 | const data = await this.authService.genCaptcha() 39 | return new CaptchaEntity(data) 40 | } 41 | 42 | @ApiOperation({ 43 | summary: '获取公钥', 44 | }) 45 | @Get('publicKey') 46 | @NotLogin() 47 | async getPublicKey() { 48 | return this.RsaService.getPublicKey() 49 | } 50 | 51 | @ApiOperation({ 52 | summary: '刷新token', 53 | }) 54 | @Post('refresh/token') 55 | @NotLogin() 56 | async refreshToken(@Body() dto: RefreshTokenDTO) { 57 | this.authService.refreshToken(dto) 58 | } 59 | 60 | @ApiOperation({ 61 | summary: '获取用户信息', 62 | }) 63 | @Get('current/user') 64 | async getCurrentUser(@Req() req: Request): Promise { 65 | let user = await this.userService.findUserById(req['userInfo'].userId) 66 | return user 67 | } 68 | 69 | @ApiOperation({ 70 | summary: '退出登录', 71 | }) 72 | @Post('logout') 73 | async logout(@Req() req: Request): Promise { 74 | //清除token和refreshToken 75 | return this.authService.logout(req) 76 | } 77 | 78 | @ApiOperation({ 79 | summary: '修改密码时邮箱验证码', 80 | }) 81 | @Post('send/reset/password/email') 82 | @NotLogin() 83 | async sendResetPasswordEmail(@Body() emailInfo: { email: string }) { 84 | return this.authService.sendResetPasswordEmail(emailInfo) 85 | } 86 | 87 | @ApiOperation({ 88 | summary: '修改密码', 89 | }) 90 | @Post('reset/password') 91 | @NotLogin() 92 | async resetPassword(@Body() resetPasswordDto: ResetPasswordDto) { 93 | return this.authService.resetPassword(resetPasswordDto) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/utils/common/snow-flake.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'process'; 2 | 3 | export class SnowFlake { 4 | // 系统上线的时间戳,我这里设置为 2023-06-22 00:00:00 的时间戳 5 | epoch = BigInt(1687392000000); 6 | 7 | // 数据中心的位数 8 | dataCenterIdBits = 5; 9 | // 机器id的位数 10 | workerIdBits = 5; 11 | // 自增序列号的位数 12 | sequenceBits = 12; 13 | 14 | // 最大的数据中心id 这段位运算可以理解为2^5-1 = 31 15 | maxDataCenterId = (BigInt(1) << BigInt(this.workerIdBits)) - BigInt(1); 16 | // 最大的机器id 这段位运算可以理解为2^5-1 = 31 17 | maxWorkerId = (BigInt(1) << BigInt(this.workerIdBits)) - BigInt(1); 18 | 19 | // 时间戳偏移位数 20 | timestampShift = BigInt( 21 | this.dataCenterIdBits + this.workerIdBits + this.sequenceBits 22 | ); 23 | 24 | // 数据中心偏移位数 25 | dataCenterIdShift = BigInt(this.workerIdBits + this.sequenceBits); 26 | // 机器id偏移位数 27 | workerIdShift = BigInt(this.sequenceBits); 28 | // 自增序列号的掩码 29 | sequenceMask = (BigInt(1) << BigInt(this.sequenceBits)) - BigInt(1); 30 | // 记录上次生成id的时间戳 31 | lastTimestamp = BigInt(-1); 32 | // 数据中心id 33 | dataCenterId = BigInt(0); 34 | // 机器id 35 | workerId = BigInt(0); 36 | // 自增序列号 37 | sequence = BigInt(0); 38 | constructor(dataCenterId: number, workerId: number) { 39 | // 校验数据中心 ID 和工作节点 ID 的范围 40 | if (dataCenterId > this.maxDataCenterId || dataCenterId < 0) { 41 | throw new Error( 42 | `Data center ID must be between 0 and ${this.maxDataCenterId}` 43 | ); 44 | } 45 | 46 | if (workerId > this.maxWorkerId || workerId < 0) { 47 | throw new Error(`Worker ID must be between 0 and ${this.maxWorkerId}`); 48 | } 49 | 50 | this.dataCenterId = BigInt(dataCenterId); 51 | this.workerId = BigInt(workerId); 52 | } 53 | 54 | nextId() { 55 | let timestamp = BigInt(Date.now()); 56 | // 如果上一次生成id的时间戳比下一次生成的还大,说明服务器时间有问题,出现了回退,这时候再生成id,可能会生成重复的id,所以直接抛出异常。 57 | if (timestamp < this.lastTimestamp) { 58 | // 时钟回拨,抛出异常并拒绝生成 ID 59 | throw new Error('Clock moved backwards. Refusing to generate ID.'); 60 | } 61 | 62 | // 如果当前时间戳和上一次的时间戳相等,序列号加一 63 | if (timestamp === this.lastTimestamp) { 64 | // 同一毫秒内生成多个 ID,递增序列号,防止冲突 65 | this.sequence = (this.sequence + BigInt(1)) & this.sequenceMask; 66 | if (this.sequence === BigInt(0)) { 67 | // 序列号溢出,等待下一毫秒 68 | timestamp = this.waitNextMillis(this.lastTimestamp); 69 | } 70 | } else { 71 | // 不同毫秒,重置序列号 72 | this.sequence = BigInt(0); 73 | } 74 | 75 | this.lastTimestamp = timestamp; 76 | 77 | // 组合各部分生成最终的 ID,可以理解为把64位二进制转换位十进制数字 78 | const id = 79 | ((timestamp - this.epoch) << this.timestampShift) | 80 | (this.dataCenterId << this.dataCenterIdShift) | 81 | (this.workerId << this.workerIdShift) | 82 | this.sequence; 83 | 84 | return id.toString(); 85 | } 86 | 87 | waitNextMillis(lastTimestamp) { 88 | let timestamp = BigInt(Date.now()); 89 | while (timestamp <= lastTimestamp) { 90 | // 主动等待,直到当前时间超过上次记录的时间戳 91 | timestamp = BigInt(Date.now()); 92 | } 93 | return timestamp; 94 | } 95 | } 96 | 97 | // 如果有pm_id,把pm_id当机器id传进去 98 | export const snowFlake = new SnowFlake(0, +env.pm_id || 0); 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin-server", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^10.0.0", 24 | "@nestjs/config": "^3.1.1", 25 | "@nestjs/core": "^10.2.6", 26 | "@nestjs/mapped-types": "^1.2.2", 27 | "@nestjs/platform-express": "^10.2.6", 28 | "@nestjs/platform-ws": "^10.2.7", 29 | "@nestjs/schedule": "^4.0.0", 30 | "@nestjs/swagger": "^7.1.12", 31 | "@nestjs/websockets": "^10.2.7", 32 | "@prisma/client": "^5.3.1", 33 | "@types/useragent": "^2.3.3", 34 | "argon2": "^0.31.1", 35 | "class-transformer": "^0.5.1", 36 | "class-validator": "^0.14.0", 37 | "ip2region": "^2.3.0", 38 | "lodash": "^4.17.21", 39 | "minio": "^7.1.3", 40 | "multer": "1.4.5-lts.1", 41 | "nanoid": "^3.3.1", 42 | "nestjs-i18n": "^10.3.5", 43 | "nestjs-minio": "^2.5.1", 44 | "node-rsa": "^1.1.1", 45 | "nodemailer": "^6.9.5", 46 | "prisma-binding": "^2.3.16", 47 | "redis": "^4.6.10", 48 | "reflect-metadata": "^0.1.13", 49 | "rxjs": "^7.8.1", 50 | "svg-captcha": "^1.4.0", 51 | "useragent": "^2.3.0", 52 | "winston": "^3.10.0", 53 | "ws": "^8.14.2" 54 | }, 55 | "devDependencies": { 56 | "@nestjs/cli": "^10.1.18", 57 | "@nestjs/schematics": "^10.0.2", 58 | "@nestjs/testing": "^10.2.6", 59 | "@types/express": "^4.17.17", 60 | "@types/jest": "^29.5.5", 61 | "@types/lodash": "^4.14.199", 62 | "@types/multer": "^1.4.8", 63 | "@types/node": "^20.6.3", 64 | "@types/node-rsa": "^1.1.2", 65 | "@types/nodemailer": "^6.4.11", 66 | "@types/supertest": "^2.0.12", 67 | "@types/ws": "^8.5.8", 68 | "@typescript-eslint/eslint-plugin": "^5.62.0", 69 | "@typescript-eslint/parser": "^5.62.0", 70 | "eslint": "^8.50.0", 71 | "eslint-config-prettier": "^8.10.0", 72 | "eslint-plugin-prettier": "^4.2.1", 73 | "jest": "^29.7.0", 74 | "prettier": "^2.8.8", 75 | "prisma": "^5.3.1", 76 | "source-map-support": "^0.5.21", 77 | "supertest": "^6.3.3", 78 | "ts-jest": "^29.1.1", 79 | "ts-loader": "^9.4.4", 80 | "ts-node": "^10.9.1", 81 | "tsconfig-paths": "^4.2.0", 82 | "typescript": "^5.2.2" 83 | }, 84 | "jest": { 85 | "moduleFileExtensions": [ 86 | "js", 87 | "json", 88 | "ts" 89 | ], 90 | "rootDir": "src", 91 | "testRegex": ".*\\.spec\\.ts$", 92 | "transform": { 93 | "^.+\\.(t|j)s$": "ts-jest" 94 | }, 95 | "collectCoverageFrom": [ 96 | "**/*.(t|j)s" 97 | ], 98 | "coverageDirectory": "../coverage", 99 | "testEnvironment": "node" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/socket/socket.gateway.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketGateway, WebSocketServer, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage } from '@nestjs/websockets'; 2 | import { Inject } from '@nestjs/common'; 3 | import {IncomingMessage} from 'http' 4 | import { RedisClientType } from 'redis'; 5 | import { Server, WebSocket } from 'ws'; 6 | import { SocketMessage, SocketMessageType } from './interface/message'; 7 | 8 | @WebSocketGateway(3001,{ 9 | cors: { 10 | origin: '*' 11 | }, 12 | }) 13 | export class SocketGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { 14 | @WebSocketServer() 15 | server: Server; 16 | 17 | connects = new Map([]) 18 | 19 | constructor( 20 | @Inject('DEFAULT') private readonly RedisClient: RedisClientType 21 | ){} 22 | 23 | /** 24 | * @description 连接时 25 | * @date 10/20/2023 26 | */ 27 | afterInit() { 28 | this.server.emit('message'); 29 | } 30 | 31 | /** 32 | * @description 处理连接的动作 33 | * @date 10/20/2023 34 | * @param socket 35 | * @param request 36 | */ 37 | async handleConnection(socket: WebSocket, request:IncomingMessage) { 38 | const token = request.url.split('=').at(-1) 39 | 40 | if(!token) { 41 | socket.close() 42 | return 43 | } 44 | const userInfoStr = await this.RedisClient.get(`token:${token}`) 45 | if(!userInfoStr){ 46 | this.server.clients.forEach((socket) => { 47 | socket.send(JSON.stringify({ 48 | type: SocketMessageType.TokenExpire 49 | })) 50 | }) 51 | } 52 | const userInfo = JSON.parse(userInfoStr) 53 | 54 | this.addConnect(userInfo.userId, socket) 55 | socket.on('message', (data) => { 56 | this.handleMessage(data.toString()) 57 | 58 | }) 59 | 60 | } 61 | 62 | /** 63 | * @description 断开连接时 64 | * @date 10/20/2023 65 | * @param socket 66 | */ 67 | handleDisconnect(socket: WebSocket) { 68 | this.deleteConnect(socket) 69 | } 70 | 71 | /** 72 | * @description 添加连接 73 | * @date 10/20/2023 74 | * @param userId 用户id 75 | * @param connect 用户socket连接 76 | */ 77 | addConnect(userId:string, socket:WebSocket){ 78 | const curConnects = this.connects.get(userId) 79 | if(curConnects){ 80 | curConnects.push(socket) 81 | } else { 82 | this.connects.set(userId, [socket]) 83 | } 84 | } 85 | 86 | /** 87 | * @description 删除连接 88 | * @date 10/20/2023 89 | * @param connect 用户socket连接 90 | */ 91 | deleteConnect(connect: WebSocket) { 92 | const connects = [...this.connects.values()]; 93 | 94 | for (let i = 0; i < connects.length; i += 1) { 95 | const sockets = connects[i]; 96 | const index = sockets.indexOf(connect); 97 | if (index >= 0) { 98 | sockets.splice(index, 1); 99 | break; 100 | } 101 | } 102 | } 103 | 104 | handleMessage(payload: string) { 105 | // 处理客户端发送的消息 106 | const message = JSON.parse(payload) as SocketMessage; 107 | // 根据消息类型进行处理逻辑 108 | // ... 109 | } 110 | 111 | /** 112 | * @description 发送消息 113 | * @date 10/20/2023 114 | * @param userId 115 | * @param data 116 | */ 117 | sendMessage(userId: string, data: SocketMessage) { 118 | const sockets = this.connects.get(userId); 119 | if (sockets?.length) { 120 | sockets.forEach(socket => { 121 | socket.send(JSON.stringify(data)); 122 | }); 123 | } 124 | } 125 | } 126 | 127 | 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ pnpm install 33 | ``` 34 | 35 | ## deploy 36 | 37 | 项目为纯 docker 部署需要 docker version >= 24 启动项目前需要将数据库整理好,sql 文件会放在仓库里 38 | 39 | 将后端文件打包成 docker 镜像(需要先执行上面的 pnpm install) 40 | 41 | ``` 42 | docker build -t 镜像名称[:tag] ./ 43 | ``` 44 | 45 | ### 数据库操作 46 | 47 | 首先进入 mysql 容器 48 | 49 | ``` 50 | docker ps //查看正在启动的容器 51 | docker exec -it 容器id /bin/sh 52 | ``` 53 | 54 | ``` 55 | mysql -u root -p 56 | use meng_admin 57 | source sql文件(路径) 58 | ``` 59 | 60 | 若出现服务器 ip 不能连接数据库问题,执行以下命令 61 | 62 | ``` 63 | CREATE USER 'root'@'%' IDENTIFIED BY 'admin123'; 64 | GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; 65 | FLUSH PRIVILEGES; 66 | ``` 67 | 68 | ### nest 后端 69 | 70 | 需要同步 prisma 结构 71 | 72 | 首先进入 nest 后端容器 73 | 74 | ``` 75 | docker ps //查看正在启动的容器 76 | docker exec -it 容器id /bin/sh 77 | ``` 78 | 79 | 执行 80 | 81 | ``` 82 | prisma migrate dev 83 | ``` 84 | 85 | ## Running the app 86 | 87 | ``` 88 | docker-compose up 89 | ``` 90 | 91 | ## Support 92 | 93 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 94 | 95 | ## Stay in touch 96 | 97 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 98 | - Website - [https://nestjs.com](https://nestjs.com/) 99 | - Twitter - [@nestframework](https://twitter.com/nestframework) 100 | 101 | ## License 102 | 103 | Nest is [MIT licensed](LICENSE). 104 | -------------------------------------------------------------------------------- /src/module/menu/menu.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { omit } from 'lodash'; 3 | import { PrismaService } from 'src/services/prisma.service'; 4 | import { CreateMenuDto } from './dto/create-menu.dto'; 5 | import { UpdateMenuDto } from './dto/update-menu.dto'; 6 | import { pageDto } from './dto/page.dto'; 7 | import { R } from 'src/utils/common/error'; 8 | 9 | @Injectable() 10 | export class MenuService { 11 | constructor( 12 | private readonly prisma: PrismaService, 13 | ){} 14 | 15 | /** 16 | * @description 创建菜单 17 | * @date 10/10/2023 18 | * @param createMenuDto 19 | */ 20 | async create(createMenuDto: CreateMenuDto) { 21 | if(createMenuDto.route){ 22 | const menu = await this.prisma.menu.findFirst({ 23 | where: { 24 | route: createMenuDto.route 25 | } 26 | }) 27 | if(menu){ 28 | throw R.error('当前路由已存在'); 29 | 30 | } 31 | } 32 | return await this.prisma.menu.create({ 33 | data: { 34 | ...createMenuDto, 35 | } 36 | }) 37 | } 38 | 39 | /** 40 | * @description 获取所有菜单 41 | * @date 10/10/2023 42 | */ 43 | async findAllMenus(){ 44 | return await this.prisma.menu.findMany() 45 | } 46 | 47 | /** 48 | * @description 分页获取菜单 49 | * @date 10/10/2023 50 | */ 51 | async findByPage(query:pageDto) { 52 | const page = +query.page ? +query.page : 1 53 | const skip = (page - 1) * (+query.size) 54 | const take = +query.size 55 | const where = { 56 | route: { 57 | contains: query.route || undefined 58 | } 59 | }; 60 | const [data, total] = await Promise.all([ 61 | this.prisma.menu.findMany({ 62 | where, 63 | skip, 64 | take, 65 | }), 66 | this.prisma.menu.count({ where }), 67 | ]); 68 | if (!data.length) return { data: [], total: 0 }; 69 | 70 | const countMap = await this.prisma.menu.groupBy({ 71 | by: 'parentId', 72 | _count: { 73 | id: true 74 | }, 75 | 76 | }) 77 | 78 | const newArr = data.filter((item) => !item.parentId) 79 | const result = newArr.map((item) => { 80 | const count = countMap.find((o) => o.parentId === item.id)?._count.id || 0; 81 | 82 | return { 83 | ...item, 84 | hasChild: Number(count) > 0 85 | } 86 | }) 87 | 88 | return { 89 | data: result, 90 | total 91 | } 92 | } 93 | /** 94 | * @description 获取子菜单 95 | * @date 10/12/2023 96 | * @param parentId 97 | */ 98 | async getChildren(parentId: string){ 99 | if(!parentId){ 100 | throw R.validateError('父节点id不能为空') 101 | } 102 | const data = await this.prisma.menu.findMany({ 103 | where: { 104 | parentId, 105 | }, 106 | orderBy: { 107 | orderNumber: 'asc' 108 | } 109 | }) 110 | 111 | if(!data.length) return [] 112 | const countMap = await this.prisma.menu.groupBy({ 113 | by: 'parentId', 114 | _count: { 115 | id: true 116 | }, 117 | }) 118 | 119 | const result = data.map((item) => { 120 | const count = countMap.find((o) => o.parentId === item.id)?._count.id || 0; 121 | return { 122 | ...item, 123 | hasChild: Number(count) > 0 124 | } 125 | }) 126 | 127 | return result 128 | } 129 | 130 | /** 131 | * @description 获取单个菜单 132 | * @date 10/10/2023 133 | * @param id 134 | */ 135 | findOne(id: string) { 136 | return `This action returns a #${id} menu`; 137 | } 138 | 139 | async update(id: string, updateMenuDto: UpdateMenuDto) { 140 | updateMenuDto = omit(updateMenuDto, ['id', 'hasChild', 'children', '_loaded_']) 141 | await this.prisma.menu.update({ 142 | where: { 143 | id 144 | }, 145 | data: { 146 | ...updateMenuDto 147 | } 148 | }) 149 | } 150 | 151 | /** 152 | * @description 移除菜单 153 | * @date 10/14/2023 154 | * @param id 155 | */ 156 | async remove(id: string) { 157 | await this.prisma.$transaction(async(prisma) => { 158 | await Promise.all([ 159 | prisma.role_Menu.deleteMany({ 160 | where: { 161 | menuId: id 162 | } 163 | }), 164 | 165 | prisma.menu.delete({ 166 | where: { 167 | id 168 | } 169 | }), 170 | ]) 171 | }) 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/module/role/role.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { omit } from 'lodash'; 3 | import { PrismaService } from 'src/services/prisma.service'; 4 | import { CreateRoleDto } from './dto/create-role.dto'; 5 | import { UpdateRoleDto } from './dto/update-role.dto'; 6 | import { RolePageDto } from './dto/role.page.dto'; 7 | import { SetRoleMenuDto } from './dto/set-role-menu.dto'; 8 | import { SocketGateway } from 'src/socket/socket.gateway'; 9 | import { SocketMessageType } from 'src/socket/interface/message'; 10 | import { R } from 'src/utils/common/error'; 11 | 12 | 13 | @Injectable() 14 | export class RoleService { 15 | constructor( 16 | private readonly prisma: PrismaService, 17 | private readonly socketGateway: SocketGateway 18 | ){} 19 | /** 20 | * @description 获取全部角色 21 | * @date 10/15/2023 22 | */ 23 | async getAllRoles(){ 24 | return await this.prisma.role.findMany() 25 | } 26 | 27 | /** 28 | * @description 获取单个角色 29 | * @date 10/15/2023 30 | */ 31 | async getSingleRole(id:string){ 32 | return await this.prisma.role.findUnique({ 33 | where: { 34 | id 35 | } 36 | }) 37 | } 38 | 39 | /** 40 | * @description 创建角色 41 | * @date 10/15/2023 42 | */ 43 | async create(createRoleDto: CreateRoleDto) { 44 | const count = await this.prisma.role.count({ 45 | where: { 46 | code: createRoleDto.code 47 | } 48 | }) 49 | if(count) { 50 | throw R.error('代码不能重复') 51 | } 52 | return await this.prisma.$transaction(async (prisma) => { 53 | const role = await prisma.role.create({ 54 | data: { 55 | name: createRoleDto.name, 56 | code: createRoleDto.code 57 | } 58 | }) 59 | const roleMenus = createRoleDto.menuIds.map((menuId) => { 60 | return prisma.role_Menu.create({ 61 | data: { 62 | menuId: menuId, 63 | roleId: role.id 64 | } 65 | }) 66 | }) 67 | await Promise.all(roleMenus) 68 | }) 69 | } 70 | /** 71 | * @description 分页回去角色列表 72 | * @date 10/15/2023 73 | */ 74 | async getRoleListByPage(rolePageDto: RolePageDto){ 75 | const {page, size, name, code} = rolePageDto 76 | const where = { 77 | name: { 78 | contains: name 79 | }, 80 | code: { 81 | contains: code 82 | } 83 | } 84 | 85 | const [data, total] = await Promise.all([ 86 | this.prisma.role.findMany({ 87 | skip: (+page - 1) * +size, 88 | take: +size, 89 | where, 90 | orderBy: { 91 | createdAt: 'desc' 92 | } 93 | }), 94 | this.prisma.role.count({where}) 95 | ]) 96 | 97 | return { 98 | data, 99 | total 100 | } 101 | } 102 | 103 | /** 104 | * @description 通过角色id获取菜单 105 | * @date 10/16/2023 106 | * @param roleId 107 | */ 108 | async getMenusByRoleId(roleId: string){ 109 | const curRoleMenus = await this.prisma.role_Menu.findMany({ 110 | where: { 111 | roleId 112 | } 113 | }) 114 | return curRoleMenus.map((o) => o.menuId) 115 | } 116 | 117 | /** 118 | * @description 为角色分配菜单 119 | * @date 10/16/2023 120 | * @param setRoleMenuDto 121 | */ 122 | async setRoleMenu(setRoleMenuDto: SetRoleMenuDto) { 123 | const {checkedKeys, roleId} = setRoleMenuDto 124 | return await this.prisma.$transaction(async (prisma) => { 125 | const curRoleMenus= await this.prisma.role_Menu.findMany({ 126 | where: { 127 | roleId 128 | } 129 | }) 130 | 131 | const existingMenuIds = curRoleMenus.map(roleMenu => roleMenu.menuId) 132 | const newMenuIds = checkedKeys.filter(menuId => !existingMenuIds.includes(menuId)) 133 | const roleMenusToDelete = curRoleMenus.filter(roleMenu => !checkedKeys.includes(roleMenu.menuId)) 134 | 135 | //跟用户改角色一样,先浅比较一下 136 | if(existingMenuIds.length !== newMenuIds.length) { 137 | //查到所有分配了该角色的用户发消息 138 | const userIds = (await this.prisma.user_Role.findMany({ 139 | where: { 140 | roleId 141 | } 142 | })).map((userRole) =>userRole.userId) 143 | 144 | userIds.forEach((userId) => { 145 | this.socketGateway.sendMessage(userId, { 146 | type: SocketMessageType.PermissionChange 147 | }) 148 | }) 149 | 150 | //再深比较 151 | const oldMenuSortId = existingMenuIds.sort() 152 | const newMenuSortId = newMenuIds.sort() 153 | 154 | if(oldMenuSortId.join('') !== newMenuSortId.join('')){ 155 | //查到所有分配了该角色的用户发消息 156 | const userIds = (await this.prisma.user_Role.findMany({ 157 | where: { 158 | roleId 159 | } 160 | })).map((userRole) =>userRole.userId) 161 | userIds.forEach((userId) => { 162 | this.socketGateway.sendMessage(userId, { 163 | type: SocketMessageType.PermissionChange 164 | }) 165 | }) 166 | } 167 | const roleMenusToCreate = newMenuIds.map((menuId) => { 168 | return prisma.role_Menu.create({ 169 | data: { 170 | roleId, 171 | menuId 172 | } 173 | }) 174 | }) 175 | await prisma.role_Menu.deleteMany({ 176 | where: { 177 | id: { 178 | in: roleMenusToDelete.map((o) => o.id) 179 | } 180 | } 181 | }) 182 | await Promise.all(roleMenusToCreate) 183 | } 184 | }) 185 | } 186 | 187 | /** 188 | * @description 更新角色 189 | * @date 10/16/2023 190 | * @param id 191 | * @param updateRoleDto 192 | */ 193 | async updateRole(id: string, updateRoleDto: UpdateRoleDto) { 194 | updateRoleDto = omit(updateRoleDto, ['id']) 195 | 196 | return await this.prisma.$transaction(async(prisma) => { 197 | const role = await prisma.role.update({ 198 | where: { 199 | id 200 | }, 201 | data: { 202 | ...updateRoleDto 203 | } 204 | }) 205 | if(Array.isArray(updateRoleDto.menuIds)){ 206 | const existingRoleMenus = await prisma.role_Menu.findMany({ 207 | where: { 208 | roleId: id 209 | } 210 | }) 211 | const existingMenuIds = existingRoleMenus.map(roleMenu => roleMenu.menuId) 212 | const newMenuIds = updateRoleDto.menuIds.filter(menuId => !existingMenuIds.includes(menuId)) 213 | 214 | //跟用户改角色一样,先浅比较一下 215 | if(existingMenuIds.length !== newMenuIds.length) { 216 | //查到所有分配了该角色的用户发消息 217 | 218 | const userIds = (await this.prisma.user_Role.findMany({ 219 | where: { 220 | roleId:id 221 | } 222 | })).map((userRole) =>userRole.userId) 223 | } 224 | //再深比较 225 | const oldMenuSortId = existingMenuIds.sort() 226 | const newMenuSortId = newMenuIds.sort() 227 | 228 | if(oldMenuSortId.join('') !== newMenuSortId.join('')){ 229 | //查到所有分配了该角色的用户发消息 230 | 231 | const userIds = (await this.prisma.user_Role.findMany({ 232 | where: { 233 | roleId:id 234 | } 235 | })).map((userRole) =>userRole.userId) 236 | 237 | userIds.forEach((userId) => this.socketGateway.sendMessage(userId,{ 238 | type: SocketMessageType.PermissionChange 239 | })) 240 | 241 | } 242 | const roleMenusToDelete = existingRoleMenus.filter(roleMenu => !updateRoleDto.menuIds.includes(roleMenu.menuId)) 243 | const roleMenusToCreate = newMenuIds.map(menuId => { 244 | return prisma.role_Menu.create({ 245 | data: { 246 | menuId: menuId, 247 | roleId: role.id 248 | } 249 | }) 250 | }) 251 | await prisma.role_Menu.deleteMany({ 252 | where: { 253 | id: { 254 | in: roleMenusToDelete.map(roleMenu => roleMenu.id) 255 | } 256 | } 257 | }) 258 | await Promise.all(roleMenusToCreate) 259 | } 260 | return role 261 | }) 262 | } 263 | 264 | 265 | /** 266 | * @description 移除角色 267 | * @date 10/16/2023 268 | * @param id 269 | */ 270 | async removeRole(id: string) { 271 | await this.prisma.$transaction(async(prisma) => { 272 | await Promise.all([ 273 | prisma.role_Menu.deleteMany({ 274 | where: { 275 | roleId: id 276 | } 277 | }), 278 | prisma.user_Role.deleteMany({ 279 | where: { 280 | roleId: id 281 | } 282 | }), 283 | prisma.role.delete({ 284 | where: { 285 | id 286 | } 287 | }) 288 | ]) 289 | }) 290 | 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/module/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common'; 2 | import { hash } from 'argon2'; 3 | import { RedisClientType } from 'redis'; 4 | import { omit} from 'lodash' 5 | import { PrismaService } from 'src/services/prisma.service'; 6 | import { EmailService } from 'src/services/mail.service'; 7 | import { UserDto } from './dto/user.dto'; 8 | import { UpdateUserDto } from './dto/update_user.dto'; 9 | import { SocketGateway } from 'src/socket/socket.gateway'; 10 | import { SocketMessageType } from 'src/socket/interface/message'; 11 | import { snowFlake } from 'src/utils/common/snow-flake'; 12 | import { R } from 'src/utils/common/error'; 13 | import { MINIO_CONNECTION } from 'nestjs-minio'; 14 | import { Client } from 'minio'; 15 | import { ConfigService } from '@nestjs/config'; 16 | 17 | @Injectable() 18 | export class UserService { 19 | constructor( 20 | private readonly prisma: PrismaService, 21 | @Inject('DEFAULT') private readonly redisClient: RedisClientType, 22 | @Inject(MINIO_CONNECTION) private readonly minioClient: Client, 23 | private readonly emailService: EmailService, 24 | private readonly socketGateway: SocketGateway, 25 | private readonly configService: ConfigService 26 | ){} 27 | 28 | /** 29 | * @description 获取当前用户 30 | * @date 10/01/2023 31 | * @param id 32 | */ 33 | async findUserById(id: string){ 34 | const user = await this.prisma.user.findUnique({ 35 | where: { 36 | id 37 | } 38 | }) 39 | const userRole = await this.prisma.user_Role.findMany( 40 | { 41 | where: { 42 | userId: id 43 | } 44 | } 45 | ) 46 | const menuIds = await this.prisma.role_Menu.findMany({ 47 | where: { 48 | roleId: { 49 | in : userRole.map((role) => role.roleId) 50 | } 51 | } 52 | }) 53 | const menus = await this.prisma.menu.findMany({ 54 | where: { 55 | id: { 56 | in : menuIds.map((menu) => menu.menuId) 57 | } 58 | } 59 | }) 60 | const fileEntity = await this.prisma.file.findMany({ 61 | where: { 62 | userId:id 63 | } 64 | }) 65 | return Object.assign(user, {menus,fileEntity}) 66 | } 67 | 68 | /** 69 | * @description 分页获取当前用户信息 70 | * @date 10/02/2023 71 | * @param parma 72 | */ 73 | async findByPage(parma: { page: string | number; size: string | number; nickName?: string; phoneNumber?: string; }){ 74 | const page = +parma.page ? +parma.page : 1 75 | const skip = (page - 1) * (+parma.size) 76 | const take = +parma.size 77 | const where = { 78 | nickName: { 79 | contains: parma.nickName || undefined, 80 | }, 81 | phoneNumber: { 82 | contains: parma.phoneNumber || undefined, 83 | }, 84 | }; 85 | 86 | const [data, total] = await Promise.all([ 87 | this.prisma.user.findMany({ 88 | where, 89 | skip, 90 | take, 91 | include: { 92 | user_Role: true, 93 | } 94 | }), 95 | this.prisma.user.count({ where }), 96 | 97 | ]); 98 | 99 | const fileEntitys = await this.prisma.file.findMany() 100 | let newData = []; 101 | data.forEach((item) => {item = Object.assign(omit(item, ['password', 'updateAt'])); newData.push(item); 102 | }) 103 | newData.map((item) => { 104 | item.user_Role = item.user_Role.map((item) => item.roleId) 105 | item.fileEntity = Object.assign(fileEntitys.filter((fileEntity) => fileEntity.userId === item.id)) 106 | }) 107 | return { 108 | data: newData, 109 | total 110 | } 111 | } 112 | 113 | /** 114 | * @description 创建新用户 115 | * @date 10/02/2023 116 | * @param createUserDto 117 | */ 118 | async createUser(createUserDto: UserDto){ 119 | const id = snowFlake.nextId().toString() 120 | const password = '123456' 121 | 122 | const emailCaptcha = await this.redisClient.get(`emailCaptcha:${createUserDto.email}`) 123 | if(emailCaptcha !== createUserDto.emailCaptcha){ 124 | throw R.error('邮箱验证码错误或失效') 125 | } 126 | await this.prisma.$transaction(async (prisma) => { 127 | await Promise.all([ 128 | prisma.user.create({ 129 | data: { 130 | id: id, 131 | ...omit(createUserDto, ['emailCaptcha', 'user_Role' ]), 132 | password: await hash(password), 133 | } 134 | }) 135 | ]) 136 | if(createUserDto.avatar){ 137 | await Promise.all([ 138 | prisma.file.updateMany({ 139 | where: { 140 | filePath: createUserDto.avatar 141 | }, 142 | data: { 143 | userId: id 144 | } 145 | }) 146 | ]) 147 | } 148 | const roleIdMap = createUserDto.user_Role.map((roleId) => { 149 | return prisma.user_Role.create({ 150 | data: { 151 | userId: id, 152 | roleId 153 | } 154 | }) 155 | }) 156 | await Promise.all(roleIdMap) 157 | 158 | return roleIdMap 159 | 160 | }) 161 | this.emailService.sendEmail({ 162 | to: createUserDto.email, 163 | subject: 'meng-admin平台账号创建成功', 164 | html: `
165 |

${createUserDto.nickName}, 你的账号开通成功

166 |

登录账号: ${createUserDto.email}

167 |

登陆密码: ${password}

168 |
169 | ` 170 | }) 171 | } 172 | /** 173 | * @description 更新用户 174 | * @date 10/03/2023 175 | * @param id 176 | * @param updateUserDto 177 | */ 178 | async updateUser(id:string, updateUserDto: UpdateUserDto){ 179 | updateUserDto = Object.assign(omit(updateUserDto, ['fileEntity','emailCaptcha'])) 180 | const fileEntity = await this.prisma.file.findFirst({ 181 | where: { 182 | userId: id 183 | } 184 | }) 185 | 186 | 187 | if(fileEntity && fileEntity.filePath != updateUserDto.avatar){ 188 | if(fileEntity && !updateUserDto.avatar){ 189 | this.prisma.$transaction(async(prisma) => { 190 | await Promise.all([ 191 | prisma.file.deleteMany({ 192 | where: { 193 | userId: id 194 | } 195 | }), 196 | this.minioClient.removeObject(this.configService.get('bucket').name, fileEntity.filePath) 197 | ]) 198 | }) 199 | 200 | } 201 | else if(fileEntity && updateUserDto.avatar){ 202 | await this.prisma.$transaction(async(prisma) => { 203 | await Promise.all([ 204 | prisma.file.deleteMany({ 205 | where: { 206 | userId: id 207 | } 208 | }), 209 | prisma.file.updateMany({ 210 | where: { 211 | filePath: updateUserDto.avatar 212 | }, 213 | data:{ 214 | userId: id, 215 | filePath: updateUserDto.avatar 216 | } 217 | }) 218 | 219 | ]) 220 | }) 221 | } 222 | } 223 | else if(!fileEntity && updateUserDto.avatar){ 224 | await this.prisma.file.create({ 225 | data: { 226 | filePath: updateUserDto.avatar, 227 | userId: id, 228 | fileName: `${Date.now() + '-' + Math.round(Math.random() * 10)}_${updateUserDto.avatar}` 229 | } 230 | }) 231 | } 232 | 233 | const userRole = await this.prisma.user_Role.findMany({ 234 | where: { 235 | userId: id 236 | } 237 | }) 238 | const oldRole = userRole.map((o) => o.roleId) 239 | const newRole = updateUserDto.user_Role 240 | //判断用户分配角色有无变化,有的话,发消息给前端 241 | if(oldRole.length !== newRole.length){ 242 | this.socketGateway.sendMessage(id, ({ 243 | type: SocketMessageType.PermissionChange 244 | })) 245 | } 246 | //若数量相等,再看是否菜单一致,若是一致排序应该没问题,然后转化为字符串比较,因为数组如果地址不同,就算值相等,也会出错 247 | const oldRoleSortId = oldRole.sort() 248 | const newRoleSortId = newRole.sort() 249 | if(oldRoleSortId.join('') !== newRoleSortId.join('')){ 250 | this.socketGateway.sendMessage(id, ({ 251 | type: SocketMessageType.PermissionChange 252 | })) 253 | } 254 | //要删除的id 255 | const userRoleToDelete = oldRole.filter((item) => !newRole.includes(item)) 256 | //要添加的id 257 | const userRoleToCreate = newRole.filter((item) => !oldRole.includes(item)) 258 | return await this.prisma.$transaction(async(prisma) => { 259 | 260 | const createRoleMap = userRoleToCreate.map((roleId) => { 261 | return prisma.user_Role.create({ 262 | data: { 263 | roleId, 264 | userId: id 265 | } 266 | }) 267 | }) 268 | await prisma.user_Role.deleteMany({ 269 | where: { 270 | roleId: { 271 | in: userRoleToDelete 272 | } 273 | } 274 | }) 275 | await Promise.all(createRoleMap) 276 | await prisma.user.update({ 277 | where: { 278 | id 279 | }, 280 | data: { 281 | ...omit(updateUserDto, ['emailCaptcha', 'user_Role']) 282 | } 283 | }) 284 | 285 | }) 286 | } 287 | 288 | /** 289 | * @description 删除用户 290 | * @date 10/03/2023 291 | * @param id 292 | */ 293 | async deleteUser(id:string){ 294 | await this.prisma.$transaction(async(prisma) => { 295 | await Promise.all([ 296 | prisma.file.deleteMany({ 297 | where: { 298 | userId:id 299 | } 300 | }), 301 | prisma.user_Role.deleteMany({ 302 | where: { 303 | userId: id 304 | } 305 | }), 306 | prisma.user.delete({ 307 | where: { 308 | id 309 | } 310 | }) 311 | ]) 312 | }) 313 | } 314 | 315 | } -------------------------------------------------------------------------------- /src/module/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common'; 2 | import {Request} from 'express' 3 | import {hash, verify} from 'argon2' 4 | import { RedisClientType } from 'redis'; 5 | import { ConfigService } from '@nestjs/config'; 6 | import * as svgCaptcha from 'svg-captcha'; 7 | import { RsaService } from 'src/services/rsa.service'; 8 | import { PrismaService } from 'src/services/prisma.service'; 9 | import { EmailService } from 'src/services/mail.service'; 10 | import { SocketGateway } from 'src/socket/socket.gateway'; 11 | import { CaptchaType, LoginDto } from './dto/login.dto'; 12 | import { RefreshTokenDTO } from './dto/refreshToken.dto'; 13 | import { ResetPasswordDto } from './dto/resetPassword.dto'; 14 | import { TokenVO } from './dto/token.dto'; 15 | import { SocketMessageType } from 'src/socket/interface/message'; 16 | import {R} from '../../utils/common/error' 17 | import {generateRandomCode} from '../../utils/common/uuid' 18 | import { uuid } from 'src/utils/common/uuid'; 19 | import { getAdressByIp, getIp, getUserAgent } from 'src/utils/common/ip'; 20 | 21 | @Injectable() 22 | export class AuthService { 23 | private chekcCapctha:CaptchaType 24 | //保存验证码 25 | constructor( 26 | private readonly prisma:PrismaService, 27 | private readonly config: ConfigService, 28 | @Inject('DEFAULT') private readonly redisClient: RedisClientType, 29 | private readonly EmailService: EmailService, 30 | private readonly RsaService: RsaService, 31 | private readonly socketGateway: SocketGateway 32 | ) {} 33 | /** 34 | * @description 返回验证码 35 | * @date 09/25/2023 36 | * @returns {*} captcha 37 | * @memberof AuthService 38 | */ 39 | public async genCaptcha():Promise { 40 | //持续创建 41 | const c = svgCaptcha.createMathExpr({ 42 | width: 100, 43 | height: 38, 44 | color: true, 45 | mathMin: 1, 46 | mathMax: 9, 47 | mathOperator: '+' 48 | }); 49 | this.chekcCapctha = Object.assign(c, { id: uuid(), time: new Date()}); 50 | return this.chekcCapctha 51 | } 52 | /** 53 | * @description 登录返回token 54 | * @date 09/25/2023 55 | * @returns {*} token,refreshtoken 56 | * @memberof AuthService 57 | */ 58 | public async login(loginDto: LoginDto, req:Request): Promise { 59 | const ip = getIp(req) 60 | const address = getAdressByIp(ip) 61 | const browser = getUserAgent(req).family 62 | const os = getUserAgent(req).os.toString() 63 | const accountNumber = loginDto.accountNumber 64 | 65 | try { 66 | //验证是否有这个用户 67 | const user = await this.prisma.user.findFirst({ 68 | where: { 69 | OR: [ 70 | { userName: accountNumber }, 71 | { email: accountNumber }, 72 | { phoneNumber: accountNumber} 73 | ] 74 | } 75 | }) 76 | 77 | if(!user) { 78 | console.log(user); 79 | throw R.error('用户名错误') 80 | } 81 | //获取私钥并且,把密码解密 82 | const password = await this.RsaService.decrypt(loginDto.publicKey, loginDto.password) 83 | //删除私钥 84 | await this.redisClient.del(`publicKey:${loginDto.publicKey}`) 85 | 86 | if(!password) { 87 | throw R.error('登陆出现异常,请重新登录'); 88 | } 89 | loginDto.password = password 90 | 91 | if(!await verify(user.password, loginDto.password)) { 92 | throw R.error('密码错误') 93 | } 94 | if(loginDto.captcha !== this.chekcCapctha.text) { 95 | throw R.error('验证码错误') 96 | } 97 | 98 | const status = true 99 | const message = '成功' 100 | 101 | await this.prisma.login_Log.create({ 102 | data: { 103 | ip, 104 | address, 105 | browser, 106 | os, 107 | userName:accountNumber, 108 | status, 109 | message 110 | } 111 | }) 112 | 113 | const expire:number = this.config.get('token').expire 114 | const refreshExpire:number = this.config.get('token').refreshExpire 115 | const token = uuid() 116 | const refreshToken = uuid() 117 | 118 | await this.redisClient 119 | .multi() 120 | .set(`token:${token}`, JSON.stringify({userId: user.id, refreshToken})) 121 | .expire(`token:${token}`, expire) 122 | .set(`refreshToken:${refreshToken}`, user.id) 123 | .expire(`refreshToken:${refreshToken}`, refreshExpire) 124 | .sAdd(`userToken_${user.id}`, token) 125 | .sAdd(`userRefreshToken_${user.id}`, refreshToken) 126 | .exec() 127 | return { 128 | expire, 129 | token, 130 | refreshExpire, 131 | refreshToken, 132 | } as TokenVO; 133 | 134 | } catch (error){ 135 | const status = false 136 | const message = error?.response || '登录失败' 137 | await this.prisma.login_Log.create({ 138 | data: { 139 | ip, 140 | os, 141 | address, 142 | browser, 143 | userName:accountNumber, 144 | status, 145 | message 146 | } 147 | }) 148 | throw R.error(message) 149 | } 150 | } 151 | 152 | 153 | /** 154 | * @description 拿到freshToken去获取新的token,refreshToken 155 | * @date — 09/26/2023 156 | * @returns token,refreshToken 157 | */ 158 | public async refreshToken(dto: RefreshTokenDTO): Promise{ 159 | const userId = await this.redisClient.get( 160 | `refreshToken:${dto.refreshToken}` 161 | ); 162 | if(!userId){ 163 | throw R.error('刷新token失败') 164 | } 165 | const expire:number = this.config.get('token').expire 166 | const token = uuid() 167 | 168 | await this.redisClient 169 | .multi() 170 | .set(`token:${token}`, JSON.stringify({userId: userId, refreshToken:dto.refreshToken})) 171 | .expire(`token:${token}`, expire) 172 | .exec() 173 | 174 | const refreshExpire = await this.redisClient.ttl( 175 | `refreshToken:${dto.refreshToken}` 176 | ) 177 | return { 178 | expire, 179 | token, 180 | refreshExpire, 181 | refreshToken: dto.refreshToken 182 | } as TokenVO 183 | } 184 | 185 | //退出登录 186 | /** 187 | * @description 退出登录 188 | * @date 09/27/2023 189 | */ 190 | async logout(req:Request){ 191 | const res = await this.redisClient 192 | .multi() 193 | .del(`token:${req['token']}`) 194 | .del(`refreshToken:${req['userInfo'].refreshToken}`) 195 | .exec() 196 | if(res.some(item => item[0])){ 197 | throw R.error('退出登录失败') 198 | } 199 | return true 200 | } 201 | 202 | //修改密码时发送邮箱验证码 203 | /** 204 | * @description 用于修改密码发送邮箱验证码 205 | * @date 10/02/2023 206 | */ 207 | async sendResetPasswordEmail(emailInfo:{email:string}){ 208 | if(!emailInfo.email) { 209 | throw R.error('邮箱不能为空') 210 | } 211 | 212 | const user = await this.prisma.user.findUnique({ 213 | where: { 214 | email: emailInfo.email 215 | } 216 | }) 217 | if(!user) { 218 | throw R.error('不存在当前邮箱') 219 | } 220 | 221 | const emailCaptcha = generateRandomCode() 222 | const mailCaptchaExpire = this.config.get('mailCaptchaExpire').expire 223 | await this.redisClient 224 | .multi() 225 | .set(`resetPasswordEmailCaptcha:${emailCaptcha}`, emailCaptcha) 226 | .expire(`resetPasswordEmailCaptcha:${emailCaptcha}`, mailCaptchaExpire) 227 | .exec() 228 | 229 | 230 | const resetPasswordUrl = `http://121.40.254.241/reset-password?email=${emailInfo.email}&emailCaptcha=${emailCaptcha}`; 231 | 232 | await this.EmailService.sendEmail({ 233 | to: emailInfo.email, 234 | html: `
235 |

236 | ${emailInfo.email}, 你好! 237 |

238 |

请先确认本邮件是否是你需要的。

239 |

请点击下面的地址,根据提示进行密码重置:

240 | 点击跳转到密码重置页面 247 |

如果单击上面按钮没有反应,请复制下面链接到浏览器窗口中,或直接输入链接。

248 |

249 | ${resetPasswordUrl} 250 |

251 |

如您未提交该申请,请不要理会此邮件,对此为您带来的不便深表歉意。

252 |

本次链接30分钟后失效

253 |
254 | meng-admin 255 |
256 |
`, 257 | subject: 'meng-admin平台密码重置提醒', 258 | }); 259 | } 260 | 261 | /** 262 | * @description 重置密码 263 | * @date 10/04/2023 264 | */ 265 | async resetPassword(resetPasswordDto: ResetPasswordDto){ 266 | const captcha = await this.redisClient.get(`resetPasswordEmailCaptcha:${resetPasswordDto.emailCaptcha}`) 267 | if(captcha !== resetPasswordDto.emailCaptcha){ 268 | throw R.error('邮箱验证码错误或已失效') 269 | } 270 | 271 | const user = await this.prisma.user.findUnique({ 272 | where: { 273 | email: resetPasswordDto.email 274 | } 275 | }) 276 | 277 | if(!user) throw R.error('邮箱不存在') 278 | 279 | const password = await this.RsaService.decrypt(resetPasswordDto.publicKey, resetPasswordDto.password) 280 | 281 | const token = await this.redisClient.sMembers(`userToken_${user.id}`) 282 | const refreshToken = await this.redisClient.sMembers(`userRefreshToken_${user.id}`) 283 | 284 | await this.prisma.$transaction(async(prisma) => { 285 | const hashPassword = await hash(password) 286 | await prisma.user.updateMany({ 287 | where: { 288 | email: resetPasswordDto.email 289 | }, 290 | data: { 291 | password: hashPassword 292 | } 293 | }) 294 | 295 | await Promise.all([ 296 | ...token.map((token) => this.redisClient.del(`token:${token}`)), 297 | ...refreshToken.map((refreshToken) => this.redisClient.del(`refreshToken:${refreshToken}`)), 298 | this.redisClient.del(`resetPasswordEmailCapthca:${resetPasswordDto.email}`) 299 | ]) 300 | 301 | this.socketGateway.sendMessage(user.id, { 302 | type: SocketMessageType.PasswordChange 303 | }) 304 | 305 | }) 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /meng_admin.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 8.0.33, for Win64 (x86_64) 2 | -- 3 | -- Host: localhost Database: meng_admin 4 | -- ------------------------------------------------------ 5 | -- Server version 8.0.27 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!50503 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `File` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `File`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!50503 SET character_set_client = utf8mb4 */; 25 | CREATE TABLE `File` ( 26 | `fileName` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 27 | `filePath` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 28 | `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 29 | `createdAt` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 30 | `userId` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 31 | PRIMARY KEY (`id`), 32 | UNIQUE KEY `File_userId_key` (`userId`) 33 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 34 | /*!40101 SET character_set_client = @saved_cs_client */; 35 | 36 | -- 37 | -- Dumping data for table `File` 38 | -- 39 | 40 | LOCK TABLES `File` WRITE; 41 | /*!40000 ALTER TABLE `File` DISABLE KEYS */; 42 | INSERT INTO `File` VALUES ('1699627615491-3_1693804756389.jpg','meng-admin/1699627615491-3_1693804756389.jpg','36d1c74e-0e75-4116-a94b-5d23f9bdb55b','2023-11-10 14:46:55.495',NULL),('1699626149748-1_author.jpeg','meng-admin/1699626149748-1_author.jpeg','3fc703bd-5ff3-4fc0-b0ae-3978c87f0069','2023-11-10 14:22:29.752',NULL),('1699625146635-0_2606cbf3a39aaf99.jpg','/upload/meng-admin/1699625146635-0_2606cbf3a39aaf99.jpg','46e9a036-99d8-40b4-a239-d76e30c286e1','2023-11-10 14:05:46.639',NULL),('1699458918835-2_2606cbf3a39aaf99.jpg','/file/meng-admin/1699628137575-4_2606cbf3a39aaf99.jpg','488c3f84-a2e2-4834-a65e-14d803222ecf','2023-11-08 15:55:18.845','50612742305873920'),('1699626001105-7_2606cbf3a39aaf99.jpg','meng-admin/1699626001105-7_2606cbf3a39aaf99.jpg','68a7e8b3-5ec9-4de5-a089-c36b8ea8733a','2023-11-10 14:20:01.109',NULL),('1699624997852-8_2606cbf3a39aaf99.jpg','/upload/meng-admin/1699624997852-8_2606cbf3a39aaf99.jpg','8f70e0f2-0037-4c5a-8ecc-863a74fc3139','2023-11-10 14:03:17.857',NULL),('1699628147155-9_1693804756389.jpg','/file/meng-admin/1699628147155-9_1693804756389.jpg','9bfc96e1-9a93-47c7-b419-589fd2cbf32a','2023-11-10 14:55:47.156',NULL),('1699627157070-0_2606cbf3a39aaf99.jpg','meng-admin/1699627157070-0_2606cbf3a39aaf99.jpg','a79195a4-0b44-4455-9ed6-7a5bd333c439','2023-11-10 14:39:17.075',NULL),('1699626859399-2_16.jpg','/file/meng-admin/1699628147155-9_1693804756389.jpg','b36bf808-f431-4160-a757-31f8d39e134d','2023-11-10 14:34:19.401','51316945366024192'),('1699628071154-8_15.jpg','/file/meng-admin/1699628071154-8_15.jpg','b6eda517-c48b-4fa5-a0d2-23294e7e2505','2023-11-10 14:54:31.295',NULL),('1699628109460-1_girl.jpg','/file/meng-admin/1699628109460-1_girl.jpg','d2e85a74-a97a-4d78-86a1-4bc7e8a9122d','2023-11-10 14:55:09.463',NULL),('1699623485210-0_2606cbf3a39aaf99.jpg','/upload/meng-admin/1699623485210-0_2606cbf3a39aaf99.jpg','d9659d65-fc4c-42ba-9861-d9ff026f7f7f','2023-11-10 13:38:05.246',NULL),('1699628111168-6_/file/meng-admin/1699628109460-1_girl.jpg','/file/meng-admin/1699628109460-1_girl.jpg','df3f8c50-b1b1-4e50-b6ae-b79de4566264','2023-11-10 14:55:11.169','50597556379451392'),('1699628137575-4_2606cbf3a39aaf99.jpg','/file/meng-admin/1699628137575-4_2606cbf3a39aaf99.jpg','dfba32fe-6013-40a1-8c4a-3c5a2202ad23','2023-11-10 14:55:37.576',NULL),('1699459863007-2_16.jpg','/upload/meng-admin/1699459863007-2_16.jpg','f145c61e-7898-4d3f-90d8-0558914c05e4','2023-11-08 16:11:03.014',NULL); 43 | /*!40000 ALTER TABLE `File` ENABLE KEYS */; 44 | UNLOCK TABLES; 45 | 46 | -- 47 | -- Table structure for table `Login_Log` 48 | -- 49 | 50 | DROP TABLE IF EXISTS `Login_Log`; 51 | /*!40101 SET @saved_cs_client = @@character_set_client */; 52 | /*!50503 SET character_set_client = utf8mb4 */; 53 | CREATE TABLE `Login_Log` ( 54 | `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 55 | `userName` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 56 | `ip` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 57 | `address` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 58 | `browser` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 59 | `os` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 60 | `status` tinyint(1) DEFAULT NULL, 61 | `message` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 62 | `createAt` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 63 | PRIMARY KEY (`id`) 64 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 65 | /*!40101 SET character_set_client = @saved_cs_client */; 66 | 67 | -- 68 | -- Dumping data for table `Login_Log` 69 | -- 70 | 71 | LOCK TABLES `Login_Log` WRITE; 72 | /*!40000 ALTER TABLE `Login_Log` DISABLE KEYS */; 73 | INSERT INTO `Login_Log` VALUES ('072f1eea-5f3b-4a0b-853e-80de1c52497e','admin','::1',' ','Chrome','Windows 10.0.0',0,'密码错误','2023-11-08 16:13:23.324'),('16bebc79-8904-4258-ba75-d93de683712c','admin','::1',' ','Chrome','Windows 10.0.0',1,'成功','2023-11-08 16:14:13.106'),('2bb3a207-9eb5-48b8-9e60-f5db198dc2f4','admin','172.22.0.1',' 内网IP','Chrome','Windows 10.0.0',1,'成功','2023-11-10 13:37:31.218'),('32985f10-6fde-434f-ab19-7ac00cf549f6','admin','::1',' ','Chrome','Windows 10.0.0',1,'成功','2023-11-08 14:57:38.999'),('6797d2e7-8da2-4a1b-bb93-b250e32bd854','admin','172.22.0.1',' 内网IP','Chrome','Windows 10.0.0',1,'成功','2023-11-11 14:23:14.433'),('69974b9e-093b-4748-85fe-41ee70e8fcdc','admin','::1',' ','Chrome','Windows 10.0.0',1,'成功','2023-11-08 16:13:56.855'),('77daaa55-26d2-44f0-8267-6953b4ea86d3','admin','::1',' ','Chrome','Windows 10.0.0',1,'成功','2023-11-08 16:14:26.958'),('8d8dc5ef-cf73-4bc2-b68c-19d65802d554','meng','::1',' ','Chrome','Windows 10.0.0',1,'成功','2023-11-08 15:58:18.162'),('a2dbb6b2-7a9c-4f5a-9f26-1736fe0ef601','admin','::1',' ','Chrome','Windows 10.0.0',0,'登录失败','2023-11-08 16:13:49.721'),('bcf2c97e-a91e-477b-92f2-45f9be5613aa','admin','::1',' ','Chrome','Windows 10.0.0',1,'成功','2023-11-08 16:09:41.996'),('da8168af-3d57-48b7-8494-b3e092ca3595','admin','::1',' ','Chrome','Windows 10.0.0',0,'密码错误','2023-11-08 16:14:21.885'),('ddf33d7e-46a3-44ab-88c1-4eacef405617','admin','172.22.0.1',' 内网IP','Chrome','Windows 10.0.0',0,'验证码错误','2023-11-10 13:37:26.898'),('e6b19f84-24d6-4ad9-a23e-494544b5ad41','admin','::1',' ','Chrome','Windows 10.0.0',0,'登录失败','2023-11-08 16:13:35.050'); 74 | /*!40000 ALTER TABLE `Login_Log` ENABLE KEYS */; 75 | UNLOCK TABLES; 76 | 77 | -- 78 | -- Table structure for table `Menu` 79 | -- 80 | 81 | DROP TABLE IF EXISTS `Menu`; 82 | /*!40101 SET @saved_cs_client = @@character_set_client */; 83 | /*!50503 SET character_set_client = utf8mb4 */; 84 | CREATE TABLE `Menu` ( 85 | `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 86 | `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 87 | `parentId` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 88 | `icon` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 89 | `type` int NOT NULL, 90 | `route` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 91 | `filePath` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 92 | `orderNumber` int DEFAULT NULL, 93 | `url` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 94 | `show` tinyint(1) DEFAULT NULL, 95 | `authCode` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 96 | `createdAt` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 97 | `updateAt` datetime(3) NOT NULL, 98 | PRIMARY KEY (`id`) 99 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 100 | /*!40101 SET character_set_client = @saved_cs_client */; 101 | 102 | -- 103 | -- Dumping data for table `Menu` 104 | -- 105 | 106 | LOCK TABLES `Menu` WRITE; 107 | /*!40000 ALTER TABLE `Menu` DISABLE KEYS */; 108 | INSERT INTO `Menu` VALUES ('00a57900-93fc-47ac-a8de-973a252256b6','查询日志','5e900b9e-2295-4c14-9226-4319c6a54980',NULL,3,NULL,NULL,10,NULL,0,'log:search','2023-11-08 15:05:32.222','2023-11-08 15:05:32.222'),('06faee57-4182-4867-b5e0-7f79fe958c88','菜单管理','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f','MenuOutlined',2,'/menu','/menu/index.tsx',10,'/menu',1,NULL,'2023-11-08 14:44:16.600','2023-11-08 14:44:16.600'),('09c8d00e-be1b-49b7-a919-aff013b3342a','不是我','f29d1c67-3aa2-413c-80ff-5ce70afb5fa4','CrownOutlined',2,'/not','/login-log/index.tsx',4,NULL,1,NULL,'2023-11-08 16:01:41.133','2023-11-08 16:01:41.133'),('1a99a88a-81f6-4467-8af6-19eca091b649','那就起来学习','45f88783-6e75-4d8a-8855-f849e62059ae','ColumnHeightOutlined',2,'/study','/role/index.tsx',4,NULL,1,NULL,'2023-11-08 16:04:58.141','2023-11-08 16:04:58.141'),('3d87d1f3-2f23-4019-b78b-bf7cd343a35d','仪表盘',NULL,'DingtalkOutlined',2,'/dashboard','/dashboard/index.tsx',10,'/dashboard',1,NULL,'2023-11-08 14:39:30.616','2023-11-08 14:39:30.616'),('3fa340e8-146f-4b49-94d7-4c5605de7ead','查找角色','c5bddf10-da68-47ac-b3ad-a2e1fb8b9513',NULL,3,NULL,NULL,10,NULL,0,'role:search','2023-11-08 15:04:01.860','2023-11-08 15:04:01.860'),('45f88783-6e75-4d8a-8855-f849e62059ae','睡不着','b3b3e7b6-2733-4e75-a2fc-65cb8a60afb9','IssuesCloseOutlined',1,'/shuibuzhao',NULL,5,NULL,1,NULL,'2023-11-08 16:04:17.364','2023-11-08 16:04:17.364'),('4c10c77b-38a2-4ca2-a628-5958edda4ee1','测试一下吧',NULL,'GitlabOutlined',1,'/test',NULL,4,NULL,1,NULL,'2023-11-08 15:59:03.662','2023-11-08 15:59:03.662'),('5e900b9e-2295-4c14-9226-4319c6a54980','日志管理','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f','CarryOutOutlined',2,'/login-log','/login-log/index.tsx',10,'/login-log',1,NULL,'2023-11-08 14:45:40.750','2023-11-08 14:45:40.750'),('60bd0492-d1f1-48f5-bc4d-af77f04bb52c','添加用户','edc1875d-febc-43f3-8286-84123ae19b67',NULL,3,NULL,NULL,10,NULL,0,'user:create','2023-11-08 15:03:28.365','2023-11-08 15:03:28.365'),('63033957-b5ac-4cdc-a37a-8f6d946647ae','新建菜单','06faee57-4182-4867-b5e0-7f79fe958c88',NULL,3,NULL,NULL,10,NULL,0,'menu:create','2023-11-08 15:04:42.005','2023-11-08 15:04:42.005'),('68ad8efe-8386-4c9d-b2b2-1dc6ab73860f','系统管理',NULL,'SettingOutlined',1,'/system',NULL,10,'/system',1,NULL,'2023-11-08 14:38:25.100','2023-11-08 14:38:25.100'),('7b9d899a-cc04-44d7-8829-44d5a23be5bc','查找用户','edc1875d-febc-43f3-8286-84123ae19b67',NULL,3,NULL,NULL,10,NULL,0,'user:search','2023-11-08 15:03:42.662','2023-11-08 15:03:42.662'),('7be14be1-28ce-4f0d-8b8c-fc544f306b65','添加角色','c5bddf10-da68-47ac-b3ad-a2e1fb8b9513',NULL,3,NULL,NULL,10,NULL,0,'role:create','2023-11-08 15:04:12.437','2023-11-08 15:04:12.437'),('8accde5b-0b19-42c9-a232-181e7f8c1ba9','表格管理',NULL,'ContainerOutlined',2,'/table','/table/index.tsx',10,'/table',1,NULL,'2023-11-08 14:40:23.096','2023-11-08 14:40:23.096'),('9021a4e2-f8c9-4535-a057-874bc7376336','你好坏',NULL,'DotChartOutlined',1,'/bad',NULL,4,NULL,1,NULL,'2023-11-08 16:00:20.956','2023-11-08 16:00:20.956'),('9c5efe3f-7de4-4520-ac55-39a9b0c36907','难受啊','4c10c77b-38a2-4ca2-a628-5958edda4ee1','AreaChartOutlined',2,'/sad','/user/index.tsx',5,NULL,1,NULL,'2023-11-08 15:59:35.523','2023-11-08 15:59:35.523'),('b3b3e7b6-2733-4e75-a2fc-65cb8a60afb9','晚安',NULL,'FallOutlined',1,'/wanan',NULL,4,NULL,1,NULL,'2023-11-08 16:03:53.529','2023-11-08 16:03:53.529'),('b73d8a61-6044-43c3-b164-0212094a6e6f','关于我',NULL,'InstagramOutlined',1,'/info',NULL,10,'/info',1,NULL,'2023-11-08 14:46:55.954','2023-11-08 14:51:51.099'),('c038457e-7319-4af6-99fe-89393c6845b0','个人管理','b73d8a61-6044-43c3-b164-0212094a6e6f','HeatMapOutlined',2,'/person','/about/index.tsx',5,NULL,1,NULL,'2023-11-08 15:08:33.217','2023-11-08 15:08:33.217'),('c5bddf10-da68-47ac-b3ad-a2e1fb8b9513','角色管理','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f','FireOutlined',2,'/role','/role/index.tsx',10,'/role',1,NULL,'2023-11-08 14:42:33.589','2023-11-08 14:42:33.589'),('edc1875d-febc-43f3-8286-84123ae19b67','用户管理','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f','UserOutlined',2,'/user','/user/index.tsx',15,'/user',1,NULL,'2023-11-08 14:41:31.876','2023-11-08 14:41:31.876'),('f29d1c67-3aa2-413c-80ff-5ce70afb5fa4','渣男','9021a4e2-f8c9-4535-a057-874bc7376336','ExclamationCircleOutlined',1,'/man',NULL,5,NULL,1,NULL,'2023-11-08 16:00:50.783','2023-11-08 16:00:50.783'); 109 | /*!40000 ALTER TABLE `Menu` ENABLE KEYS */; 110 | UNLOCK TABLES; 111 | 112 | -- 113 | -- Table structure for table `Role` 114 | -- 115 | 116 | DROP TABLE IF EXISTS `Role`; 117 | /*!40101 SET @saved_cs_client = @@character_set_client */; 118 | /*!50503 SET character_set_client = utf8mb4 */; 119 | CREATE TABLE `Role` ( 120 | `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 121 | `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 122 | `code` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 123 | `createdAt` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 124 | PRIMARY KEY (`id`), 125 | UNIQUE KEY `Role_code_key` (`code`) 126 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 127 | /*!40101 SET character_set_client = @saved_cs_client */; 128 | 129 | -- 130 | -- Dumping data for table `Role` 131 | -- 132 | 133 | LOCK TABLES `Role` WRITE; 134 | /*!40000 ALTER TABLE `Role` DISABLE KEYS */; 135 | INSERT INTO `Role` VALUES ('1cbebe30-6143-44d0-95c7-c1b9994d84c2','测试开发','301','2023-11-08 15:57:43.439'),('3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','管理员','1201','2023-11-08 15:57:57.392'),('570f3047-32e5-4ab7-a350-84e7fd0f1774','后端开发','600','2023-11-08 15:57:14.181'),('5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','前端开发','300','2023-11-08 14:55:51.204'),('c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','pm','120','2023-11-08 15:57:25.000'); 136 | /*!40000 ALTER TABLE `Role` ENABLE KEYS */; 137 | UNLOCK TABLES; 138 | 139 | -- 140 | -- Table structure for table `Role_Menu` 141 | -- 142 | 143 | DROP TABLE IF EXISTS `Role_Menu`; 144 | /*!40101 SET @saved_cs_client = @@character_set_client */; 145 | /*!50503 SET character_set_client = utf8mb4 */; 146 | CREATE TABLE `Role_Menu` ( 147 | `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 148 | `roleId` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 149 | `menuId` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 150 | PRIMARY KEY (`id`), 151 | UNIQUE KEY `Role_Menu_roleId_menuId_key` (`roleId`,`menuId`), 152 | KEY `Role_Menu_menuId_fkey` (`menuId`), 153 | CONSTRAINT `Role_Menu_menuId_fkey` FOREIGN KEY (`menuId`) REFERENCES `Menu` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE, 154 | CONSTRAINT `Role_Menu_roleId_fkey` FOREIGN KEY (`roleId`) REFERENCES `Role` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE 155 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 156 | /*!40101 SET character_set_client = @saved_cs_client */; 157 | 158 | -- 159 | -- Dumping data for table `Role_Menu` 160 | -- 161 | 162 | LOCK TABLES `Role_Menu` WRITE; 163 | /*!40000 ALTER TABLE `Role_Menu` DISABLE KEYS */; 164 | INSERT INTO `Role_Menu` VALUES ('341c17b2-17e1-4805-bc4e-46c184050b6f','1cbebe30-6143-44d0-95c7-c1b9994d84c2','00a57900-93fc-47ac-a8de-973a252256b6'),('3c35be8f-bb63-41f8-a029-d69282cce89a','1cbebe30-6143-44d0-95c7-c1b9994d84c2','06faee57-4182-4867-b5e0-7f79fe958c88'),('e97deff0-cfc6-4d31-ae4f-62122cc1b446','1cbebe30-6143-44d0-95c7-c1b9994d84c2','3d87d1f3-2f23-4019-b78b-bf7cd343a35d'),('56dc5540-eec8-41ac-a69d-aebffbca5c6d','1cbebe30-6143-44d0-95c7-c1b9994d84c2','3fa340e8-146f-4b49-94d7-4c5605de7ead'),('8234a290-6a1d-476f-90da-1d90b2911414','1cbebe30-6143-44d0-95c7-c1b9994d84c2','5e900b9e-2295-4c14-9226-4319c6a54980'),('2414024c-a8cd-49a8-a7e4-cde7237bd1b3','1cbebe30-6143-44d0-95c7-c1b9994d84c2','60bd0492-d1f1-48f5-bc4d-af77f04bb52c'),('c12d0063-8d1b-4073-b794-523bfb2d150b','1cbebe30-6143-44d0-95c7-c1b9994d84c2','63033957-b5ac-4cdc-a37a-8f6d946647ae'),('62b27c45-693a-4572-abd3-6e4ae69cd960','1cbebe30-6143-44d0-95c7-c1b9994d84c2','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f'),('dd037370-3423-489f-8341-784b446f76ed','1cbebe30-6143-44d0-95c7-c1b9994d84c2','7b9d899a-cc04-44d7-8829-44d5a23be5bc'),('3531fa17-a33c-4996-8b6b-ba8d65b582c0','1cbebe30-6143-44d0-95c7-c1b9994d84c2','7be14be1-28ce-4f0d-8b8c-fc544f306b65'),('22dafc93-03e2-464d-a9e4-b747eb1270a3','1cbebe30-6143-44d0-95c7-c1b9994d84c2','8accde5b-0b19-42c9-a232-181e7f8c1ba9'),('61908e31-567a-4ab4-b548-322e60fe8ec6','1cbebe30-6143-44d0-95c7-c1b9994d84c2','c5bddf10-da68-47ac-b3ad-a2e1fb8b9513'),('9b370e08-8b2b-445c-915c-63f91a07a7ac','1cbebe30-6143-44d0-95c7-c1b9994d84c2','edc1875d-febc-43f3-8286-84123ae19b67'),('58bf7a7b-f683-4123-a36d-f90d423c31e1','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','00a57900-93fc-47ac-a8de-973a252256b6'),('0135add1-29b1-499b-8199-6599c5385651','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','06faee57-4182-4867-b5e0-7f79fe958c88'),('dca5cc03-deea-4060-b24c-f132617a3fbc','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','09c8d00e-be1b-49b7-a919-aff013b3342a'),('b1d4856a-bda5-453c-b4ab-e6a10c097560','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','1a99a88a-81f6-4467-8af6-19eca091b649'),('e76423c4-0c75-42bc-a99e-8c15935453c2','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','3d87d1f3-2f23-4019-b78b-bf7cd343a35d'),('bc92c1b5-06a8-4588-93be-f316a060e914','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','3fa340e8-146f-4b49-94d7-4c5605de7ead'),('9a8c9166-abde-40eb-bc75-df3170c1b28d','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','45f88783-6e75-4d8a-8855-f849e62059ae'),('13e5c05c-b2fe-40c4-8e4f-efb189b87b4a','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','4c10c77b-38a2-4ca2-a628-5958edda4ee1'),('97db7745-b269-4052-ae72-fa681a003fd2','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','5e900b9e-2295-4c14-9226-4319c6a54980'),('2f7cffd6-001d-4a23-a776-ff01f3f38fc8','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','60bd0492-d1f1-48f5-bc4d-af77f04bb52c'),('b072cf5c-d876-4e31-8eb9-edb3c969b05a','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','63033957-b5ac-4cdc-a37a-8f6d946647ae'),('6178f7ca-4d37-45a7-9070-5051cc38a13c','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f'),('3127410b-027c-4adc-a56e-316c19ae6feb','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','7b9d899a-cc04-44d7-8829-44d5a23be5bc'),('28ccdb20-0a0e-433a-81f2-0c3cce3ebdc1','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','7be14be1-28ce-4f0d-8b8c-fc544f306b65'),('63c8a0f9-548a-4647-b091-e05ed379efdc','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','9021a4e2-f8c9-4535-a057-874bc7376336'),('0775c5c2-ca55-4a47-9ee7-3eb1e08cee24','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','9c5efe3f-7de4-4520-ac55-39a9b0c36907'),('4b436100-dc17-4beb-9862-34fccec92ffd','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','b3b3e7b6-2733-4e75-a2fc-65cb8a60afb9'),('ca7e8dcc-a852-4926-a127-b9276752d316','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','b73d8a61-6044-43c3-b164-0212094a6e6f'),('2504e01e-472c-4273-8d12-a6c0a7dbfe0e','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','c038457e-7319-4af6-99fe-89393c6845b0'),('ab6cc3ac-4caf-4cd1-a881-b6c75d719686','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','c5bddf10-da68-47ac-b3ad-a2e1fb8b9513'),('c33bb33e-fc46-473c-838c-94a3f49e6443','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','edc1875d-febc-43f3-8286-84123ae19b67'),('ab2c8bc1-a11d-4fbc-acc3-397102fbcd70','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','f29d1c67-3aa2-413c-80ff-5ce70afb5fa4'),('aaffe5e4-5a7d-461f-ba36-062fea2582e5','570f3047-32e5-4ab7-a350-84e7fd0f1774','00a57900-93fc-47ac-a8de-973a252256b6'),('82f5b396-4e4f-47d9-9bcf-f44bc001d856','570f3047-32e5-4ab7-a350-84e7fd0f1774','06faee57-4182-4867-b5e0-7f79fe958c88'),('7de8e82d-da3e-49c1-a336-6617ea364a25','570f3047-32e5-4ab7-a350-84e7fd0f1774','3d87d1f3-2f23-4019-b78b-bf7cd343a35d'),('4cecb3c1-71b9-47e0-a30f-e2ef03ced46a','570f3047-32e5-4ab7-a350-84e7fd0f1774','3fa340e8-146f-4b49-94d7-4c5605de7ead'),('c241bca4-2c1e-4ed2-8cf4-5081047670a3','570f3047-32e5-4ab7-a350-84e7fd0f1774','5e900b9e-2295-4c14-9226-4319c6a54980'),('958506be-c6e6-4fb8-a629-dc3932b3a6f5','570f3047-32e5-4ab7-a350-84e7fd0f1774','60bd0492-d1f1-48f5-bc4d-af77f04bb52c'),('a2846fd9-9535-43b5-98ac-a5971fb7e90c','570f3047-32e5-4ab7-a350-84e7fd0f1774','63033957-b5ac-4cdc-a37a-8f6d946647ae'),('697b3565-3ded-4154-acf2-c9375c946b2f','570f3047-32e5-4ab7-a350-84e7fd0f1774','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f'),('c69ee5d1-6f0f-443a-a901-de5c18d93629','570f3047-32e5-4ab7-a350-84e7fd0f1774','7b9d899a-cc04-44d7-8829-44d5a23be5bc'),('b3ee3e33-82e7-4f32-b80e-3a1014248f75','570f3047-32e5-4ab7-a350-84e7fd0f1774','7be14be1-28ce-4f0d-8b8c-fc544f306b65'),('cee45df4-836f-4d1c-81f9-9b089790b16c','570f3047-32e5-4ab7-a350-84e7fd0f1774','8accde5b-0b19-42c9-a232-181e7f8c1ba9'),('d3cadf87-3857-4bc6-901d-97e8ec07918a','570f3047-32e5-4ab7-a350-84e7fd0f1774','b73d8a61-6044-43c3-b164-0212094a6e6f'),('125fe03b-02ec-44cd-9e86-c1a208024649','570f3047-32e5-4ab7-a350-84e7fd0f1774','c038457e-7319-4af6-99fe-89393c6845b0'),('2bffadcf-3102-404c-a4a4-1a164b47ef97','570f3047-32e5-4ab7-a350-84e7fd0f1774','c5bddf10-da68-47ac-b3ad-a2e1fb8b9513'),('f3e729c2-5528-42e6-b735-f2611320d802','570f3047-32e5-4ab7-a350-84e7fd0f1774','edc1875d-febc-43f3-8286-84123ae19b67'),('17bf1fb1-0f4d-45b5-9765-da25c9b63b62','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','00a57900-93fc-47ac-a8de-973a252256b6'),('9857ac4e-dcf0-4715-ad63-81838289acc5','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','06faee57-4182-4867-b5e0-7f79fe958c88'),('54fd8a19-c827-4b1b-930c-098134e86fbd','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','3d87d1f3-2f23-4019-b78b-bf7cd343a35d'),('ef4b58f9-cb1c-4b89-a5fc-39683d52a3ba','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','3fa340e8-146f-4b49-94d7-4c5605de7ead'),('c65a07c9-b546-4b4a-817c-3b0042f76272','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','5e900b9e-2295-4c14-9226-4319c6a54980'),('5d61831b-ed3c-4c93-92a5-d6eb0ef97b3b','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','60bd0492-d1f1-48f5-bc4d-af77f04bb52c'),('0889963c-4673-43ec-90ce-6022c56faa3b','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','63033957-b5ac-4cdc-a37a-8f6d946647ae'),('ef4faaee-4ecb-4cf4-b8b6-f09e0246b486','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f'),('a6c5ca28-6648-48d3-9661-1f018dc6f5ed','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','7b9d899a-cc04-44d7-8829-44d5a23be5bc'),('a8135855-0cf6-4aed-a346-d4c29a699f01','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','7be14be1-28ce-4f0d-8b8c-fc544f306b65'),('bf896f05-cdf0-4dae-9030-b6863f6a803a','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','8accde5b-0b19-42c9-a232-181e7f8c1ba9'),('5366bab9-908d-40c0-8ec7-a9b354fcd207','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','b73d8a61-6044-43c3-b164-0212094a6e6f'),('8c7403a0-65c2-4d1b-a55d-5ea905c00288','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','c038457e-7319-4af6-99fe-89393c6845b0'),('a24b52c1-4a8b-48f1-a1f5-d189512aad6a','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','c5bddf10-da68-47ac-b3ad-a2e1fb8b9513'),('836b4701-9390-4152-948d-6edf084cd30c','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','edc1875d-febc-43f3-8286-84123ae19b67'),('0e36f478-9d7d-4e9b-b838-d6d4e7012cc7','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','00a57900-93fc-47ac-a8de-973a252256b6'),('ebc76dab-5efe-48a3-9e17-1a298957b5df','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','06faee57-4182-4867-b5e0-7f79fe958c88'),('5b034c2c-c949-4080-95c1-d65302622139','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','3fa340e8-146f-4b49-94d7-4c5605de7ead'),('95d6cf87-0284-48b9-a222-2c93f6a00f23','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','5e900b9e-2295-4c14-9226-4319c6a54980'),('488e99ee-06ca-429d-b935-d4ef1668cb95','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','60bd0492-d1f1-48f5-bc4d-af77f04bb52c'),('26d16c62-a3d6-4eac-b8fb-eefe70404291','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','63033957-b5ac-4cdc-a37a-8f6d946647ae'),('d39958cb-60e5-45af-9608-047959e2fd27','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','68ad8efe-8386-4c9d-b2b2-1dc6ab73860f'),('7dd01dce-021a-4c5b-905f-934b611badac','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','7b9d899a-cc04-44d7-8829-44d5a23be5bc'),('32012f73-c277-4c79-a073-25da107bb537','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','7be14be1-28ce-4f0d-8b8c-fc544f306b65'),('81a5280d-6744-4196-a8b3-4aa3115f14c1','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','8accde5b-0b19-42c9-a232-181e7f8c1ba9'),('829f0054-67bb-4134-9689-e612212fbac4','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','c5bddf10-da68-47ac-b3ad-a2e1fb8b9513'),('256aa90e-55ec-436c-b1b2-5344204b1264','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','edc1875d-febc-43f3-8286-84123ae19b67'); 165 | /*!40000 ALTER TABLE `Role_Menu` ENABLE KEYS */; 166 | UNLOCK TABLES; 167 | 168 | -- 169 | -- Table structure for table `User` 170 | -- 171 | 172 | DROP TABLE IF EXISTS `User`; 173 | /*!40101 SET @saved_cs_client = @@character_set_client */; 174 | /*!50503 SET character_set_client = utf8mb4 */; 175 | CREATE TABLE `User` ( 176 | `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 177 | `userName` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 178 | `nickName` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 179 | `password` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 180 | `phoneNumber` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 181 | `email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 182 | `avatar` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 183 | `sex` int DEFAULT NULL, 184 | `createdAt` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 185 | `updateAt` datetime(3) NOT NULL, 186 | PRIMARY KEY (`id`), 187 | UNIQUE KEY `User_userName_key` (`userName`), 188 | UNIQUE KEY `User_phoneNumber_key` (`phoneNumber`), 189 | UNIQUE KEY `User_email_key` (`email`) 190 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 191 | /*!40101 SET character_set_client = @saved_cs_client */; 192 | 193 | -- 194 | -- Dumping data for table `User` 195 | -- 196 | 197 | LOCK TABLES `User` WRITE; 198 | /*!40000 ALTER TABLE `User` DISABLE KEYS */; 199 | INSERT INTO `User` VALUES ('50597556379451392','admin','檬','$argon2id$v=19$m=65536,t=3,p=4$Lm3ZtEJTkX2Frf4aVsrtEQ$rHAQsbvq7I4kMlFfZMU2hwnGySB1p1VH/pXB+fXyZQ4','15512356987','1374744754@qq.com','/file/meng-admin/1699628109460-1_girl.jpg',0,'2023-11-08 14:56:37.503','2023-11-10 14:55:11.175'),('50612742305873920','meng','檬多','$argon2id$v=19$m=65536,t=3,p=4$YmPMsw6SgJiN0OfeWpVF2w$hpU6ovjH0JidKsEY7e4Iig/TB7lV3XPo0kpioXVXfLk','15571432650','w20021125@qq.com','/file/meng-admin/1699628137575-4_2606cbf3a39aaf99.jpg',0,'2023-11-08 15:56:58.113','2023-11-10 14:55:39.261'),('51316945366024192','test','测试用户','$argon2id$v=19$m=65536,t=3,p=4$LQWtIzfYKwhhegkG4A42Ow$m7jzrvUIsaAb/97bKmagVs3Nw3ZaWD9rYbQWsEpERHM','15571432659','w1374744754@163.com','/file/meng-admin/1699628147155-9_1693804756389.jpg',0,'2023-11-10 14:35:13.200','2023-11-10 14:55:48.720'); 200 | /*!40000 ALTER TABLE `User` ENABLE KEYS */; 201 | UNLOCK TABLES; 202 | 203 | -- 204 | -- Table structure for table `User_Role` 205 | -- 206 | 207 | DROP TABLE IF EXISTS `User_Role`; 208 | /*!40101 SET @saved_cs_client = @@character_set_client */; 209 | /*!50503 SET character_set_client = utf8mb4 */; 210 | CREATE TABLE `User_Role` ( 211 | `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 212 | `roleId` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 213 | `userId` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, 214 | PRIMARY KEY (`id`), 215 | UNIQUE KEY `User_Role_roleId_userId_key` (`roleId`,`userId`), 216 | KEY `User_Role_userId_fkey` (`userId`), 217 | CONSTRAINT `User_Role_roleId_fkey` FOREIGN KEY (`roleId`) REFERENCES `Role` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE, 218 | CONSTRAINT `User_Role_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE 219 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 220 | /*!40101 SET character_set_client = @saved_cs_client */; 221 | 222 | -- 223 | -- Dumping data for table `User_Role` 224 | -- 225 | 226 | LOCK TABLES `User_Role` WRITE; 227 | /*!40000 ALTER TABLE `User_Role` DISABLE KEYS */; 228 | INSERT INTO `User_Role` VALUES ('8207c05e-c1d6-4926-bcb1-1e87e5d4713a','1cbebe30-6143-44d0-95c7-c1b9994d84c2','50597556379451392'),('33bef241-d6ce-4dc3-b4bd-a889256ea318','1cbebe30-6143-44d0-95c7-c1b9994d84c2','51316945366024192'),('49abdbd0-9bc0-406e-ab16-1c052e86a202','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','50597556379451392'),('137eced4-e7ed-4838-b055-8bf501d5aadf','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','50612742305873920'),('891a10f8-906b-43c7-8595-e8105757bd68','3a9fb3e2-7438-4773-a3fd-7d1034b07f8f','51316945366024192'),('4803e140-e11b-4b67-9f0d-247daf48cee2','570f3047-32e5-4ab7-a350-84e7fd0f1774','50597556379451392'),('c7f40d4b-5f2f-40f0-a538-dc282714c5c6','570f3047-32e5-4ab7-a350-84e7fd0f1774','51316945366024192'),('98e9c91b-57aa-4d44-965a-c1788c7dbba3','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','50597556379451392'),('55b4c983-3dbb-491a-96f5-aa56692c3b4d','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','50612742305873920'),('21954185-382b-4e2a-9d0f-dc4a6343f574','5f50eb20-8cb4-40d9-ac49-9f29dde3a8ce','51316945366024192'),('90ce7c10-631e-4cfd-940e-c0dfe31ce3cd','c7548d6f-5bf7-4ee7-855f-ada859f0aa8d','50597556379451392'); 229 | /*!40000 ALTER TABLE `User_Role` ENABLE KEYS */; 230 | UNLOCK TABLES; 231 | 232 | -- 233 | -- Table structure for table `_prisma_migrations` 234 | -- 235 | 236 | DROP TABLE IF EXISTS `_prisma_migrations`; 237 | /*!40101 SET @saved_cs_client = @@character_set_client */; 238 | /*!50503 SET character_set_client = utf8mb4 */; 239 | CREATE TABLE `_prisma_migrations` ( 240 | `id` varchar(36) COLLATE utf8mb4_unicode_ci NOT NULL, 241 | `checksum` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, 242 | `finished_at` datetime(3) DEFAULT NULL, 243 | `migration_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, 244 | `logs` text COLLATE utf8mb4_unicode_ci, 245 | `rolled_back_at` datetime(3) DEFAULT NULL, 246 | `started_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 247 | `applied_steps_count` int unsigned NOT NULL DEFAULT '0', 248 | PRIMARY KEY (`id`) 249 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 250 | /*!40101 SET character_set_client = @saved_cs_client */; 251 | 252 | -- 253 | -- Dumping data for table `_prisma_migrations` 254 | -- 255 | 256 | LOCK TABLES `_prisma_migrations` WRITE; 257 | /*!40000 ALTER TABLE `_prisma_migrations` DISABLE KEYS */; 258 | INSERT INTO `_prisma_migrations` VALUES ('1a94a7a4-8285-4d68-b3d7-c9ea7f59685c','6d2693c1824a3bd0e0b5190613bf709d3059f754445947ea22c1f5539eb6b924','2023-11-08 14:25:27.951','20231008085656_',NULL,NULL,'2023-11-08 14:25:27.935',1),('1f191d54-1a23-4fe8-805e-39fef1a0529f','97da515480634d507fd349d673f68fc077c4de99c5d48affa3328660d7ce891a','2023-11-08 14:25:27.521','20231001041208_',NULL,NULL,'2023-11-08 14:25:27.488',1),('201fc51b-9c67-4738-a9aa-80a594903c98','cf6e81269ee01edb317eccdff3ed106584537de72b8af58fae2bab2d4a386583','2023-11-08 14:25:27.295','20230924053039_add_ctime_and_utime',NULL,NULL,'2023-11-08 14:25:27.241',1),('20a647ff-b589-4b72-8592-d6fce86fc718','6d61d5cb26eb79446c9b249ddc044194c0793d693c5bda18947293942300bc7c','2023-11-08 14:25:28.420','20231018133005_',NULL,NULL,'2023-11-08 14:25:28.393',1),('2145ad33-9e28-4ba4-8e04-f253f93d3e8f','f063cd97b71fc1d4ca2b57cf1cf78467835e00d4b86c3fc2c01260ba08c76b7d','2023-11-08 14:25:28.881','20231031131110_',NULL,NULL,'2023-11-08 14:25:28.867',1),('2b4af5a5-bf80-4dbd-b246-f0184ac8a082','c2b602989274ef5e5abbbcc12f556b64d09651dba0798d12a4dcc12c9daf95b1','2023-11-08 14:25:27.392','20231001034449_',NULL,NULL,'2023-11-08 14:25:27.352',1),('30702e1d-9f46-4912-bfc7-aaf2ca203652','1e734dd5e2768a2eb0fd1ea46d9abe0a795725b199b63fd53adadc4e21c0f103','2023-11-08 14:25:28.533','20231020045510_user_mount_role',NULL,NULL,'2023-11-08 14:25:28.468',1),('471a1a6a-df5a-495e-aa80-a335f0fcc2b5','7884dcbd0b7774d077fbe421b7348dedc1b0e15b8888423d711c4d033bdac21a','2023-11-08 14:25:28.765','20231031112046_',NULL,NULL,'2023-11-08 14:25:28.739',1),('485b2ed7-0158-43ed-93b7-4de4f10b7118','28a28d268af60e31a6b4f6eb56f0c66af1cf52375fa839dd4db9eba91b653c7a','2023-11-08 14:25:28.671','20231028142009_',NULL,NULL,'2023-11-08 14:25:28.648',1),('491d6830-bd7d-4846-b93d-89dc9c5cd415','118ceaf2b5a742111ba926c0e18a9bcd1191adb88ac373e0011cd192111b23f4','2023-11-08 14:25:28.255','20231018131728_reset',NULL,NULL,'2023-11-08 14:25:28.198',1),('4d7b175d-5ffd-4c68-9917-685976f22178','52ff61c4ddfd21e69dc884d05d25a90116e04c228c46b78b964f1e228ed102ab','2023-11-08 14:25:28.387','20231018132847_update_id',NULL,NULL,'2023-11-08 14:25:28.263',1),('50591f7e-2de8-4808-9b66-88cbe5a6d45b','2d0245d8b781e4960249fe1dcb8a0b7771a187b9ac99dd703c148abe51a78c2b','2023-11-08 14:25:27.133','20230921091715_init',NULL,NULL,'2023-11-08 14:25:27.112',1),('50ebebeb-f2ef-44b3-9187-0ecf3cf62dda','9db190321164dc09d9eb1257e0e5972e9d87b910c760467347b9c917926c97c5','2023-11-08 14:25:27.752','20231002075603_user_include_file',NULL,NULL,'2023-11-08 14:25:27.676',1),('56081568-e70b-4100-acb6-70b2897dc810','dd462cc10ce18e5ca700ed40f2878bee09106ba5b1732db4377b6f657301f555','2023-11-08 14:25:27.230','20230923085258_edit_sex',NULL,NULL,'2023-11-08 14:25:27.190',1),('56c7b2ed-bb03-4871-97f9-2e478279ebc9','012230c484e161762306a127cb89e1a73ee15a903fe96dbbeaa1cb4cfa0f9230','2023-11-08 14:25:28.940','20231105152609_',NULL,NULL,'2023-11-08 14:25:28.925',1),('5ecf8db6-5c58-459b-9fcf-caff24d7b633','e79f7cd78c946d73959f223489a186a60151288ef9f3ef3a5a15b1d6ebd531fc','2023-11-08 14:25:27.668','20231001075129_',NULL,NULL,'2023-11-08 14:25:27.638',1),('5fb2973e-1de5-432b-805b-f01299ce09dc','78097a6aeb39c629b4f683e0ece9eae57f5e8db2e04cdb3f907aaa33caed16e0','2023-11-08 14:25:27.828','20231002145741_',NULL,NULL,'2023-11-08 14:25:27.804',1),('620aac3d-ae14-4200-bed2-3c7cca7ba02d','aad5883148754aa11cfc92772e2f0aa51c923806800aa4ccd10cc06f59d40120','2023-11-08 14:25:28.619','20231020050501_add_table_user_role',NULL,NULL,'2023-11-08 14:25:28.539',1),('7d86c7db-a029-4dfb-b1c2-8596af510b4f','7330af820625eebd46938bc200dce86325f5019fa9b411baa8ca383399a2cd20','2023-11-08 14:25:28.461','20231018133030_',NULL,NULL,'2023-11-08 14:25:28.427',1),('7de350ab-aebb-48fc-9138-491816d6df91','23f6865de10418e94af85fadbedfff1f979f811d77a5ddc94761878a9984a807','2023-11-08 14:25:27.907','20231008085428_',NULL,NULL,'2023-11-08 14:25:27.895',1),('7e3a9b92-117e-42ab-9706-32ada8629849','ad92034da5d9d9b0245a7519a5d98d1402a929511afe84eac9b35837d6a42921','2023-11-08 14:25:28.103','20231012070141_',NULL,NULL,'2023-11-08 14:25:28.075',1),('8082ca5b-437b-4598-a00f-825ca761b759','012230c484e161762306a127cb89e1a73ee15a903fe96dbbeaa1cb4cfa0f9230','2023-11-08 15:01:18.257','20231108150118_file_change',NULL,NULL,'2023-11-08 15:01:18.244',1),('8192a11d-e135-439e-8864-664ffc573fb3','0dcfa5a9ad1f0b535dba0d65b8e41160268252d22866da8837ac053b898aedbe','2023-11-08 14:25:28.707','20231028142702_edit_login_log_status',NULL,NULL,'2023-11-08 14:25:28.679',1),('82148534-951e-4f8f-b239-bf326184ec72','2ef8225ad5600a527bb217bcc0e9ba725b999edff68f8e46e8733145120efb00','2023-11-08 14:25:28.860','20231031130254_',NULL,NULL,'2023-11-08 14:25:28.844',1),('91d47e1c-6a93-4e99-adfd-2c7cbea28291','cd92c5b6dbbb1d77d2d95a980db1e0d40be7dd1ee47f6c3717bd4d19c7e37322','2023-11-08 14:25:27.484','20231001041129_',NULL,NULL,'2023-11-08 14:25:27.447',1),('96fbf159-9f7a-4220-a86e-2ce37c9061ee','507e4d758b7e3038d3c9bf9ea066e47bc7292c3241c3e035902aa702773df6dc','2023-11-08 14:25:27.582','20231001074426_file',NULL,NULL,'2023-11-08 14:25:27.568',1),('a7605595-8c6f-47ad-bb5a-f8d15e69bd27','78c80e6ee09355a5b23236ff77b2eeb3bdc4b0879943f69312afc819de6df0bf','2023-11-08 14:25:27.797','20231002080515_',NULL,NULL,'2023-11-08 14:25:27.759',1),('b101d0b4-0a26-4b81-bebf-386bd9c3f36d','f752835df31141e16d4937946da0e96cc17e070a7befa59e7aea039be5bfe5ad','2023-11-08 14:25:27.631','20231001075022_',NULL,NULL,'2023-11-08 14:25:27.590',1),('b4f90cc8-580c-44e8-971f-252542306d1a','d9527203078878fb02344d92b13ce308c2936481b66dac645e48130ab7a19e1f','2023-11-08 14:25:27.888','20231007142628_',NULL,NULL,'2023-11-08 14:25:27.836',1),('b9b863d7-ae20-4b54-9f56-9d58f6e51cd1','56ee903e2b7c1cd5b733c6f5c548fb15cdc903107c94a44dbc260642e45c0d4d','2023-11-08 14:25:28.143','20231012071604_',NULL,NULL,'2023-11-08 14:25:28.110',1),('bb9cc471-ed3f-4071-93b7-3e5a33cd4739','6d2693c1824a3bd0e0b5190613bf709d3059f754445947ea22c1f5539eb6b924','2023-11-08 14:25:28.958','20231105154011_',NULL,NULL,'2023-11-08 14:25:28.944',1),('bdfcc35f-4906-4916-a393-e5d8a0c1d181','0a9ab02ec8f9af5cd53bdc54940bef9337bb5588615fd5a337f0e7d27bf89aba','2023-11-08 14:25:28.731','20231028143801_',NULL,NULL,'2023-11-08 14:25:28.714',1),('c373b273-74c9-4fcc-a32d-4bd8f1726168','49b8637fc8886c0329671d545acd942d71df9f95b339ba00024593c3a1ab623c','2023-11-08 14:25:28.837','20231031122453_reset_table',NULL,NULL,'2023-11-08 14:25:28.775',1),('c408a061-31fe-4c8e-9012-fb628b216705','9e66ab887bae391f40b893b3d3465afd6aa395c67a14c9582e89d2598e1d66c2','2023-11-08 14:25:27.344','20231001034056_',NULL,NULL,'2023-11-08 14:25:27.304',1),('cd318189-3a66-43bf-ba70-e4924f0a96a8','97da515480634d507fd349d673f68fc077c4de99c5d48affa3328660d7ce891a','2023-11-08 14:25:27.438','20231001034533_',NULL,NULL,'2023-11-08 14:25:27.400',1),('e0070377-b4a8-4cf3-8467-2a9b8e6fe8af','448d1a4714df39b0467d5857cd4889300aaa4260a21d0cae3c901baf9009bb20','2023-11-08 14:25:27.561','20231001074327_file',NULL,NULL,'2023-11-08 14:25:27.530',1),('e88781d7-c39a-487a-9558-9aa1cf716102','bad6f447ddfcc00ff10426c8830847967438ca568ecf74675299667021687a83','2023-11-08 14:25:28.191','20231012071747_',NULL,NULL,'2023-11-08 14:25:28.152',1),('ec437dbb-95fd-4804-845e-5db700a4bcd7','5d8dd0e9a168074f475b1ceb85c86aa5582ef34e92f154ccf0f85d38526077da','2023-11-08 14:25:28.640','20231028141149_set_table_login_log',NULL,NULL,'2023-11-08 14:25:28.625',1),('f1d68e5d-511b-4db8-9797-a212126ff343','eb9f598346af809b6a868c3ccd7b058883e5ed904279719b19d214ad0dbef99e','2023-11-08 14:25:28.921','20231031131339_',NULL,NULL,'2023-11-08 14:25:28.888',1),('f7809f7b-c782-4e47-bdb2-4facb6a346b1','3a6820350384f9e192e15991e6ad5ddc7f5c289926704bedca2d38e008fda30e','2023-11-08 14:25:27.180','20230923085000_enrich_user_model',NULL,NULL,'2023-11-08 14:25:27.143',1),('f88ccc27-476e-439c-bd24-b60c2f27920f','445238935628b78140fa05b5527e6fb43874f079cf008710e7247e2334e83350','2023-11-08 14:25:28.068','20231012063134_',NULL,NULL,'2023-11-08 14:25:27.961',1),('fdcff3c7-3b38-41ce-af20-5989b029eb64','012230c484e161762306a127cb89e1a73ee15a903fe96dbbeaa1cb4cfa0f9230','2023-11-08 14:25:27.927','20231008085537_',NULL,NULL,'2023-11-08 14:25:27.914',1); 259 | /*!40000 ALTER TABLE `_prisma_migrations` ENABLE KEYS */; 260 | UNLOCK TABLES; 261 | 262 | -- 263 | -- Dumping routines for database 'meng_admin' 264 | -- 265 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 266 | 267 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 268 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 269 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 270 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 271 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 272 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 273 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 274 | 275 | -- Dump completed on 2023-11-12 0:26:06 276 | --------------------------------------------------------------------------------