refactor: 华信OA后端架构搭建
This commit is contained in:
commit
c0fe67ff19
|
@ -0,0 +1,94 @@
|
||||||
|
// 请使用npm run c提交代码。遵循代码提交规范
|
||||||
|
module.exports = {
|
||||||
|
types: [
|
||||||
|
{ value: 'feat', name: '功能: ✨ 新增功能', emoji: ':sparkles:' },
|
||||||
|
{ value: 'fix', name: '修复: 🐛 修复缺陷', emoji: ':bug:' },
|
||||||
|
{ value: 'docs', name: '文档: 📝 文档变更', emoji: ':memo:' },
|
||||||
|
{
|
||||||
|
value: 'style',
|
||||||
|
name: '格式: 🌈 代码格式(不影响功能,例如空格、分号等格式修正)',
|
||||||
|
emoji: ':lipstick:',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'refactor',
|
||||||
|
name: '重构: 🔄 代码重构(不包括 bug 修复、功能新增)',
|
||||||
|
emoji: ':recycle:',
|
||||||
|
},
|
||||||
|
{ value: 'perf', name: '性能: 🚀 性能优化', emoji: ':zap:' },
|
||||||
|
{
|
||||||
|
value: 'test',
|
||||||
|
name: '测试: 🧪 添加疏漏测试或已有测试改动',
|
||||||
|
emoji: ':white_check_mark:',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'build',
|
||||||
|
name: '构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)',
|
||||||
|
emoji: ':package:',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'ci',
|
||||||
|
name: '集成: ⚙️ 修改 CI 配置、脚本',
|
||||||
|
emoji: ':ferris_wheel:',
|
||||||
|
},
|
||||||
|
{ value: 'revert', name: '回退: ↩️ 回滚 commit', emoji: ':rewind:' },
|
||||||
|
{
|
||||||
|
value: 'chore',
|
||||||
|
name: '其他: 🛠️ 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)',
|
||||||
|
emoji: ':hammer:',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
useEmoji: true,
|
||||||
|
emojiAlign: 'center',
|
||||||
|
useAI: false,
|
||||||
|
aiNumber: 1,
|
||||||
|
themeColorCode: '',
|
||||||
|
scopes: [],
|
||||||
|
allowCustomScopes: true,
|
||||||
|
allowEmptyScopes: true,
|
||||||
|
customScopesAlign: 'bottom',
|
||||||
|
customScopesAlias: 'custom',
|
||||||
|
emptyScopesAlias: 'empty',
|
||||||
|
upperCaseSubject: false,
|
||||||
|
markBreakingChangeMode: false,
|
||||||
|
breaklineNumber: 100,
|
||||||
|
breaklineChar: '|',
|
||||||
|
issuePrefixes: [
|
||||||
|
{ value: 'closed', name: 'closed: ISSUES has been processed' },
|
||||||
|
],
|
||||||
|
customIssuePrefixAlign: 'top',
|
||||||
|
emptyIssuePrefixAlias: 'skip',
|
||||||
|
customIssuePrefixAlias: 'custom',
|
||||||
|
allowCustomIssuePrefix: true,
|
||||||
|
allowEmptyIssuePrefix: true,
|
||||||
|
confirmColorize: true,
|
||||||
|
maxHeaderLength: Infinity,
|
||||||
|
maxSubjectLength: Infinity,
|
||||||
|
minSubjectLength: 0,
|
||||||
|
scopeOverrides: undefined,
|
||||||
|
defaultBody: '',
|
||||||
|
defaultIssues: '',
|
||||||
|
defaultScope: '',
|
||||||
|
defaultSubject: '',
|
||||||
|
messages: {
|
||||||
|
type: '选择一种你期望的提交类型(type):',
|
||||||
|
// scope: '选择一个更改的范围(scope) (可选):',
|
||||||
|
// used if allowCustomScopes is true
|
||||||
|
// customScope: 'Denote the SCOPE of this change:',
|
||||||
|
subject: '输入本次commit记录说明:\n',
|
||||||
|
// body: '长说明,使用"|"换行(可选):\n',
|
||||||
|
// breaking: '非兼容性说明 (可选):\n',
|
||||||
|
// footer: '关联关闭的issue,例如:#31, #34(可选):\n',
|
||||||
|
confirmCommit: '确定提交说明?',
|
||||||
|
},
|
||||||
|
skipQuestions: ['scope', 'body', 'breaking', 'footer'],
|
||||||
|
allowBreakingChanges: [
|
||||||
|
'fix',
|
||||||
|
'feat',
|
||||||
|
'update',
|
||||||
|
'refactor',
|
||||||
|
'perf',
|
||||||
|
'build',
|
||||||
|
'revert',
|
||||||
|
],
|
||||||
|
subjectLimit: 500, // 提交长度限制500
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
# package-lock.json
|
||||||
|
# yarn.lock
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# Code
|
||||||
|
src/config/config.development.*
|
||||||
|
docs/*
|
||||||
|
# sql/*
|
||||||
|
test/*
|
||||||
|
README.md
|
||||||
|
|
||||||
|
# Dev data
|
||||||
|
/__data/
|
|
@ -0,0 +1,32 @@
|
||||||
|
# app
|
||||||
|
APP_NAME = Nest Admin
|
||||||
|
APP_PORT = 7001
|
||||||
|
APP_BASE_URL = http://localhost:${APP_PORT}
|
||||||
|
APP_LOCALE = zh-CN
|
||||||
|
|
||||||
|
# logger
|
||||||
|
LOGGER_LEVEL = verbose
|
||||||
|
LOGGER_MAX_FILES = 31
|
||||||
|
|
||||||
|
TZ = Asia/Shanghai
|
||||||
|
|
||||||
|
# OSS(qiniu)
|
||||||
|
OSS_ACCESSKEY=xxx
|
||||||
|
OSS_SECRETKEY=xxx
|
||||||
|
OSS_DOMAIN=https://cdn.buqiyuan.site
|
||||||
|
OSS_BUCKET=nest-admin
|
||||||
|
OSS_ZONE=Zone_z2 # Zone_as0 | Zone_na0 | Zone_z0 | Zone_z1 | Zone_z2
|
||||||
|
OSS_ACCESS_TYPE=public # or private
|
||||||
|
|
||||||
|
DB_HOST = host.docker.internal
|
||||||
|
DB_PORT = 13307
|
||||||
|
DB_DATABASE = nest_admin
|
||||||
|
DB_USERNAME = root
|
||||||
|
DB_PASSWORD = root
|
||||||
|
DB_SYNCHRONIZE = false
|
||||||
|
DB_LOGGING = ["error"]
|
||||||
|
|
||||||
|
REDIS_PORT = 6379
|
||||||
|
REDIS_HOST = host.docker.internal
|
||||||
|
REDIS_PASSWORD = 123456
|
||||||
|
REDIS_DB = 0
|
|
@ -0,0 +1,35 @@
|
||||||
|
# logger
|
||||||
|
LOGGER_LEVEL = debug
|
||||||
|
|
||||||
|
# security
|
||||||
|
JWT_SECRET = admin!@#123
|
||||||
|
JWT_EXPIRE = 86400 # 单位秒
|
||||||
|
REFRESH_TOKEN_SECRET = admin!@#123
|
||||||
|
REFRESH_TOKEN_EXPIRE = 2592000
|
||||||
|
|
||||||
|
# swagger
|
||||||
|
SWAGGER_ENABLE = true
|
||||||
|
SWAGGER_PATH = api-docs
|
||||||
|
SWAGGER_VERSION = 1.0
|
||||||
|
|
||||||
|
# db
|
||||||
|
DB_HOST = 127.0.0.1
|
||||||
|
DB_PORT = 13307
|
||||||
|
DB_DATABASE = nest_admin
|
||||||
|
DB_USERNAME = root
|
||||||
|
DB_PASSWORD = root
|
||||||
|
DB_SYNCHRONIZE = true
|
||||||
|
DB_LOGGING = ["error"]
|
||||||
|
|
||||||
|
# redis
|
||||||
|
REDIS_PORT = 6379
|
||||||
|
REDIS_HOST = 127.0.0.1
|
||||||
|
REDIS_PASSWORD = 123456
|
||||||
|
REDIS_DB = 0
|
||||||
|
|
||||||
|
# smtp
|
||||||
|
SMTP_HOST = smtp.163.com
|
||||||
|
SMTP_PORT = 465
|
||||||
|
SMTP_USER = nest_admin@163.com
|
||||||
|
SMTP_PASS = VIPLLOIPMETTROYU
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# logger
|
||||||
|
LOGGER_LEVEL = debug
|
||||||
|
|
||||||
|
# security
|
||||||
|
JWT_SECRET = admin!@#123
|
||||||
|
JWT_EXPIRE = 86400 # 单位秒
|
||||||
|
REFRESH_TOKEN_SECRET = admin!@#123
|
||||||
|
REFRESH_TOKEN_EXPIRE = 2592000
|
||||||
|
|
||||||
|
# swagger
|
||||||
|
SWAGGER_ENABLE = true
|
||||||
|
SWAGGER_PATH = api-docs
|
||||||
|
SWAGGER_VERSION = 1.0
|
||||||
|
|
||||||
|
# db
|
||||||
|
DB_HOST = host.docker.internal
|
||||||
|
DB_PORT = 13307
|
||||||
|
DB_DATABASE = nest_admin
|
||||||
|
DB_USERNAME = root
|
||||||
|
DB_PASSWORD = root
|
||||||
|
DB_SYNCHRONIZE = false
|
||||||
|
DB_LOGGING = ["error"]
|
||||||
|
|
||||||
|
# redis
|
||||||
|
REDIS_PORT = 6379
|
||||||
|
REDIS_HOST = host.docker.internal
|
||||||
|
REDIS_PASSWORD = 123456
|
||||||
|
REDIS_DB = 0
|
||||||
|
|
||||||
|
# smtp
|
||||||
|
SMTP_HOST = smtp.163.com
|
||||||
|
SMTP_PORT = 465
|
||||||
|
SMTP_USER = nest_admin@163.com
|
||||||
|
SMTP_PASS = VIPLLOIPMETTROYU
|
||||||
|
|
||||||
|
# 是否为演示模式(在演示模式下,会拦截除 GET 方法以外的所有请求)
|
||||||
|
IS_DEMO = false
|
|
@ -0,0 +1,11 @@
|
||||||
|
# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings
|
||||||
|
|
||||||
|
# Automatically normalize line endings (to LF) for all text-based files.
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Declare files that will always have CRLF line endings on checkout.
|
||||||
|
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||||
|
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||||
|
|
||||||
|
# Denote all files that are truly binary and should not be modified.
|
||||||
|
*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary
|
|
@ -0,0 +1,77 @@
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
*-dist
|
||||||
|
.cache
|
||||||
|
.history
|
||||||
|
.vercel/
|
||||||
|
|
||||||
|
.turbo
|
||||||
|
.local
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
#.env.development
|
||||||
|
#.env.production
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
# .vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
.nestjs_repl_history
|
||||||
|
out
|
||||||
|
|
||||||
|
# temp data
|
||||||
|
__data
|
||||||
|
|
||||||
|
public/upload
|
||||||
|
types/env.d.ts
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run commitlint
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
#推送之前运行eslint检查
|
||||||
|
npx lint-staged
|
||||||
|
##推送之前运行单元测试检查
|
||||||
|
#npm run test:unit
|
|
@ -0,0 +1,6 @@
|
||||||
|
shamefully-hoist=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
|
||||||
|
# 使用淘宝镜像源
|
||||||
|
registry = https://registry.npmmirror.com
|
||||||
|
# registry = https://registry.npmjs.org
|
|
@ -0,0 +1,19 @@
|
||||||
|
//发布应用 生成commit日志记录。
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
types: [
|
||||||
|
{ type: 'feat', section: '✨ Features | 新功能' },
|
||||||
|
{ type: 'fix', section: '🐛 Bug Fixes | Bug 修复' },
|
||||||
|
{ type: 'init', section: '📦️ Init | 初始化' },
|
||||||
|
{ type: 'docs', section: '📝 Documentation | 文档' },
|
||||||
|
{ type: 'style', section: '🌈 Styles | 风格' },
|
||||||
|
{ type: 'refactor', section: '🔄 Code Refactoring | 代码重构' },
|
||||||
|
{ type: 'perf', section: '🚀 Performance Improvements | 性能优化' },
|
||||||
|
{ type: 'test', section: '🧪 Tests | 测试' },
|
||||||
|
{ type: 'revert', section: '↩️ Revert | 回退' },
|
||||||
|
{ type: 'build', section: '📦️ Build System | 打包构建' },
|
||||||
|
{ type: 'update', section: '⚙️ update | 构建/工程依赖/工具升级' },
|
||||||
|
{ type: 'tool', section: '🛠️ tool | 工具升级' },
|
||||||
|
{ type: 'ci', section: '⚙️ Continuous Integration | CI 配置' },
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug Nest Framework",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": ["run", "start:debug", "--", "--inspect-brk"],
|
||||||
|
"autoAttachChildProcesses": true,
|
||||||
|
"restart": true,
|
||||||
|
"sourceMaps": true,
|
||||||
|
"stopOnEntry": false,
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
// Enable the ESlint flat config support
|
||||||
|
"eslint.experimental.useFlatConfig": true,
|
||||||
|
|
||||||
|
// Disable the default formatter, use eslint instead
|
||||||
|
"prettier.enable": false,
|
||||||
|
"editor.formatOnSave": false,
|
||||||
|
|
||||||
|
// Auto fix
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||||
|
"eslint.rules.customizations": [
|
||||||
|
{ "rule": "style/*", "severity": "off" },
|
||||||
|
{ "rule": "*-indent", "severity": "off" },
|
||||||
|
{ "rule": "*-spacing", "severity": "off" },
|
||||||
|
{ "rule": "*-spaces", "severity": "off" },
|
||||||
|
{ "rule": "*-order", "severity": "off" },
|
||||||
|
{ "rule": "*-dangle", "severity": "off" },
|
||||||
|
{ "rule": "*-newline", "severity": "off" },
|
||||||
|
{ "rule": "*quotes", "severity": "off" },
|
||||||
|
{ "rule": "*semi", "severity": "off" }
|
||||||
|
],
|
||||||
|
|
||||||
|
// Enable eslint for all supported languages
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue",
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
"yaml"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
# 遇到网络问题可以配置镜像加速:https://gist.github.com/y0ngb1n/7e8f16af3242c7815e7ca2f0833d3ea6
|
||||||
|
# FROM 表示设置要制作的镜像基于哪个镜像,FROM指令必须是整个Dockerfile的第一个指令,如果指定的镜像不存在默认会自动从Docker Hub上下载。
|
||||||
|
# 指定我们的基础镜像是node,latest表示版本是最新, 如果要求空间极致,可以选择lts-alpine
|
||||||
|
# 使用 as 来为某一阶段命名
|
||||||
|
FROM node:20-slim AS base
|
||||||
|
|
||||||
|
ENV PROJECT_DIR=/nest-admin \
|
||||||
|
DB_HOST=mysql \
|
||||||
|
APP_PORT=7001 \
|
||||||
|
PNPM_HOME="/pnpm" \
|
||||||
|
PATH="$PNPM_HOME:$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
RUN corepack enable \
|
||||||
|
&& yarn global add pm2
|
||||||
|
|
||||||
|
# WORKDIR指令用于设置Dockerfile中的RUN、CMD和ENTRYPOINT指令执行命令的工作目录(默认为/目录),该指令在Dockerfile文件中可以出现多次,
|
||||||
|
# 如果使用相对路径则为相对于WORKDIR上一次的值,
|
||||||
|
# 例如WORKDIR /data,WORKDIR logs,RUN pwd最终输出的当前目录是/data/logs。
|
||||||
|
# cd 到 /nest-admin
|
||||||
|
WORKDIR $PROJECT_DIR
|
||||||
|
COPY ./ $PROJECT_DIR
|
||||||
|
RUN chmod +x ./wait-for-it.sh
|
||||||
|
|
||||||
|
# set timezone
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& echo 'Asia/Shanghai' > /etc/timezone
|
||||||
|
|
||||||
|
# see https://pnpm.io/docker
|
||||||
|
FROM base AS prod-deps
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
|
||||||
|
# mirror acceleration
|
||||||
|
# RUN npm config set registry https://registry.npmmirror.com
|
||||||
|
# RUN pnpm config set registry https://registry.npmmirror.com
|
||||||
|
# RUN npm config rm proxy && npm config rm https-proxy
|
||||||
|
|
||||||
|
FROM base
|
||||||
|
COPY --from=prod-deps $PROJECT_DIR/node_modules $PROJECT_DIR/node_modules
|
||||||
|
COPY --from=build $PROJECT_DIR/dist $PROJECT_DIR/dist
|
||||||
|
|
||||||
|
# EXPOSE port
|
||||||
|
EXPOSE $APP_PORT
|
||||||
|
|
||||||
|
# 容器启动时执行的命令,类似npm run start
|
||||||
|
# CMD ["pnpm", "start:prod"]
|
||||||
|
# CMD ["pm2-runtime", "ecosystem.config.js"]
|
||||||
|
ENTRYPOINT ./wait-for-it.sh $DB_HOST:$DB_PORT -- pm2-runtime ecosystem.config.js
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021-present buqiyuan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,112 @@
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
- `nodejs` `16.20.2`+
|
||||||
|
- `docker` `20.x`+ ,其中 `docker compose`版本需要 `2.17.0`+
|
||||||
|
- `mysql` `8.x`+
|
||||||
|
- 使用 [`pnpm`](https://pnpm.io/zh/) 包管理器安装项目依赖
|
||||||
|
|
||||||
|
演示环境账号密码:
|
||||||
|
|
||||||
|
| 账号 | 密码 | 权限 |
|
||||||
|
| :-------: | :----: | :--------: |
|
||||||
|
| admin | a123456 | 超级管理员 |
|
||||||
|
|
||||||
|
> 所有新建的用户初始密码都为 a123456
|
||||||
|
|
||||||
|
本地部署账号密码:
|
||||||
|
|
||||||
|
| 账号 | 密码 | 权限 |
|
||||||
|
| :-------: | :----: | :--------: |
|
||||||
|
| admin | a123456 | 超级管理员 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 本地开发
|
||||||
|
|
||||||
|
- 【可选】如果你是新手,还不太会搭建`mysql/redis`,你可以使用 `Docker` 启动指定服务供本地开发时使用, 例如:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动MySql服务
|
||||||
|
docker compose --env-file .env --env-file .env.development run -d --service-ports mysql
|
||||||
|
# 启动Redis服务
|
||||||
|
docker compose --env-file .env --env-file .env.development run -d --service-ports redis
|
||||||
|
```
|
||||||
|
|
||||||
|
- 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- 运行
|
||||||
|
启动成功后,通过 <http://localhost:7001/api-docs/> 访问。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- 打包
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
2.使用docker运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
停止并删除所有容器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm docker:down
|
||||||
|
# or
|
||||||
|
docker compose --env-file .env --env-file .env.production down
|
||||||
|
```
|
||||||
|
|
||||||
|
删除镜像
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm docker:rmi
|
||||||
|
# or
|
||||||
|
docker rmi buqiyuan/nest-admin-server:stable
|
||||||
|
```
|
||||||
|
|
||||||
|
查看实时日志输出
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm docker:logs
|
||||||
|
# or
|
||||||
|
docker compose --env-file .env --env-file .env.production logs -f
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据库迁移
|
||||||
|
|
||||||
|
1. 更新数据库(或初始化数据)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm migration:run
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 生成迁移
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm migration:generate
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 回滚到最后一次更新
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm migration:revert
|
||||||
|
```
|
||||||
|
|
||||||
|
更多细节,请移步至[官方文档](https://typeorm.io/migrations)
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> 如果你的`实体类`或`数据库配置`有更新,请执行`npm run build`后再进行数据库迁移相关操作。
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 请使用npm run c/yarn c提交代码。
|
||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
rules: {
|
||||||
|
'type-enum': [
|
||||||
|
2,
|
||||||
|
'always',
|
||||||
|
[
|
||||||
|
'feat', // 新增功能
|
||||||
|
'fix', // 修复缺陷
|
||||||
|
'docs', // 文档变更
|
||||||
|
'style', // 代码格式(不影响功能,例如空格、分号等格式修正)
|
||||||
|
'refactor', // 代码重构(不包括 bug 修复、功能新增)
|
||||||
|
'perf', // 性能优化
|
||||||
|
'test', // 添加疏漏测试或已有测试改动
|
||||||
|
'build', // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
|
||||||
|
'ci', // 修改 CI 配置、脚本
|
||||||
|
'revert', // 回滚 commit
|
||||||
|
'chore', // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'subject-case': [0], // subject大小写不做校验
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,744 @@
|
||||||
|
/*
|
||||||
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
|
Source Server : nest-admin
|
||||||
|
Source Server Type : MySQL
|
||||||
|
Source Server Version : 80030 (8.0.30)
|
||||||
|
Source Host : localhost:13307
|
||||||
|
Source Schema : nest_admin
|
||||||
|
|
||||||
|
Target Server Type : MySQL
|
||||||
|
Target Server Version : 80030 (8.0.30)
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 10/02/2024 09:43:40
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_captcha_log
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_captcha_log`;
|
||||||
|
CREATE TABLE `sys_captcha_log` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` int DEFAULT NULL,
|
||||||
|
`account` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
|
||||||
|
`code` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
|
||||||
|
`provider` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_captcha_log
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_config
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_config`;
|
||||||
|
CREATE TABLE `sys_config` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE KEY `IDX_2c363c25cf99bcaab3a7f389ba` (`key`) USING BTREE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_config
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_config` (`id`, `key`, `name`, `value`, `remark`, `created_at`, `updated_at`) VALUES (1, 'sys_user_initPassword', '初始密码', '123456', '创建管理员账号的初始密码', '2023-11-10 00:31:44.154921', '2023-11-10 00:31:44.161263');
|
||||||
|
INSERT INTO `sys_config` (`id`, `key`, `name`, `value`, `remark`, `created_at`, `updated_at`) VALUES (2, 'sys_api_token', 'API Token', 'nest-admin', '用于请求 @ApiToken 的控制器', '2023-11-10 00:31:44.154921', '2024-01-29 09:52:27.000000');
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_dept
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_dept`;
|
||||||
|
CREATE TABLE `sys_dept` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
|
||||||
|
`orderNo` int DEFAULT '0',
|
||||||
|
`mpath` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '',
|
||||||
|
`parentId` int DEFAULT NULL,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `FK_c75280b01c49779f2323536db67` (`parentId`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_c75280b01c49779f2323536db67` FOREIGN KEY (`parentId`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_dept
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (1, '华东分部', 1, '1.', NULL, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (2, '研发部', 1, '1.2.', 1, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (3, '市场部', 2, '1.3.', 1, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (4, '商务部', 3, '1.4.', 1, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (5, '财务部', 4, '1.5.', 1, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (6, '华南分部', 2, '6.', NULL, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (7, '西北分部', 3, '7.', NULL, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (8, '研发部', 1, '6.8.', 6, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (9, '市场部', 1, '6.9.', 6, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709');
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_dict
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_dict`;
|
||||||
|
CREATE TABLE `sys_dict` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
`create_by` int NOT NULL COMMENT '创建者',
|
||||||
|
`update_by` int NOT NULL COMMENT '更新者',
|
||||||
|
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`status` tinyint NOT NULL DEFAULT '1',
|
||||||
|
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `IDX_d112365748f740ee260b65ce91` (`name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_dict
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_dict_item
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_dict_item`;
|
||||||
|
CREATE TABLE `sys_dict_item` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
`create_by` int NOT NULL COMMENT '创建者',
|
||||||
|
`update_by` int NOT NULL COMMENT '更新者',
|
||||||
|
`label` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`value` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`order` int DEFAULT NULL COMMENT '字典项排序',
|
||||||
|
`status` tinyint NOT NULL DEFAULT '1',
|
||||||
|
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`type_id` int DEFAULT NULL,
|
||||||
|
`orderNo` int DEFAULT NULL COMMENT '字典项排序',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `FK_d68ea74fcb041c8cfd1fd659844` (`type_id`),
|
||||||
|
CONSTRAINT `FK_d68ea74fcb041c8cfd1fd659844` FOREIGN KEY (`type_id`) REFERENCES `sys_dict_type` (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_dict_item
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (1, '2024-01-29 01:24:51.846135', '2024-01-29 02:23:19.000000', 1, 1, '男', '1', 0, 1, '性别男', 1, 3);
|
||||||
|
INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (2, '2024-01-29 01:32:58.458741', '2024-01-29 01:58:20.000000', 1, 1, '女', '0', 1, 1, '性别女', 1, 2);
|
||||||
|
INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (3, '2024-01-29 01:59:17.805394', '2024-01-29 14:37:18.000000', 1, 1, '人妖王', '3', NULL, 1, '安布里奥·伊万科夫', 1, 0);
|
||||||
|
INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (5, '2024-01-29 02:13:01.782466', '2024-01-29 02:13:01.782466', 1, 1, '显示', '1', NULL, 1, '显示菜单', 2, 0);
|
||||||
|
INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (6, '2024-01-29 02:13:31.134721', '2024-01-29 02:13:31.134721', 1, 1, '隐藏', '0', NULL, 1, '隐藏菜单', 2, 0);
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_dict_type
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_dict_type`;
|
||||||
|
CREATE TABLE `sys_dict_type` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
`create_by` int NOT NULL COMMENT '创建者',
|
||||||
|
`update_by` int NOT NULL COMMENT '更新者',
|
||||||
|
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`status` tinyint NOT NULL DEFAULT '1',
|
||||||
|
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `IDX_74d0045ff7fab9f67adc0b1bda` (`code`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_dict_type
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_dict_type` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `name`, `status`, `remark`, `code`) VALUES (1, '2024-01-28 08:19:12.777447', '2024-02-08 13:05:10.000000', 1, 1, '性别', 1, '性别单选', 'sys_user_gender');
|
||||||
|
INSERT INTO `sys_dict_type` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `name`, `status`, `remark`, `code`) VALUES (2, '2024-01-28 08:38:41.235185', '2024-01-29 02:11:33.000000', 1, 1, '菜单显示状态', 1, '菜单显示状态', 'sys_show_hide');
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_login_log
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_login_log`;
|
||||||
|
CREATE TABLE `sys_login_log` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`ua` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`provider` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
`user_id` int DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `FK_3029712e0df6a28edaee46fd470` (`user_id`),
|
||||||
|
CONSTRAINT `FK_3029712e0df6a28edaee46fd470` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_login_log
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_menu
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_menu`;
|
||||||
|
CREATE TABLE `sys_menu` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`parent_id` int DEFAULT NULL,
|
||||||
|
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`permission` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`type` tinyint NOT NULL DEFAULT '0',
|
||||||
|
`icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '',
|
||||||
|
`order_no` int DEFAULT '0',
|
||||||
|
`component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`keep_alive` tinyint NOT NULL DEFAULT '1',
|
||||||
|
`show` tinyint NOT NULL DEFAULT '1',
|
||||||
|
`status` tinyint NOT NULL DEFAULT '1',
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
`is_ext` tinyint NOT NULL DEFAULT '0',
|
||||||
|
`ext_open_mode` tinyint NOT NULL DEFAULT '1',
|
||||||
|
`active_menu` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=128 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_menu
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (1, NULL, '/system', '系统管理', '', 0, 'ant-design:setting-outlined', 254, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:46.668745', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (2, 1, '/system/user', '用户管理', 'system:user:list', 1, 'ant-design:user-outlined', 0, 'system/user/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:10:30.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (3, 1, '/system/role', '角色管理', 'system:role:list', 1, 'ep:user', 1, 'system/role/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:11:02.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (4, 1, '/system/menu', '菜单管理', 'system:menu:list', 1, 'ep:menu', 2, 'system/menu/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:11:18.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (5, 1, '/system/monitor', '系统监控', '', 0, 'ep:monitor', 5, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:44.567023', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (6, 5, '/system/monitor/online', '在线用户', 'system:online:list', 1, '', 0, 'system/monitor/online/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-15 22:13:59.519267', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (7, 5, '/sys/monitor/login-log', '登录日志', 'system:log:login:list', 1, '', 0, 'system/monitor/log/login/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-15 22:14:02.610719', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (8, 5, '/system/monitor/serve', '服务监控', 'system:serve:stat', 1, '', 4, 'system/monitor/serve/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-15 22:14:05.606355', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (9, 1, '/system/schedule', '任务调度', '', 0, 'ant-design:schedule-filled', 6, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:52.967983', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (10, 9, '/system/task', '任务管理', '', 1, '', 0, 'system/schedule/task/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:14:39.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (11, 9, '/system/task/log', '任务日志', 'system:task:list', 1, '', 0, 'system/schedule/log/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:15:01.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (12, NULL, '/document', '文档', '', 0, 'ion:tv-outline', 2, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:42.514264', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (14, 12, 'https://www.typeorm.org/', 'Typeorm中文文档(外链)', NULL, 1, '', 3, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-30 18:39:53.000000', 1, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (15, 12, 'https://docs.nestjs.cn/', 'Nest.js中文文档(内嵌)', '', 1, '', 4, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-30 18:40:43.000000', 1, 2, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (20, 2, NULL, '新增', 'system:user:create', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (21, 2, '', '删除', 'system:user:delete', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (22, 2, '', '更新', 'system:user:update', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (23, 2, '', '查询', 'system:user:read', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (24, 3, '', '新增', 'system:role:create', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (25, 3, '', '删除', 'system:role:delete', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (26, 3, '', '修改', 'system:role:update', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (27, 3, '', '查询', 'system:role:read', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (28, 4, NULL, '新增', 'system:menu:create', 2, NULL, 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (29, 4, NULL, '删除', 'system:menu:delete', 2, NULL, 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (30, 4, '', '修改', 'system:menu:update', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (31, 4, NULL, '查询', 'system:menu:read', 2, NULL, 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (32, 6, '', '下线', 'system:online:kick', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (34, 10, '', '新增', 'system:task:create', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (35, 10, '', '删除', 'system:task:delete', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (36, 10, '', '执行一次', 'system:task:once', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (37, 10, '', '查询', 'system:task:read', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (38, 10, '', '运行', 'system:task:start', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (39, 10, '', '暂停', 'system:task:stop', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (40, 10, '', '更新', 'system:task:update', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (41, 7, '', '查询登录日志', 'system:log:login:list', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (42, 7, '', '查询任务日志', 'system:log:task:list', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (43, NULL, '/about', '关于', '', 1, 'ant-design:info-circle-outlined', 260, 'account/about', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-02-10 09:35:41.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (48, NULL, '/tool', '系统工具', NULL, 0, 'ant-design:tool-outlined', 254, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:28.327223', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (49, 48, '/tool/email', '邮件工具', 'system:tools:email', 1, 'ant-design:send-outlined', 1, 'tool/email/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-25 00:59:07.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (50, 49, NULL, '发送邮件', 'tools:email:send', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (51, 48, '/tool/storage', '存储管理', 'tool:storage:list', 1, 'ant-design:appstore-outlined', 2, 'tool/storage/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-25 00:59:17.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (52, 51, NULL, '文件上传', 'upload:upload', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-25 01:04:08.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (53, 51, NULL, '文件删除', 'tool:storage:delete', 2, '', 2, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-25 00:56:01.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (54, 2, NULL, '修改密码', 'system:user:password', 2, '', 5, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (56, 1, '/system/dict-type', '字典管理', 'system:dict-type:list', 1, 'ant-design:book-outlined', 4, 'system/dict-type/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:12.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (57, 56, NULL, '新增', 'system:dict-type:create', 2, '', 1, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:20.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (58, 56, NULL, '更新', 'system:dict-type:update', 2, '', 2, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:26.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (59, 56, NULL, '删除', 'system:dict-type:delete', 2, '', 3, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:42.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (60, 56, NULL, '查询', 'system:dict-type:info', 2, '', 4, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:36.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (61, 1, '/system/dept', '部门管理', 'system:dept:list', 1, 'ant-design:deployment-unit-outlined', 3, 'system/dept/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:11:55.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (62, 61, NULL, '新增', 'system:dept:create', 2, '', 1, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (63, 61, NULL, '更新', 'system:dept:update', 2, '', 2, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (64, 61, NULL, '删除', 'system:dept:delete', 2, '', 3, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (65, 61, NULL, '查询', 'system:dept:read', 2, '', 4, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (68, 5, '/health', '健康检查', '', 1, '', 4, '', 1, 0, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:33.352155', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (69, 68, NULL, '网络', 'app:health:network', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (70, 68, NULL, '数据库', 'app:health: database', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (86, 1, '/param-config', '参数配置', 'system:param-config:list', 1, 'ep:edit', 255, 'system/param-config/index', 0, 1, 1, '2024-01-10 17:34:52.569663', '2024-01-19 02:11:27.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (87, 86, NULL, '查询', 'system:param-config:read', 2, '', 255, NULL, 1, 1, 1, '2024-01-10 17:39:20.983241', '2024-01-10 17:39:20.983241', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (88, 86, NULL, '新增', 'system:param-config:create', 2, '', 255, NULL, 1, 1, 1, '2024-01-10 17:39:57.543510', '2024-01-10 17:39:57.543510', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (89, 86, NULL, '更新', 'system:param-config:update', 2, '', 255, NULL, 1, 1, 1, '2024-01-10 17:40:27.355944', '2024-01-10 17:40:27.355944', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (92, 86, NULL, '删除', 'system:param-config:delete', 2, '', 255, NULL, 1, 1, 1, '2024-01-10 17:57:32.059887', '2024-01-10 17:57:32.059887', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (107, 1, 'system/dict-item/:id', '字典项管理', 'system:dict-item:list', 1, 'ant-design:facebook-outlined', 255, 'system/dict-item/index', 0, 0, 1, '2024-01-28 09:21:17.409532', '2024-01-30 13:09:47.000000', 0, 1, '字典管理');
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (108, 107, NULL, '新增', 'system:dict-item:create', 2, '', 255, NULL, 1, 1, 1, '2024-01-28 09:22:39.401758', '2024-01-28 22:38:36.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (109, 107, NULL, '更新', 'system:dict-item:update', 2, '', 255, NULL, 1, 1, 1, '2024-01-28 09:26:43.911886', '2024-01-28 09:26:43.911886', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (110, 107, NULL, '删除', 'system:dict-item:delete', 2, '', 255, NULL, 1, 1, 1, '2024-01-28 09:27:28.535225', '2024-01-28 09:27:28.535225', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (111, 107, NULL, '查询', 'system:dict-item:info', 2, '', 255, NULL, 1, 1, 1, '2024-01-28 09:27:43.894820', '2024-01-28 09:27:43.894820', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (112, 12, 'https://antdv.com/components/overview-cn', 'antdv文档(内嵌)', NULL, 1, '', 255, NULL, 1, 1, 1, '2024-01-29 09:23:08.407723', '2024-01-30 18:41:19.000000', 1, 2, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (115, NULL, 'netdisk', '网盘管理', NULL, 0, 'ant-design:cloud-server-outlined', 255, NULL, 1, 1, 1, '2024-02-10 08:00:02.394616', '2024-02-10 09:35:34.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (116, 115, 'manage', '文件管理', 'netdisk:manage:list', 1, '', 252, 'netdisk/manage', 0, 1, 1, '2024-02-10 08:03:49.837348', '2024-02-10 09:34:41.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (117, 116, NULL, '创建文件或文件夹', 'netdisk:manage:create', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:40:22.317257', '2024-02-10 08:40:22.317257', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (118, 116, NULL, '查看文件', 'netdisk:manage:read', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:41:22.008015', '2024-02-10 08:41:22.008015', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (119, 116, NULL, '更新', 'netdisk:manage:update', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:41:50.691643', '2024-02-10 08:41:50.691643', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (120, 116, NULL, '删除', 'netdisk:manage:delete', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:42:09.480601', '2024-02-10 08:42:09.480601', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (121, 116, NULL, '获取文件上传token', 'netdisk:manage:token', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:42:57.688104', '2024-02-10 08:42:57.688104', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (122, 116, NULL, '添加文件备注', 'netdisk:manage:mark', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:43:40.117321', '2024-02-10 08:43:40.117321', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (123, 116, NULL, '下载文件', 'netdisk:manage:download', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:44:01.338984', '2024-02-10 08:44:01.338984', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (124, 116, NULL, '重命名文件或文件夹', 'netdisk:manage:rename', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:44:27.233379', '2024-02-10 08:45:36.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (125, 116, NULL, '复制文件或文件夹', 'netdisk:manage:copy', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:44:44.725391', '2024-02-10 08:45:48.000000', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (126, 116, NULL, '剪切文件或文件夹', 'netdisk:manage:cut', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:45:21.660511', '2024-02-10 08:45:21.660511', 0, 1, NULL);
|
||||||
|
INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (127, 115, 'overview', '网盘概览', 'netdisk:overview:desc', 1, '', 254, 'netdisk/overview', 0, 1, 1, '2024-02-10 09:32:56.981190', '2024-02-10 09:34:18.000000', 0, 1, NULL);
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_role
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_role`;
|
||||||
|
CREATE TABLE `sys_role` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`status` tinyint DEFAULT '1',
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
`default` tinyint DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE KEY `IDX_223de54d6badbe43a5490450c3` (`name`) USING BTREE,
|
||||||
|
UNIQUE KEY `IDX_05edc0a51f41bb16b7d8137da9` (`value`) USING BTREE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_role
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_role` (`id`, `value`, `name`, `remark`, `status`, `created_at`, `updated_at`, `default`) VALUES (1, 'admin', '管理员', '超级管理员', 1, '2023-11-10 00:31:44.058463', '2024-01-28 21:08:39.000000', NULL);
|
||||||
|
INSERT INTO `sys_role` (`id`, `value`, `name`, `remark`, `status`, `created_at`, `updated_at`, `default`) VALUES (2, 'user', '用户', '', 1, '2023-11-10 00:31:44.058463', '2024-01-30 18:44:45.000000', 1);
|
||||||
|
INSERT INTO `sys_role` (`id`, `value`, `name`, `remark`, `status`, `created_at`, `updated_at`, `default`) VALUES (9, 'test', '测试', NULL, 1, '2024-01-23 22:46:52.408827', '2024-01-30 01:04:52.000000', NULL);
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_role_menus
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_role_menus`;
|
||||||
|
CREATE TABLE `sys_role_menus` (
|
||||||
|
`role_id` int NOT NULL,
|
||||||
|
`menu_id` int NOT NULL,
|
||||||
|
PRIMARY KEY (`role_id`,`menu_id`),
|
||||||
|
KEY `IDX_35ce749b04d57e226d059e0f63` (`role_id`),
|
||||||
|
KEY `IDX_2b95fdc95b329d66c18f5baed6` (`menu_id`),
|
||||||
|
CONSTRAINT `FK_2b95fdc95b329d66c18f5baed6d` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `FK_35ce749b04d57e226d059e0f633` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_role_menus
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 1);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 2);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 3);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 4);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 5);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 6);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 7);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 8);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 9);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 10);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 11);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 12);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 14);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 15);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 20);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 21);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 22);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 23);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 24);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 25);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 26);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 27);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 28);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 29);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 30);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 31);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 32);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 34);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 35);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 36);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 37);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 38);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 39);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 40);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 41);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 42);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 43);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 48);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 49);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 50);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 51);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 52);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 53);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 54);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 56);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 57);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 58);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 59);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 60);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 61);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 62);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 63);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 64);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 65);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 68);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 69);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 70);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 86);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 87);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 88);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 89);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 92);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 107);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 108);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 109);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 110);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 111);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 1);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 5);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 6);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 7);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 8);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 9);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 10);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 11);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 12);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 14);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 15);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 32);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 34);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 35);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 36);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 37);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 38);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 39);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 40);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 41);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 42);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 43);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 48);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 49);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 50);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 51);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 52);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 53);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 56);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 57);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 58);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 59);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 60);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 68);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 69);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 70);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 86);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 87);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 88);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 89);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 92);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 107);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 108);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 109);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 110);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 111);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 112);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 1);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 2);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 3);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 4);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 5);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 6);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 7);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 8);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 9);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 10);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 11);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 20);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 21);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 22);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 23);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 24);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 25);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 26);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 27);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 28);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 29);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 30);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 31);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 32);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 34);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 35);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 36);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 37);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 38);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 39);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 40);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 41);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 42);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 54);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 56);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 57);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 58);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 59);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 60);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 61);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 62);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 63);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 64);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 65);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 68);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 69);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 70);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 86);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 87);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 88);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 89);
|
||||||
|
INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 92);
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_task
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_task`;
|
||||||
|
CREATE TABLE `sys_task` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`service` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`type` tinyint NOT NULL DEFAULT '0',
|
||||||
|
`status` tinyint NOT NULL DEFAULT '1',
|
||||||
|
`start_time` datetime DEFAULT NULL,
|
||||||
|
`end_time` datetime DEFAULT NULL,
|
||||||
|
`limit` int DEFAULT '0',
|
||||||
|
`cron` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`every` int DEFAULT NULL,
|
||||||
|
`data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
|
||||||
|
`job_opts` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
|
||||||
|
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE KEY `IDX_ef8e5ab5ef2fe0ddb1428439ef` (`name`) USING BTREE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_task
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_task` (`id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`, `created_at`, `updated_at`) VALUES (2, '定时清空登录日志', 'LogClearJob.clearLoginLog', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 0, '', '{\"count\":1,\"key\":\"__default__:2:::0 0 3 ? * 1\",\"cron\":\"0 0 3 ? * 1\",\"jobId\":2}', '定时清空登录日志', '2023-11-10 00:31:44.197779', '2024-02-10 09:43:14.000000');
|
||||||
|
INSERT INTO `sys_task` (`id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`, `created_at`, `updated_at`) VALUES (3, '定时清空任务日志', 'LogClearJob.clearTaskLog', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 0, '', '{\"count\":1,\"key\":\"__default__:3:::0 0 3 ? * 1\",\"cron\":\"0 0 3 ? * 1\",\"jobId\":3}', '定时清空任务日志', '2023-11-10 00:31:44.197779', '2024-02-10 09:43:14.000000');
|
||||||
|
INSERT INTO `sys_task` (`id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`, `created_at`, `updated_at`) VALUES (4, '访问百度首页', 'HttpRequestJob.handle', 0, 0, NULL, NULL, 1, '* * * * * ?', NULL, '{\"url\":\"https://www.baidu.com\",\"method\":\"get\"}', NULL, '访问百度首页', '2023-11-10 00:31:44.197779', '2023-11-10 00:31:44.206935');
|
||||||
|
INSERT INTO `sys_task` (`id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`, `created_at`, `updated_at`) VALUES (5, '发送邮箱', 'EmailJob.send', 0, 0, NULL, NULL, -1, '0 0 0 1 * ?', NULL, '{\"subject\":\"这是标题\",\"to\":\"zeyu57@163.com\",\"content\":\"这是正文\"}', NULL, '每月发送邮箱', '2023-11-10 00:31:44.197779', '2023-11-10 00:31:44.206935');
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_task_log
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_task_log`;
|
||||||
|
CREATE TABLE `sys_task_log` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`task_id` int DEFAULT NULL,
|
||||||
|
`status` tinyint NOT NULL DEFAULT '0',
|
||||||
|
`detail` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
|
||||||
|
`consume_time` int DEFAULT '0',
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `FK_f4d9c36052fdb188ff5c089454b` (`task_id`),
|
||||||
|
CONSTRAINT `FK_f4d9c36052fdb188ff5c089454b` FOREIGN KEY (`task_id`) REFERENCES `sys_task` (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_task_log
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_task_log` (`id`, `task_id`, `status`, `detail`, `consume_time`, `created_at`, `updated_at`) VALUES (1, 3, 1, NULL, 0, '2024-02-05 03:06:22.037448', '2024-02-05 03:06:22.037448');
|
||||||
|
INSERT INTO `sys_task_log` (`id`, `task_id`, `status`, `detail`, `consume_time`, `created_at`, `updated_at`) VALUES (2, 2, 1, NULL, 0, '2024-02-10 09:42:21.738712', '2024-02-10 09:42:21.738712');
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_user
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_user`;
|
||||||
|
CREATE TABLE `sys_user` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`psalt` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`status` tinyint DEFAULT '1',
|
||||||
|
`qq` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`dept_id` int DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE KEY `IDX_9e7164b2f1ea1348bc0eb0a7da` (`username`) USING BTREE,
|
||||||
|
KEY `FK_96bde34263e2ae3b46f011124ac` (`dept_id`),
|
||||||
|
CONSTRAINT `FK_96bde34263e2ae3b46f011124ac` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_user
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_user` (`id`, `username`, `password`, `avatar`, `email`, `phone`, `remark`, `psalt`, `status`, `qq`, `created_at`, `updated_at`, `nickname`, `dept_id`) VALUES (1, 'admin', 'a11571e778ee85e82caae2d980952546', 'https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=1743369777', '1743369777@qq.com', '10086', '管理员', 'xQYCspvFb8cAW6GG1pOoUGTLqsuUSO3d', 1, '1743369777', '2023-11-10 00:31:44.104382', '2024-01-29 09:49:43.000000', 'bqy', 1);
|
||||||
|
INSERT INTO `sys_user` (`id`, `username`, `password`, `avatar`, `email`, `phone`, `remark`, `psalt`, `status`, `qq`, `created_at`, `updated_at`, `nickname`, `dept_id`) VALUES (2, 'user', 'dbd89546dec743f82bb9073d6ac39361', 'https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=1743369777', 'luffy@qq.com', '10010', '王路飞', 'qlovDV7pL5dPYPI3QgFFo1HH74nP6sJe', 1, '1743369777', '2023-11-10 00:31:44.104382', '2024-01-29 09:49:57.000000', 'luffy', 8);
|
||||||
|
INSERT INTO `sys_user` (`id`, `username`, `password`, `avatar`, `email`, `phone`, `remark`, `psalt`, `status`, `qq`, `created_at`, `updated_at`, `nickname`, `dept_id`) VALUES (8, 'developer', 'f03fa2a99595127b9a39587421d471f6', '/upload/cfd0d14459bc1a47-202402032141838.jpeg', 'nami@qq.com', '10000', '小贼猫', 'NbGM1z9Vhgo7f4dd2I7JGaGP12RidZdE', 1, '1743369777', '2023-11-10 00:31:44.104382', '2024-02-03 21:41:18.000000', '娜美', 7);
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for sys_user_roles
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `sys_user_roles`;
|
||||||
|
CREATE TABLE `sys_user_roles` (
|
||||||
|
`user_id` int NOT NULL,
|
||||||
|
`role_id` int NOT NULL,
|
||||||
|
PRIMARY KEY (`user_id`,`role_id`),
|
||||||
|
KEY `IDX_96311d970191a044ec048011f4` (`user_id`),
|
||||||
|
KEY `IDX_6d61c5b3f76a3419d93a421669` (`role_id`),
|
||||||
|
CONSTRAINT `FK_6d61c5b3f76a3419d93a4216695` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`),
|
||||||
|
CONSTRAINT `FK_96311d970191a044ec048011f44` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of sys_user_roles
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `sys_user_roles` (`user_id`, `role_id`) VALUES (1, 1);
|
||||||
|
INSERT INTO `sys_user_roles` (`user_id`, `role_id`) VALUES (2, 2);
|
||||||
|
INSERT INTO `sys_user_roles` (`user_id`, `role_id`) VALUES (8, 2);
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for todo
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `todo`;
|
||||||
|
CREATE TABLE `todo` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`value` varchar(255) NOT NULL,
|
||||||
|
`user_id` int DEFAULT NULL,
|
||||||
|
`status` tinyint NOT NULL DEFAULT '0',
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `FK_9cb7989853c4cb7fe427db4b260` (`user_id`),
|
||||||
|
CONSTRAINT `FK_9cb7989853c4cb7fe427db4b260` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of todo
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `todo` (`id`, `value`, `user_id`, `status`, `created_at`, `updated_at`) VALUES (1, 'nest.js', NULL, 0, '2023-11-10 00:31:44.139730', '2023-11-10 00:31:44.147629');
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for tool_storage
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `tool_storage`;
|
||||||
|
CREATE TABLE `tool_storage` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件名',
|
||||||
|
`fileName` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '真实文件名',
|
||||||
|
`ext_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
|
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
|
`size` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
|
`user_id` int DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=79 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of tool_storage
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `tool_storage` (`id`, `created_at`, `updated_at`, `name`, `fileName`, `ext_name`, `path`, `type`, `size`, `user_id`) VALUES (78, '2024-02-03 21:41:16.851178', '2024-02-03 21:41:16.851178', 'cfd0d14459bc1a47-202402032141838.jpeg', 'cfd0d14459bc1a47.jpeg', 'jpeg', '/upload/cfd0d14459bc1a47-202402032141838.jpeg', '图片', '33.92 KB', 1);
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for user_access_tokens
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `user_access_tokens`;
|
||||||
|
CREATE TABLE `user_access_tokens` (
|
||||||
|
`id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`expired_at` datetime NOT NULL COMMENT '令牌过期时间',
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '令牌创建时间',
|
||||||
|
`user_id` int DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `FK_e9d9d0c303432e4e5e48c1c3e90` (`user_id`),
|
||||||
|
CONSTRAINT `FK_e9d9d0c303432e4e5e48c1c3e90` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of user_access_tokens
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('09cf7b0a-62e0-45ee-96b0-e31de32361e0', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDc1MDkxNTd9.0gtKlcxrxQ-TarEai2lsBnfMc852ZDYHeSjjhpo5Fn8', '2024-02-11 04:05:58', '2024-02-10 04:05:57.696509', 1);
|
||||||
|
INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('3f7dffae-db1f-47dc-9677-5c956c3de39e', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDczMTEzMDJ9.D5Qpht1RquKor8WtgfGAcCp8LwG7z3FZhIwbyQzhDmE', '2024-02-08 21:08:22', '2024-02-07 21:08:22.130066', 1);
|
||||||
|
INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('40342c3e-194c-42eb-adee-189389839195', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDczNzIxNjF9.tRQOxhB-01Pcut5MXm4L5D1OrbMJfS4LfUys0XB4kWs', '2024-02-09 14:02:41', '2024-02-08 14:02:41.081164', 1);
|
||||||
|
INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('9d1ba8e9-dffc-4b15-b21f-4a90f196e39c', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDc1Mjc5MDV9.7LeiS3LBBdiAc7YrULWpmnI1oNSvR79K-qjEOlBYOnI', '2024-02-11 09:18:26', '2024-02-10 09:18:25.656695', 1);
|
||||||
|
INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('edbed8fb-bfc7-4fc7-a012-e9fca8ef93fb', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDczNzIxMjd9.VRuJHGca2IPrdfTyW09wfhht4x8JX207pKG-0aZyF60', '2024-02-09 14:02:07', '2024-02-08 14:02:07.390658', 1);
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for user_refresh_tokens
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `user_refresh_tokens`;
|
||||||
|
CREATE TABLE `user_refresh_tokens` (
|
||||||
|
`id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`expired_at` datetime NOT NULL COMMENT '令牌过期时间',
|
||||||
|
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '令牌创建时间',
|
||||||
|
`accessTokenId` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `REL_1dfd080c2abf42198691b60ae3` (`accessTokenId`),
|
||||||
|
CONSTRAINT `FK_1dfd080c2abf42198691b60ae39` FOREIGN KEY (`accessTokenId`) REFERENCES `user_access_tokens` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Records of user_refresh_tokens
|
||||||
|
-- ----------------------------
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('202d0969-6721-4f6f-bf34-f0d1931d4d01', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiRTRpOXVYei1TdldjdWRnclFXVmFXIiwiaWF0IjoxNzA3MzcyMTYxfQ.NOQufR5EAPE2uZoyenmAj9H7S7qo4d6W1aW2ojDxZQc', '2024-03-09 14:02:41', '2024-02-08 14:02:41.091492', '40342c3e-194c-42eb-adee-189389839195');
|
||||||
|
INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('461f9b7c-e500-4762-a6d9-f9ea47163064', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoicXJvTWNYMnhNRW5uRmZGWkQtaUx0IiwiaWF0IjoxNzA3MzExMzAyfQ.dFIWCePZnn2z2Qv1D5PKBKXUwVDI0Gp091MIOi9jiIo', '2024-03-08 21:08:22', '2024-02-07 21:08:22.145464', '3f7dffae-db1f-47dc-9677-5c956c3de39e');
|
||||||
|
INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('b375e623-2d82-48f0-9b7a-9058e3850cc6', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoicDhUMzdGNFFaUDJHLU5yNGVha21wIiwiaWF0IjoxNzA3MzcyMTI3fQ.fn3It6RKIxXlKmqixg0BMmY_YsQmAxtetueqW-0y1IM', '2024-03-09 14:02:07', '2024-02-08 14:02:07.410008', 'edbed8fb-bfc7-4fc7-a012-e9fca8ef93fb');
|
||||||
|
INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('e620ccc1-9e40-4387-9f21-f0722e535a63', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiNE5WdmFIc2hWaU05ZFh0QnVBaHNsIiwiaWF0IjoxNzA3NTI3OTA1fQ.zzyGX0mOJe6KWpTzIi7We9d9c0MRuDeGC86DMB0Vubs', '2024-03-11 09:18:26', '2024-02-10 09:18:25.664251', '9d1ba8e9-dffc-4b15-b21f-4a90f196e39c');
|
||||||
|
INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('f9a003e8-91b7-41ee-979e-e39cca3534ec', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiWGJQdl9SVjFtUl80N0o0TGF0QlV5IiwiaWF0IjoxNzA3NTA5MTU3fQ.oEVdWSigTpAQY7F8MlwBnedldH0sJT1YF1Mt0ZUbIw4', '2024-03-11 04:05:58', '2024-02-10 04:05:57.706763', '09cf7b0a-62e0-45ee-96b0-e31de32361e0');
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
|
@ -0,0 +1,54 @@
|
||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
absolute_redirect off; #取消绝对路径的重定向
|
||||||
|
sendfile on;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_http_version 1.1;
|
||||||
|
gzip_disable "MSIE [1-6]\.";
|
||||||
|
gzip_min_length 256;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied expired no-cache no-store private auth;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
gzip_comp_level 9;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# same docker config
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
# support history mode
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 后端服务
|
||||||
|
location ^~ /api/ {
|
||||||
|
proxy_pass http://nest-admin-server:7001/; # 转发规则
|
||||||
|
proxy_set_header Host $proxy_host; # 修改转发请求头,让目标应用可以受到真实的请求
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
# websocket服务
|
||||||
|
location ^~ /ws-api/ {
|
||||||
|
proxy_pass http://nest-admin-server:7002/;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-real-ip $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:latest
|
||||||
|
container_name: nest-admin-mysql
|
||||||
|
restart: always
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
- .env.production
|
||||||
|
environment:
|
||||||
|
- MYSQL_HOST=${DB_HOST}
|
||||||
|
- MYSQL_PORT=${DB_PORT}
|
||||||
|
- MYSQL_DATABASE=${DB_DATABASE}
|
||||||
|
- MYSQL_USERNAME=${DB_USERNAME}
|
||||||
|
- MYSQL_PASSWORD=${DB_PASSWORD}
|
||||||
|
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- '${DB_PORT}:3306'
|
||||||
|
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci #设置utf8字符集
|
||||||
|
volumes:
|
||||||
|
- ./__data/mysql/:/var/lib/mysql/ # ./__data/mysql/ 路径可以替换成自己的路径
|
||||||
|
- ./deploy/sql/:/docker-entrypoint-initdb.d/ # 初始化的脚本,若 ./__data/mysql/ 文件夹存在数据,则不会执行初始化脚本
|
||||||
|
networks:
|
||||||
|
- nest_admin_net
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
container_name: nest-admin-redis
|
||||||
|
restart: always
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
- .env.production
|
||||||
|
ports:
|
||||||
|
- '${REDIS_PORT}:6379'
|
||||||
|
command: >
|
||||||
|
--requirepass ${REDIS_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- nest_admin_net
|
||||||
|
|
||||||
|
nest-admin-server:
|
||||||
|
# build: 从当前路径构建镜像
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: nest-admin-server
|
||||||
|
restart: always
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
- .env.production
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
ports:
|
||||||
|
- '${APP_PORT}:${APP_PORT}'
|
||||||
|
# 当前服务启动之前先要把depends_on指定的服务启动起来才行
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- redis
|
||||||
|
networks:
|
||||||
|
- nest_admin_net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
nest_admin_net:
|
||||||
|
name: nest_admin_net
|
|
@ -0,0 +1,22 @@
|
||||||
|
const { cpus } = require('os')
|
||||||
|
|
||||||
|
const cpuLen = cpus().length
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'nest-admin',
|
||||||
|
script: './dist/main.js',
|
||||||
|
autorestart: true,
|
||||||
|
exec_mode: 'cluster',
|
||||||
|
watch: false,
|
||||||
|
instances: cpuLen,
|
||||||
|
max_memory_restart: '520M',
|
||||||
|
args: '',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: process.env.APP_PORT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
const antfu = require('@antfu/eslint-config').default
|
||||||
|
|
||||||
|
module.exports = antfu({
|
||||||
|
stylistic: {
|
||||||
|
indent: 2,
|
||||||
|
quotes: 'single',
|
||||||
|
},
|
||||||
|
typescript: true,
|
||||||
|
}, {
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off',
|
||||||
|
'unused-imports/no-unused-vars': 'off',
|
||||||
|
'unused-imports/no-unused-imports': 2,
|
||||||
|
|
||||||
|
'ts/consistent-type-imports': 'off',
|
||||||
|
'node/prefer-global/process': 'off',
|
||||||
|
'node/prefer-global/buffer': 'off',
|
||||||
|
|
||||||
|
'import/order': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
'pathGroups': [
|
||||||
|
{
|
||||||
|
pattern: '~/**',
|
||||||
|
group: 'external',
|
||||||
|
position: 'after',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'alphabetize': { order: 'asc', caseInsensitive: false },
|
||||||
|
'newlines-between': 'always-and-inside-groups',
|
||||||
|
'warnOnUnassignedImports': true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true,
|
||||||
|
"assets": [
|
||||||
|
{ "include": "assets/**/*", "outDir": "dist", "watchAssets": true }
|
||||||
|
],
|
||||||
|
"plugins": [{
|
||||||
|
"name": "@nestjs/swagger",
|
||||||
|
"options": {
|
||||||
|
"introspectComments": true
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
{
|
||||||
|
"name": "nest-admin",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"private": true,
|
||||||
|
"packageManager": "pnpm@8.10.2",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18",
|
||||||
|
"pnpm": ">=8.1.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "npm run gen-env-types",
|
||||||
|
"prebuild": "rimraf dist",
|
||||||
|
"build": "nest build",
|
||||||
|
"dev": "npm run start",
|
||||||
|
"dev:debug": "npm run start:debug",
|
||||||
|
"repl": "npm run start -- --entryFile repl",
|
||||||
|
"bundle": "rimraf out && npm run build && ncc build dist/main.js -o out -m -t && chmod +x out/index.js",
|
||||||
|
"start": "cross-env NODE_ENV=development nest start -w --path tsconfig.json",
|
||||||
|
"start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
|
||||||
|
"start:prod": "cross-env NODE_ENV=production node dist/main",
|
||||||
|
"prod": "cross-env NODE_ENV=production pm2-runtime start ecosystem.config.js",
|
||||||
|
"prod:pm2": "cross-env NODE_ENV=production pm2 restart ecosystem.config.js",
|
||||||
|
"prod:stop": "pm2 stop ecosystem.config.js",
|
||||||
|
"prod:debug": "cross-env NODE_ENV=production nest start --debug --watch",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"doc": "compodoc -p tsconfig.json -s",
|
||||||
|
"gen-env-types": "npx tsx scripts/genEnvTypes.ts",
|
||||||
|
"typeorm": "NODE_ENV=development typeorm-ts-node-esm -d ./dist/config/database.config.js",
|
||||||
|
"migration:create": "npm run typeorm migration:create ./src/migrations/initData",
|
||||||
|
"migration:generate": "npm run typeorm migration:generate ./src/migrations/update-table_$(echo $npm_package_version | sed 's/\\./_/g')",
|
||||||
|
"migration:run": "npm run typeorm -- migration:run",
|
||||||
|
"migration:revert": "npm run typeorm -- migration:revert",
|
||||||
|
"cleanlog": "rimraf logs",
|
||||||
|
"docker:build:dev": "docker compose --env-file .env --env-file .env.development up --build",
|
||||||
|
"docker:build": "docker compose --env-file .env --env-file .env.production up --build",
|
||||||
|
"docker:up": "docker compose --env-file .env --env-file .env.production up -d --no-build",
|
||||||
|
"docker:down": "docker compose --env-file .env --env-file .env.production down",
|
||||||
|
"docker:rmi": "docker compose --env-file .env --env-file .env.production stop nest-admin-server && docker container rm nest-admin-server && docker rmi nest-admin-server",
|
||||||
|
"docker:logs": "docker compose --env-file .env --env-file .env.production logs -f",
|
||||||
|
"c": "git add . && git cz && git push",
|
||||||
|
"release": "standard-version",
|
||||||
|
"commitlint": "commitlint --config commitlint.config.cjs -e -V",
|
||||||
|
"lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,md}\""
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/cookie": "^9.3.1",
|
||||||
|
"@fastify/multipart": "^8.1.0",
|
||||||
|
"@fastify/static": "^7.0.1",
|
||||||
|
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||||
|
"@nestjs-modules/mailer": "^1.10.3",
|
||||||
|
"@nestjs/axios": "^3.0.2",
|
||||||
|
"@nestjs/bull": "^10.1.0",
|
||||||
|
"@nestjs/cache-manager": "^2.2.1",
|
||||||
|
"@nestjs/common": "^10.3.3",
|
||||||
|
"@nestjs/config": "^3.2.0",
|
||||||
|
"@nestjs/core": "^10.3.3",
|
||||||
|
"@nestjs/event-emitter": "^2.0.4",
|
||||||
|
"@nestjs/jwt": "^10.2.0",
|
||||||
|
"@nestjs/passport": "^10.0.3",
|
||||||
|
"@nestjs/platform-fastify": "^10.3.3",
|
||||||
|
"@nestjs/platform-socket.io": "^10.3.3",
|
||||||
|
"@nestjs/schedule": "^4.0.1",
|
||||||
|
"@nestjs/swagger": "^7.3.0",
|
||||||
|
"@nestjs/terminus": "^10.2.2",
|
||||||
|
"@nestjs/throttler": "^5.1.2",
|
||||||
|
"@nestjs/typeorm": "^10.0.2",
|
||||||
|
"@nestjs/websockets": "^10.3.3",
|
||||||
|
"@socket.io/redis-adapter": "^8.2.1",
|
||||||
|
"@socket.io/redis-emitter": "^5.1.0",
|
||||||
|
"@types/lodash": "^4.14.202",
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"bull": "^4.12.2",
|
||||||
|
"cache-manager": "^5.4.0",
|
||||||
|
"cache-manager-ioredis-yet": "^1.2.2",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
|
"cron": "^3.1.6",
|
||||||
|
"cron-parser": "^4.9.0",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"dotenv": "16.4.4",
|
||||||
|
"handlebars": "^4.7.8",
|
||||||
|
"helmet": "^7.1.0",
|
||||||
|
"ioredis": "^5.3.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"mysql2": "^3.9.1",
|
||||||
|
"nanoid": "^3.3.7",
|
||||||
|
"nodemailer": "^6.9.9",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-google-oauth20": "^2.0.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
|
"qiniu": "^7.11.0",
|
||||||
|
"reflect-metadata": "^0.2.1",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"socket.io": "^4.7.4",
|
||||||
|
"stacktrace-js": "^2.0.2",
|
||||||
|
"svg-captcha": "^1.4.0",
|
||||||
|
"systeminformation": "^5.22.0",
|
||||||
|
"typeorm": "0.3.17",
|
||||||
|
"ua-parser-js": "^1.0.37",
|
||||||
|
"winston": "^3.11.0",
|
||||||
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@antfu/eslint-config": "^2.6.4",
|
||||||
|
"@compodoc/compodoc": "^1.1.23",
|
||||||
|
"@nestjs/cli": "^10.3.2",
|
||||||
|
"@nestjs/schematics": "^10.1.1",
|
||||||
|
"@nestjs/testing": "^10.3.2",
|
||||||
|
"@types/cache-manager": "^4.0.6",
|
||||||
|
"@types/jest": "29.5.12",
|
||||||
|
"@types/multer": "^1.4.11",
|
||||||
|
"@types/node": "^20.11.16",
|
||||||
|
"@types/supertest": "^6.0.2",
|
||||||
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"lint-staged": "^15.2.2",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"supertest": "^6.3.4",
|
||||||
|
"ts-jest": "^29.1.2",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"cliui": "^8.0.1",
|
||||||
|
"commitizen": "^4.3.0",
|
||||||
|
"cz-customizable": "^7.0.0",
|
||||||
|
"standard-version": "^9.5.0",
|
||||||
|
"husky": "^8.0.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"@liaoliaots/nestjs-redis": "npm:@songkeys/nestjs-redis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"commitizen": {
|
||||||
|
"path": "cz-customizable"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": [
|
||||||
|
"npm run lint"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^~/(.*)$": "<rootDir>/$1"
|
||||||
|
},
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,49 @@
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
const directoryPath = path.resolve(__dirname, '..')
|
||||||
|
|
||||||
|
const targets = ['.env', `.env.${process.env.NODE_ENV || 'development'}`]
|
||||||
|
|
||||||
|
const envObj = targets.reduce((prev, file) => {
|
||||||
|
const result = dotenv.parse(fs.readFileSync(path.join(directoryPath, file)))
|
||||||
|
return { ...prev, ...result }
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
const envType = Object.entries<string>(envObj).reduce((prev, [key, value]) => {
|
||||||
|
return `${prev}
|
||||||
|
${key}: '${value}';`
|
||||||
|
}, '').trim()
|
||||||
|
|
||||||
|
fs.writeFile(path.join(directoryPath, 'types/env.d.ts'), `
|
||||||
|
// generate by ./scripts/generateEnvTypes.ts
|
||||||
|
declare global {
|
||||||
|
namespace NodeJS {
|
||||||
|
interface ProcessEnv {
|
||||||
|
${envType}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {};
|
||||||
|
`, (err) => {
|
||||||
|
if (err)
|
||||||
|
console.log('生成 env.d.ts 文件失败')
|
||||||
|
else
|
||||||
|
console.log('成功生成 env.d.ts 文件')
|
||||||
|
})
|
||||||
|
|
||||||
|
// console.log('envObj:', envObj)
|
||||||
|
|
||||||
|
function formatValue(value) {
|
||||||
|
let _value
|
||||||
|
try {
|
||||||
|
const res = JSON.parse(value)
|
||||||
|
_value = typeof res === 'object' ? value : res
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
_value = `'${value}'`
|
||||||
|
}
|
||||||
|
return _value
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { exec } from 'node:child_process'
|
||||||
|
|
||||||
|
import { CronJob } from 'cron'
|
||||||
|
|
||||||
|
/** 此文件仅供演示时使用 */
|
||||||
|
|
||||||
|
const runMigrationGenerate = async function () {
|
||||||
|
exec('npm run migration:revert && npm run migration:run', (error, stdout, stderr) => {
|
||||||
|
if (!error)
|
||||||
|
console.log('操作成功', error)
|
||||||
|
|
||||||
|
else
|
||||||
|
console.log('操作失败', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const job = CronJob.from({
|
||||||
|
/** 每天凌晨 4.30 恢复初始数据 */
|
||||||
|
cronTime: '30 4 * * *',
|
||||||
|
timeZone: 'Asia/Shanghai',
|
||||||
|
start: true,
|
||||||
|
onTick() {
|
||||||
|
runMigrationGenerate()
|
||||||
|
console.log('Task executed daily at 4.30 AM:', new Date().toLocaleTimeString())
|
||||||
|
},
|
||||||
|
})
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { ClassSerializerInterceptor, Module } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { ConfigModule } from '@nestjs/config'
|
||||||
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'
|
||||||
|
|
||||||
|
import config from '~/config'
|
||||||
|
import { SharedModule } from '~/shared/shared.module'
|
||||||
|
|
||||||
|
import { AllExceptionsFilter } from './common/filters/any-exception.filter'
|
||||||
|
|
||||||
|
import { IdempotenceInterceptor } from './common/interceptors/idempotence.interceptor'
|
||||||
|
import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor'
|
||||||
|
import { TransformInterceptor } from './common/interceptors/transform.interceptor'
|
||||||
|
import { AuthModule } from './modules/auth/auth.module'
|
||||||
|
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard'
|
||||||
|
import { RbacGuard } from './modules/auth/guards/rbac.guard'
|
||||||
|
import { HealthModule } from './modules/health/health.module'
|
||||||
|
import { NetdiskModule } from './modules/netdisk/netdisk.module'
|
||||||
|
import { SseModule } from './modules/sse/sse.module'
|
||||||
|
import { SystemModule } from './modules/system/system.module'
|
||||||
|
import { TasksModule } from './modules/tasks/tasks.module'
|
||||||
|
import { TodoModule } from './modules/todo/todo.module'
|
||||||
|
import { ToolsModule } from './modules/tools/tools.module'
|
||||||
|
import { DatabaseModule } from './shared/database/database.module'
|
||||||
|
|
||||||
|
import { SocketModule } from './socket/socket.module'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
expandVariables: true,
|
||||||
|
// 指定多个 env 文件时,第一个优先级最高
|
||||||
|
envFilePath: ['.env.local', `.env.${process.env.NODE_ENV}`, '.env'],
|
||||||
|
load: [...Object.values(config)],
|
||||||
|
}),
|
||||||
|
SharedModule,
|
||||||
|
DatabaseModule,
|
||||||
|
|
||||||
|
AuthModule,
|
||||||
|
SystemModule,
|
||||||
|
TasksModule.forRoot(),
|
||||||
|
ToolsModule,
|
||||||
|
SocketModule,
|
||||||
|
HealthModule,
|
||||||
|
SseModule,
|
||||||
|
NetdiskModule,
|
||||||
|
|
||||||
|
// biz
|
||||||
|
|
||||||
|
// end biz
|
||||||
|
|
||||||
|
TodoModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: APP_FILTER, useClass: AllExceptionsFilter },
|
||||||
|
|
||||||
|
{ provide: APP_INTERCEPTOR, useClass: ClassSerializerInterceptor },
|
||||||
|
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
|
||||||
|
{ provide: APP_INTERCEPTOR, useFactory: () => new TimeoutInterceptor(15 * 1000) },
|
||||||
|
{ provide: APP_INTERCEPTOR, useClass: IdempotenceInterceptor },
|
||||||
|
|
||||||
|
{ provide: APP_GUARD, useClass: JwtAuthGuard },
|
||||||
|
{ provide: APP_GUARD, useClass: RbacGuard },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<p>你的验证码是:</p>
|
||||||
|
<h2>{{code}}</h2>
|
||||||
|
<p>该验证码 10 分钟内有效,请勿将验证码告知给他人!</p>
|
||||||
|
<font color='grey'>本邮件由系统自动发出,请勿回复。</font>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<p>Your verification code is:</p>
|
||||||
|
<h1>{{verificationCode}}</h1>
|
||||||
|
<p>This code will expire in 10 minutes.</p>
|
||||||
|
<font color='grey'>This email is sent automatically by the system, please do not
|
||||||
|
reply.</font>
|
|
@ -0,0 +1,46 @@
|
||||||
|
import FastifyCookie from '@fastify/cookie'
|
||||||
|
import FastifyMultipart from '@fastify/multipart'
|
||||||
|
import { FastifyAdapter } from '@nestjs/platform-fastify'
|
||||||
|
|
||||||
|
const app: FastifyAdapter = new FastifyAdapter({
|
||||||
|
trustProxy: true,
|
||||||
|
logger: false,
|
||||||
|
// forceCloseConnections: true,
|
||||||
|
})
|
||||||
|
export { app as fastifyApp }
|
||||||
|
|
||||||
|
app.register(FastifyMultipart, {
|
||||||
|
limits: {
|
||||||
|
fields: 10, // Max number of non-file fields
|
||||||
|
fileSize: 1024 * 1024 * 6, // limit size 6M
|
||||||
|
files: 5, // Max number of file fields
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.register(FastifyCookie, {
|
||||||
|
secret: 'cookie-secret', // 这个 secret 不太重要,不存鉴权相关,无关紧要
|
||||||
|
})
|
||||||
|
|
||||||
|
app.getInstance().addHook('onRequest', (request, reply, done) => {
|
||||||
|
// set undefined origin
|
||||||
|
const { origin } = request.headers
|
||||||
|
if (!origin)
|
||||||
|
request.headers.origin = request.headers.host
|
||||||
|
|
||||||
|
// forbidden php
|
||||||
|
|
||||||
|
const { url } = request
|
||||||
|
|
||||||
|
if (url.endsWith('.php')) {
|
||||||
|
reply.raw.statusMessage
|
||||||
|
= 'Eh. PHP is not support on this machine. Yep, I also think PHP is bestest programming language. But for me it is beyond my reach.'
|
||||||
|
|
||||||
|
return reply.code(418).send()
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip favicon request
|
||||||
|
if (url.match(/favicon.ico$/) || url.match(/manifest.json$/))
|
||||||
|
return reply.code(204).send()
|
||||||
|
|
||||||
|
done()
|
||||||
|
})
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { INestApplication } from '@nestjs/common'
|
||||||
|
import { IoAdapter } from '@nestjs/platform-socket.io'
|
||||||
|
import { createAdapter } from '@socket.io/redis-adapter'
|
||||||
|
|
||||||
|
import { REDIS_PUBSUB } from '~/shared/redis/redis.constant'
|
||||||
|
|
||||||
|
export const RedisIoAdapterKey = 'm-shop-socket'
|
||||||
|
|
||||||
|
export class RedisIoAdapter extends IoAdapter {
|
||||||
|
constructor(private readonly app: INestApplication) {
|
||||||
|
super(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
createIOServer(port: number, options?: any) {
|
||||||
|
const server = super.createIOServer(port, options)
|
||||||
|
|
||||||
|
const { pubClient, subClient } = this.app.get(REDIS_PUBSUB)
|
||||||
|
|
||||||
|
const redisAdapter = createAdapter(pubClient, subClient, {
|
||||||
|
key: RedisIoAdapterKey,
|
||||||
|
requestsTimeout: 10000,
|
||||||
|
})
|
||||||
|
server.adapter(redisAdapter)
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { HttpStatus, Type, applyDecorators } from '@nestjs/common'
|
||||||
|
import { ApiExtraModels, ApiResponse, getSchemaPath } from '@nestjs/swagger'
|
||||||
|
|
||||||
|
import { ResOp } from '~/common/model/response.model'
|
||||||
|
|
||||||
|
const baseTypeNames = ['String', 'Number', 'Boolean']
|
||||||
|
|
||||||
|
function genBaseProp(type: Type<any>) {
|
||||||
|
if (baseTypeNames.includes(type.name))
|
||||||
|
return { type: type.name.toLocaleLowerCase() }
|
||||||
|
else
|
||||||
|
return { $ref: getSchemaPath(type) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 生成返回结果装饰器
|
||||||
|
*/
|
||||||
|
export function ApiResult<TModel extends Type<any>>({
|
||||||
|
type,
|
||||||
|
isPage,
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
type?: TModel | TModel[]
|
||||||
|
isPage?: boolean
|
||||||
|
status?: HttpStatus
|
||||||
|
}) {
|
||||||
|
let prop = null
|
||||||
|
|
||||||
|
if (Array.isArray(type)) {
|
||||||
|
if (isPage) {
|
||||||
|
prop = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
items: {
|
||||||
|
type: 'array',
|
||||||
|
items: { $ref: getSchemaPath(type[0]) },
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
itemCount: { type: 'number', default: 0 },
|
||||||
|
totalItems: { type: 'number', default: 0 },
|
||||||
|
itemsPerPage: { type: 'number', default: 0 },
|
||||||
|
totalPages: { type: 'number', default: 0 },
|
||||||
|
currentPage: { type: 'number', default: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prop = {
|
||||||
|
type: 'array',
|
||||||
|
items: genBaseProp(type[0]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type) {
|
||||||
|
prop = genBaseProp(type)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prop = { type: 'null', default: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = Array.isArray(type) ? type[0] : type
|
||||||
|
|
||||||
|
return applyDecorators(
|
||||||
|
ApiExtraModels(model),
|
||||||
|
ApiResponse({
|
||||||
|
status,
|
||||||
|
schema: {
|
||||||
|
allOf: [
|
||||||
|
{ $ref: getSchemaPath(ResOp) },
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
data: prop,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common'
|
||||||
|
|
||||||
|
export const BYPASS_KEY = '__bypass_key__'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当不需要转换成基础返回格式时添加该装饰器
|
||||||
|
*/
|
||||||
|
export function Bypass() {
|
||||||
|
return SetMetadata(BYPASS_KEY, true)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import type { ExecutionContext } from '@nestjs/common'
|
||||||
|
import { createParamDecorator } from '@nestjs/common'
|
||||||
|
import type { FastifyRequest } from 'fastify'
|
||||||
|
|
||||||
|
export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => {
|
||||||
|
const request = ctx.switchToHttp().getRequest<FastifyRequest>()
|
||||||
|
return data ? request.cookies?.[data] : request.cookies
|
||||||
|
})
|
|
@ -0,0 +1,19 @@
|
||||||
|
import cluster from 'node:cluster'
|
||||||
|
|
||||||
|
import { Cron } from '@nestjs/schedule'
|
||||||
|
|
||||||
|
import { isMainProcess } from '~/global/env'
|
||||||
|
|
||||||
|
export const CronOnce: typeof Cron = (...rest): MethodDecorator => {
|
||||||
|
// If not in cluster mode, and PM2 main worker
|
||||||
|
if (isMainProcess)
|
||||||
|
// eslint-disable-next-line no-useless-call
|
||||||
|
return Cron.call(null, ...rest)
|
||||||
|
|
||||||
|
if (cluster.isWorker && cluster.worker?.id === 1)
|
||||||
|
// eslint-disable-next-line no-useless-call
|
||||||
|
return Cron.call(null, ...rest)
|
||||||
|
|
||||||
|
const returnNothing: MethodDecorator = () => {}
|
||||||
|
return returnNothing
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { applyDecorators } from '@nestjs/common'
|
||||||
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsDate,
|
||||||
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsPositive,
|
||||||
|
IsString,
|
||||||
|
Max,
|
||||||
|
MaxLength,
|
||||||
|
Min,
|
||||||
|
MinLength,
|
||||||
|
} from 'class-validator'
|
||||||
|
import { isNumber } from 'lodash'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ToArray,
|
||||||
|
ToBoolean,
|
||||||
|
ToDate,
|
||||||
|
ToLowerCase,
|
||||||
|
ToNumber,
|
||||||
|
ToTrim,
|
||||||
|
ToUpperCase,
|
||||||
|
} from './transform.decorator'
|
||||||
|
|
||||||
|
interface IOptionalOptions {
|
||||||
|
required?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface INumberFieldOptions extends IOptionalOptions {
|
||||||
|
each?: boolean
|
||||||
|
int?: boolean
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
positive?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IStringFieldOptions extends IOptionalOptions {
|
||||||
|
each?: boolean
|
||||||
|
minLength?: number
|
||||||
|
maxLength?: number
|
||||||
|
lowerCase?: boolean
|
||||||
|
upperCase?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NumberField(
|
||||||
|
options: INumberFieldOptions = {},
|
||||||
|
): PropertyDecorator {
|
||||||
|
const { each, min, max, int, positive, required = true } = options
|
||||||
|
|
||||||
|
const decorators = [ToNumber()]
|
||||||
|
|
||||||
|
if (each)
|
||||||
|
decorators.push(ToArray())
|
||||||
|
|
||||||
|
if (int)
|
||||||
|
decorators.push(IsInt({ each }))
|
||||||
|
else
|
||||||
|
decorators.push(IsNumber({}, { each }))
|
||||||
|
|
||||||
|
if (isNumber(min))
|
||||||
|
decorators.push(Min(min, { each }))
|
||||||
|
|
||||||
|
if (isNumber(max))
|
||||||
|
decorators.push(Max(max, { each }))
|
||||||
|
|
||||||
|
if (positive)
|
||||||
|
decorators.push(IsPositive({ each }))
|
||||||
|
|
||||||
|
if (!required)
|
||||||
|
decorators.push(IsOptional())
|
||||||
|
|
||||||
|
return applyDecorators(...decorators)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StringField(
|
||||||
|
options: IStringFieldOptions = {},
|
||||||
|
): PropertyDecorator {
|
||||||
|
const {
|
||||||
|
each,
|
||||||
|
minLength,
|
||||||
|
maxLength,
|
||||||
|
lowerCase,
|
||||||
|
upperCase,
|
||||||
|
required = true,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const decorators = [IsString({ each }), ToTrim()]
|
||||||
|
|
||||||
|
if (each)
|
||||||
|
decorators.push(ToArray())
|
||||||
|
|
||||||
|
if (isNumber(minLength))
|
||||||
|
decorators.push(MinLength(minLength, { each }))
|
||||||
|
|
||||||
|
if (isNumber(maxLength))
|
||||||
|
decorators.push(MaxLength(maxLength, { each }))
|
||||||
|
|
||||||
|
if (lowerCase)
|
||||||
|
decorators.push(ToLowerCase())
|
||||||
|
|
||||||
|
if (upperCase)
|
||||||
|
decorators.push(ToUpperCase())
|
||||||
|
|
||||||
|
if (!required)
|
||||||
|
decorators.push(IsOptional())
|
||||||
|
else
|
||||||
|
decorators.push(IsNotEmpty({ each }))
|
||||||
|
|
||||||
|
return applyDecorators(...decorators)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BooleanField(
|
||||||
|
options: IOptionalOptions = {},
|
||||||
|
): PropertyDecorator {
|
||||||
|
const decorators = [ToBoolean(), IsBoolean()]
|
||||||
|
|
||||||
|
const { required = true } = options
|
||||||
|
|
||||||
|
if (!required)
|
||||||
|
decorators.push(IsOptional())
|
||||||
|
|
||||||
|
return applyDecorators(...decorators)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DateField(options: IOptionalOptions = {}): PropertyDecorator {
|
||||||
|
const decorators = [ToDate(), IsDate()]
|
||||||
|
|
||||||
|
const { required = true } = options
|
||||||
|
|
||||||
|
if (!required)
|
||||||
|
decorators.push(IsOptional())
|
||||||
|
|
||||||
|
return applyDecorators(...decorators)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import type { ExecutionContext } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { createParamDecorator } from '@nestjs/common'
|
||||||
|
import type { FastifyRequest } from 'fastify'
|
||||||
|
|
||||||
|
import { getIp } from '~/utils/ip.util'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速获取IP
|
||||||
|
*/
|
||||||
|
export const Ip = createParamDecorator((_, context: ExecutionContext) => {
|
||||||
|
const request = context.switchToHttp().getRequest<FastifyRequest>()
|
||||||
|
return getIp(request)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速获取request path,并不包括url params
|
||||||
|
*/
|
||||||
|
export const Uri = createParamDecorator((_, context: ExecutionContext) => {
|
||||||
|
const request = context.switchToHttp().getRequest<FastifyRequest>()
|
||||||
|
return request.routerPath
|
||||||
|
})
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { HttpStatus, NotAcceptableException, Param, ParseIntPipe } from '@nestjs/common'
|
||||||
|
|
||||||
|
export function IdParam() {
|
||||||
|
return Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE, exceptionFactory: (_error) => {
|
||||||
|
throw new NotAcceptableException('id 格式不正确')
|
||||||
|
} }))
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { IdempotenceOption } from '../interceptors/idempotence.interceptor'
|
||||||
|
|
||||||
|
export const HTTP_IDEMPOTENCE_KEY = '__idempotence_key__'
|
||||||
|
export const HTTP_IDEMPOTENCE_OPTIONS = '__idempotence_options__'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 幂等
|
||||||
|
*/
|
||||||
|
export function Idempotence(options?: IdempotenceOption): MethodDecorator {
|
||||||
|
return function (target, key, descriptor: PropertyDescriptor) {
|
||||||
|
SetMetadata(HTTP_IDEMPOTENCE_OPTIONS, options || {})(descriptor.value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { applyDecorators } from '@nestjs/common'
|
||||||
|
import { ApiSecurity } from '@nestjs/swagger'
|
||||||
|
|
||||||
|
export const API_SECURITY_AUTH = 'auth'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* like to @ApiSecurity('auth')
|
||||||
|
*/
|
||||||
|
export function ApiSecurityAuth(): ClassDecorator & MethodDecorator {
|
||||||
|
return applyDecorators(ApiSecurity(API_SECURITY_AUTH))
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
import { Transform } from 'class-transformer'
|
||||||
|
import { castArray, isArray, isNil, trim } from 'lodash'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert string to number
|
||||||
|
*/
|
||||||
|
export function ToNumber(): PropertyDecorator {
|
||||||
|
return Transform(
|
||||||
|
(params) => {
|
||||||
|
const value = params.value as string[] | string
|
||||||
|
|
||||||
|
if (isArray(value))
|
||||||
|
return value.map(v => Number(v))
|
||||||
|
|
||||||
|
return Number(value)
|
||||||
|
},
|
||||||
|
{ toClassOnly: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert string to int
|
||||||
|
*/
|
||||||
|
export function ToInt(): PropertyDecorator {
|
||||||
|
return Transform(
|
||||||
|
(params) => {
|
||||||
|
const value = params.value as string[] | string
|
||||||
|
|
||||||
|
if (isArray(value))
|
||||||
|
return value.map(v => Number.parseInt(v))
|
||||||
|
|
||||||
|
return Number.parseInt(value)
|
||||||
|
},
|
||||||
|
{ toClassOnly: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert string to boolean
|
||||||
|
*/
|
||||||
|
export function ToBoolean(): PropertyDecorator {
|
||||||
|
return Transform(
|
||||||
|
(params) => {
|
||||||
|
switch (params.value) {
|
||||||
|
case 'true':
|
||||||
|
return true
|
||||||
|
case 'false':
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return params.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ toClassOnly: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert string to Date
|
||||||
|
*/
|
||||||
|
export function ToDate(): PropertyDecorator {
|
||||||
|
return Transform(
|
||||||
|
(params) => {
|
||||||
|
const { value } = params
|
||||||
|
|
||||||
|
if (!value)
|
||||||
|
return
|
||||||
|
|
||||||
|
return new Date(value)
|
||||||
|
},
|
||||||
|
{ toClassOnly: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* transforms to array, specially for query params
|
||||||
|
*/
|
||||||
|
export function ToArray(): PropertyDecorator {
|
||||||
|
return Transform(
|
||||||
|
(params) => {
|
||||||
|
const { value } = params
|
||||||
|
|
||||||
|
if (isNil(value))
|
||||||
|
return []
|
||||||
|
|
||||||
|
return castArray(value)
|
||||||
|
},
|
||||||
|
{ toClassOnly: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* trim spaces from start and end, replace multiple spaces with one.
|
||||||
|
*/
|
||||||
|
export function ToTrim(): PropertyDecorator {
|
||||||
|
return Transform(
|
||||||
|
(params) => {
|
||||||
|
const value = params.value as string[] | string
|
||||||
|
|
||||||
|
if (isArray(value))
|
||||||
|
return value.map(v => trim(v))
|
||||||
|
|
||||||
|
return trim(value)
|
||||||
|
},
|
||||||
|
{ toClassOnly: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lowercase value
|
||||||
|
*/
|
||||||
|
export function ToLowerCase(): PropertyDecorator {
|
||||||
|
return Transform(
|
||||||
|
(params) => {
|
||||||
|
const value = params.value as string[] | string
|
||||||
|
|
||||||
|
if (!value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (isArray(value))
|
||||||
|
return value.map(v => v.toLowerCase())
|
||||||
|
|
||||||
|
return value.toLowerCase()
|
||||||
|
},
|
||||||
|
{ toClassOnly: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uppercase value
|
||||||
|
*/
|
||||||
|
export function ToUpperCase(): PropertyDecorator {
|
||||||
|
return Transform(
|
||||||
|
(params) => {
|
||||||
|
const value = params.value as string[] | string
|
||||||
|
|
||||||
|
if (!value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (isArray(value))
|
||||||
|
return value.map(v => v.toUpperCase())
|
||||||
|
|
||||||
|
return value.toUpperCase()
|
||||||
|
},
|
||||||
|
{ toClassOnly: true },
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
import { Expose, Transform } from 'class-transformer'
|
||||||
|
import { IsInt, IsOptional, Max, Min } from 'class-validator'
|
||||||
|
|
||||||
|
export class CursorDto<T = any> {
|
||||||
|
@ApiProperty({ minimum: 0, default: 0 })
|
||||||
|
@Min(0)
|
||||||
|
@IsInt()
|
||||||
|
@Expose()
|
||||||
|
@IsOptional({ always: true })
|
||||||
|
@Transform(({ value: val }) => (val ? Number.parseInt(val) : 0), {
|
||||||
|
toClassOnly: true,
|
||||||
|
})
|
||||||
|
cursor?: number
|
||||||
|
|
||||||
|
@ApiProperty({ minimum: 1, maximum: 100, default: 10 })
|
||||||
|
@Min(1)
|
||||||
|
@Max(100)
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional({ always: true })
|
||||||
|
@Expose()
|
||||||
|
@Transform(({ value: val }) => (val ? Number.parseInt(val) : 10), {
|
||||||
|
toClassOnly: true,
|
||||||
|
})
|
||||||
|
limit?: number
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { IsDefined, IsNotEmpty, IsNumber } from 'class-validator'
|
||||||
|
|
||||||
|
export class BatchDeleteDto {
|
||||||
|
@IsDefined()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsNumber({}, { each: true })
|
||||||
|
ids: number[]
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { IsNumber } from 'class-validator'
|
||||||
|
|
||||||
|
export class IdDto {
|
||||||
|
@IsNumber()
|
||||||
|
id: number
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
import { Expose, Transform } from 'class-transformer'
|
||||||
|
import { Allow, IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator'
|
||||||
|
|
||||||
|
export enum Order {
|
||||||
|
ASC = 'ASC',
|
||||||
|
DESC = 'DESC',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PagerDto<T = any> {
|
||||||
|
@ApiProperty({ minimum: 1, default: 1 })
|
||||||
|
@Min(1)
|
||||||
|
@IsInt()
|
||||||
|
@Expose()
|
||||||
|
@IsOptional({ always: true })
|
||||||
|
@Transform(({ value: val }) => (val ? Number.parseInt(val) : 1), {
|
||||||
|
toClassOnly: true,
|
||||||
|
})
|
||||||
|
page?: number
|
||||||
|
|
||||||
|
@ApiProperty({ minimum: 1, maximum: 100, default: 10 })
|
||||||
|
@Min(1)
|
||||||
|
@Max(100)
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional({ always: true })
|
||||||
|
@Expose()
|
||||||
|
@Transform(({ value: val }) => (val ? Number.parseInt(val) : 10), {
|
||||||
|
toClassOnly: true,
|
||||||
|
})
|
||||||
|
pageSize?: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
field?: string // | keyof T
|
||||||
|
|
||||||
|
@ApiProperty({ enum: Order })
|
||||||
|
@IsEnum(Order)
|
||||||
|
@IsOptional()
|
||||||
|
@Transform(({ value }) => (value === 'asc' ? Order.ASC : Order.DESC))
|
||||||
|
order?: Order
|
||||||
|
|
||||||
|
@Allow()
|
||||||
|
_t?: number
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'
|
||||||
|
import { Exclude } from 'class-transformer'
|
||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
VirtualColumn,
|
||||||
|
} from 'typeorm'
|
||||||
|
|
||||||
|
// 如果觉得前端转换时间太麻烦,并且不考虑通用性的话,可以在服务端进行转换,eg: @UpdateDateColumn({ name: 'updated_at', transformer })
|
||||||
|
// const transformer: ValueTransformer = {
|
||||||
|
// to(value) {
|
||||||
|
// return value
|
||||||
|
// },
|
||||||
|
// from(value) {
|
||||||
|
// return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
export abstract class CommonEntity extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at' })
|
||||||
|
updatedAt: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class CompleteEntity extends CommonEntity {
|
||||||
|
@ApiHideProperty()
|
||||||
|
@Exclude()
|
||||||
|
@Column({ name: 'create_by', update: false, comment: '创建者' })
|
||||||
|
createBy: number
|
||||||
|
|
||||||
|
@ApiHideProperty()
|
||||||
|
@Exclude()
|
||||||
|
@Column({ name: 'update_by', comment: '更新者' })
|
||||||
|
updateBy: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不会保存到数据库中的虚拟列,数据量大时可能会有性能问题,有性能要求请考虑在 service 层手动实现
|
||||||
|
* @see https://typeorm.io/decorator-reference#virtualcolumn
|
||||||
|
*/
|
||||||
|
@ApiProperty({ description: '创建者' })
|
||||||
|
@VirtualColumn({ query: alias => `SELECT username FROM sys_user WHERE id = ${alias}.create_by` })
|
||||||
|
creator: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新者' })
|
||||||
|
@VirtualColumn({ query: alias => `SELECT username FROM sys_user WHERE id = ${alias}.update_by` })
|
||||||
|
updater: string
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { HttpException, HttpStatus } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { ErrorEnum } from '~/constants/error-code.constant'
|
||||||
|
import { RESPONSE_SUCCESS_CODE } from '~/constants/response.constant'
|
||||||
|
|
||||||
|
export class BusinessException extends HttpException {
|
||||||
|
private errorCode: number
|
||||||
|
|
||||||
|
constructor(error: ErrorEnum | string) {
|
||||||
|
// 如果是非 ErrorEnum
|
||||||
|
if (!error.includes(':')) {
|
||||||
|
super(
|
||||||
|
HttpException.createBody({
|
||||||
|
code: RESPONSE_SUCCESS_CODE,
|
||||||
|
message: error,
|
||||||
|
}),
|
||||||
|
HttpStatus.OK,
|
||||||
|
)
|
||||||
|
this.errorCode = RESPONSE_SUCCESS_CODE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [code, message] = error.split(':')
|
||||||
|
super(
|
||||||
|
HttpException.createBody({
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
}),
|
||||||
|
HttpStatus.OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.errorCode = Number(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
getErrorCode(): number {
|
||||||
|
return this.errorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BusinessException as BizException }
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { NotFoundException } from '@nestjs/common'
|
||||||
|
import { sample } from 'lodash'
|
||||||
|
|
||||||
|
export const NotFoundMessage = ['404, Not Found']
|
||||||
|
|
||||||
|
export class CannotFindException extends NotFoundException {
|
||||||
|
constructor() {
|
||||||
|
super(sample(NotFoundMessage))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { HttpException } from '@nestjs/common'
|
||||||
|
import { WsException } from '@nestjs/websockets'
|
||||||
|
|
||||||
|
import { ErrorEnum } from '~/constants/error-code.constant'
|
||||||
|
|
||||||
|
export class SocketException extends WsException {
|
||||||
|
private errorCode: number
|
||||||
|
|
||||||
|
constructor(message: string)
|
||||||
|
constructor(error: ErrorEnum)
|
||||||
|
constructor(...args: any) {
|
||||||
|
const error = args[0]
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
super(
|
||||||
|
HttpException.createBody({
|
||||||
|
code: 0,
|
||||||
|
message: error,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
this.errorCode = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [code, message] = error.split(':')
|
||||||
|
super(
|
||||||
|
HttpException.createBody({
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
this.errorCode = Number(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
getErrorCode(): number {
|
||||||
|
return this.errorCode
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
import {
|
||||||
|
ArgumentsHost,
|
||||||
|
Catch,
|
||||||
|
ExceptionFilter,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Logger,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { FastifyReply, FastifyRequest } from 'fastify'
|
||||||
|
|
||||||
|
import { BusinessException } from '~/common/exceptions/biz.exception'
|
||||||
|
import { ErrorEnum } from '~/constants/error-code.constant'
|
||||||
|
|
||||||
|
import { isDev } from '~/global/env'
|
||||||
|
|
||||||
|
interface myError {
|
||||||
|
readonly status: number
|
||||||
|
readonly statusCode?: number
|
||||||
|
|
||||||
|
readonly message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Catch()
|
||||||
|
export class AllExceptionsFilter implements ExceptionFilter {
|
||||||
|
private readonly logger = new Logger(AllExceptionsFilter.name)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.registerCatchAllExceptionsHook()
|
||||||
|
}
|
||||||
|
|
||||||
|
catch(exception: unknown, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp()
|
||||||
|
const request = ctx.getRequest<FastifyRequest>()
|
||||||
|
const response = ctx.getResponse<FastifyReply>()
|
||||||
|
|
||||||
|
const url = request.raw.url!
|
||||||
|
|
||||||
|
const status
|
||||||
|
= exception instanceof HttpException
|
||||||
|
? exception.getStatus()
|
||||||
|
: (exception as myError)?.status
|
||||||
|
|| (exception as myError)?.statusCode
|
||||||
|
|| HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
|
|
||||||
|
let message
|
||||||
|
= (exception as any)?.response?.message
|
||||||
|
|| (exception as myError)?.message
|
||||||
|
|| `${exception}`
|
||||||
|
|
||||||
|
// 系统内部错误时
|
||||||
|
if (
|
||||||
|
status === HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
|
&& !(exception instanceof BusinessException)
|
||||||
|
) {
|
||||||
|
Logger.error(exception, undefined, 'Catch')
|
||||||
|
|
||||||
|
// 生产环境下隐藏错误信息
|
||||||
|
if (!isDev)
|
||||||
|
message = ErrorEnum.SERVER_ERROR?.split(':')[1]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.logger.warn(
|
||||||
|
`错误信息:(${status}) ${message} Path: ${decodeURI(url)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiErrorCode: number
|
||||||
|
= exception instanceof BusinessException ? exception.getErrorCode() : status
|
||||||
|
|
||||||
|
// 返回基础响应结果
|
||||||
|
const resBody: IBaseResponse = {
|
||||||
|
code: apiErrorCode,
|
||||||
|
message,
|
||||||
|
data: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
response.status(status).send(resBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCatchAllExceptionsHook() {
|
||||||
|
process.on('unhandledRejection', (reason) => {
|
||||||
|
console.error('unhandledRejection: ', reason)
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on('uncaughtException', (err) => {
|
||||||
|
console.error('uncaughtException: ', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
import type {
|
||||||
|
CallHandler,
|
||||||
|
ExecutionContext,
|
||||||
|
NestInterceptor,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ConflictException,
|
||||||
|
Injectable,
|
||||||
|
SetMetadata,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { Reflector } from '@nestjs/core'
|
||||||
|
import type { FastifyRequest } from 'fastify'
|
||||||
|
import { catchError, tap } from 'rxjs'
|
||||||
|
|
||||||
|
import { CacheService } from '~/shared/redis/cache.service'
|
||||||
|
import { hashString } from '~/utils'
|
||||||
|
import { getIp } from '~/utils/ip.util'
|
||||||
|
import { getRedisKey } from '~/utils/redis.util'
|
||||||
|
|
||||||
|
import { HTTP_IDEMPOTENCE_KEY, HTTP_IDEMPOTENCE_OPTIONS } from '../decorators/idempotence.decorator'
|
||||||
|
|
||||||
|
const IdempotenceHeaderKey = 'x-idempotence'
|
||||||
|
|
||||||
|
export interface IdempotenceOption {
|
||||||
|
errorMessage?: string
|
||||||
|
pendingMessage?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果重复请求的话,手动处理异常
|
||||||
|
*/
|
||||||
|
handler?: (req: FastifyRequest) => any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录重复请求的时间
|
||||||
|
* @default 60
|
||||||
|
*/
|
||||||
|
expired?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果 header 没有幂等 key,根据 request 生成 key,如何生成这个 key 的方法
|
||||||
|
*/
|
||||||
|
generateKey?: (req: FastifyRequest) => string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅读取 header 的 key,不自动生成
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
disableGenerateKey?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class IdempotenceInterceptor implements NestInterceptor {
|
||||||
|
constructor(
|
||||||
|
private readonly reflector: Reflector,
|
||||||
|
private readonly cacheService: CacheService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async intercept(context: ExecutionContext, next: CallHandler) {
|
||||||
|
const request = context.switchToHttp().getRequest<FastifyRequest>()
|
||||||
|
|
||||||
|
// skip Get 请求
|
||||||
|
if (request.method.toUpperCase() === 'GET')
|
||||||
|
return next.handle()
|
||||||
|
|
||||||
|
const handler = context.getHandler()
|
||||||
|
const options: IdempotenceOption | undefined = this.reflector.get(
|
||||||
|
HTTP_IDEMPOTENCE_OPTIONS,
|
||||||
|
handler,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!options)
|
||||||
|
return next.handle()
|
||||||
|
|
||||||
|
const {
|
||||||
|
errorMessage = '相同请求成功后在 60 秒内只能发送一次',
|
||||||
|
pendingMessage = '相同请求正在处理中...',
|
||||||
|
handler: errorHandler,
|
||||||
|
expired = 60,
|
||||||
|
disableGenerateKey = false,
|
||||||
|
} = options
|
||||||
|
const redis = this.cacheService.getClient()
|
||||||
|
|
||||||
|
const idempotence = request.headers[IdempotenceHeaderKey] as string
|
||||||
|
const key = disableGenerateKey
|
||||||
|
? undefined
|
||||||
|
: options.generateKey
|
||||||
|
? options.generateKey(request)
|
||||||
|
: this.generateKey(request)
|
||||||
|
|
||||||
|
const idempotenceKey
|
||||||
|
= !!(idempotence || key) && getRedisKey(`idempotence:${idempotence || key}`)
|
||||||
|
|
||||||
|
SetMetadata(HTTP_IDEMPOTENCE_KEY, idempotenceKey)(handler)
|
||||||
|
|
||||||
|
if (idempotenceKey) {
|
||||||
|
const resultValue: '0' | '1' | null = (await redis.get(
|
||||||
|
idempotenceKey,
|
||||||
|
)) as any
|
||||||
|
if (resultValue !== null) {
|
||||||
|
if (errorHandler)
|
||||||
|
return await errorHandler(request)
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
1: errorMessage,
|
||||||
|
0: pendingMessage,
|
||||||
|
}[resultValue]
|
||||||
|
throw new ConflictException(message)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await redis.set(idempotenceKey, '0', 'EX', expired)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next.handle().pipe(
|
||||||
|
tap(async () => {
|
||||||
|
idempotenceKey && (await redis.set(idempotenceKey, '1', 'KEEPTTL'))
|
||||||
|
}),
|
||||||
|
catchError(async (err) => {
|
||||||
|
if (idempotenceKey)
|
||||||
|
await redis.del(idempotenceKey)
|
||||||
|
|
||||||
|
throw err
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateKey(req: FastifyRequest) {
|
||||||
|
const { body, params, query = {}, headers, url } = req
|
||||||
|
|
||||||
|
const obj = { body, url, params, query } as any
|
||||||
|
|
||||||
|
const uuid = headers['x-uuid']
|
||||||
|
if (uuid) {
|
||||||
|
obj.uuid = uuid
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const ua = headers['user-agent']
|
||||||
|
const ip = getIp(req)
|
||||||
|
|
||||||
|
if (!ua && !ip)
|
||||||
|
return undefined
|
||||||
|
|
||||||
|
Object.assign(obj, { ua, ip })
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashString(JSON.stringify(obj))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import {
|
||||||
|
CallHandler,
|
||||||
|
ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
Logger,
|
||||||
|
NestInterceptor,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { Observable, tap } from 'rxjs'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoggingInterceptor implements NestInterceptor {
|
||||||
|
private logger = new Logger(LoggingInterceptor.name, { timestamp: false })
|
||||||
|
|
||||||
|
intercept(
|
||||||
|
context: ExecutionContext,
|
||||||
|
next: CallHandler<any>,
|
||||||
|
): Observable<any> {
|
||||||
|
const call$ = next.handle()
|
||||||
|
const request = context.switchToHttp().getRequest()
|
||||||
|
const content = `${request.method} -> ${request.url}`
|
||||||
|
const isSse = request.headers.accept === 'text/event-stream'
|
||||||
|
this.logger.debug(`+++ 请求:${content}`)
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
return call$.pipe(
|
||||||
|
tap(() => {
|
||||||
|
if (isSse)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.logger.debug(`--- 响应:${content}${` +${Date.now() - now}ms`}`)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import {
|
||||||
|
CallHandler,
|
||||||
|
ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
RequestTimeoutException,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { Observable, TimeoutError, throwError } from 'rxjs'
|
||||||
|
import { catchError, timeout } from 'rxjs/operators'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TimeoutInterceptor implements NestInterceptor {
|
||||||
|
constructor(private readonly time: number = 10000) {}
|
||||||
|
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
|
return next.handle().pipe(
|
||||||
|
timeout(this.time),
|
||||||
|
catchError((err) => {
|
||||||
|
if (err instanceof TimeoutError)
|
||||||
|
return throwError(new RequestTimeoutException('请求超时'))
|
||||||
|
|
||||||
|
return throwError(err)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
CallHandler,
|
||||||
|
ExecutionContext,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { Reflector } from '@nestjs/core'
|
||||||
|
import { Observable } from 'rxjs'
|
||||||
|
import { map } from 'rxjs/operators'
|
||||||
|
|
||||||
|
import { ResOp } from '~/common/model/response.model'
|
||||||
|
|
||||||
|
import { BYPASS_KEY } from '../decorators/bypass.decorator'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一处理返回接口结果,如果不需要则添加 @Bypass 装饰器
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class TransformInterceptor implements NestInterceptor {
|
||||||
|
constructor(private readonly reflector: Reflector) {}
|
||||||
|
|
||||||
|
intercept(
|
||||||
|
context: ExecutionContext,
|
||||||
|
next: CallHandler<any>,
|
||||||
|
): Observable<any> {
|
||||||
|
const bypass = this.reflector.get<boolean>(
|
||||||
|
BYPASS_KEY,
|
||||||
|
context.getHandler(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (bypass)
|
||||||
|
return next.handle()
|
||||||
|
|
||||||
|
return next.handle().pipe(
|
||||||
|
map((data) => {
|
||||||
|
// if (typeof data === 'undefined') {
|
||||||
|
// context.switchToHttp().getResponse().status(HttpStatus.NO_CONTENT);
|
||||||
|
// return data;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return new ResOp(HttpStatus.OK, data ?? null)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
|
||||||
|
import {
|
||||||
|
RESPONSE_SUCCESS_CODE,
|
||||||
|
RESPONSE_SUCCESS_MSG,
|
||||||
|
} from '~/constants/response.constant'
|
||||||
|
|
||||||
|
export class ResOp<T = any> {
|
||||||
|
@ApiProperty({ type: 'object' })
|
||||||
|
data?: T
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'number', default: RESPONSE_SUCCESS_CODE })
|
||||||
|
code: number
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'string', default: RESPONSE_SUCCESS_MSG })
|
||||||
|
message: string
|
||||||
|
|
||||||
|
constructor(code: number, data: T, message = RESPONSE_SUCCESS_MSG) {
|
||||||
|
this.code = code
|
||||||
|
this.data = data
|
||||||
|
this.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
static success<T>(data?: T, message?: string) {
|
||||||
|
return new ResOp(RESPONSE_SUCCESS_CODE, data, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
static error(code: number, message) {
|
||||||
|
return new ResOp(code, {}, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TreeResult<T> {
|
||||||
|
@ApiProperty()
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
parentId: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
children?: TreeResult<T>[]
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import {
|
||||||
|
ArgumentMetadata,
|
||||||
|
BadRequestException,
|
||||||
|
Injectable,
|
||||||
|
PipeTransform,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ParseIntPipe implements PipeTransform<string, number> {
|
||||||
|
transform(value: string, metadata: ArgumentMetadata): number {
|
||||||
|
const val = Number.parseInt(value, 10)
|
||||||
|
|
||||||
|
if (Number.isNaN(val))
|
||||||
|
throw new BadRequestException('id validation failed')
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { ConfigType, registerAs } from '@nestjs/config'
|
||||||
|
|
||||||
|
import { env, envNumber } from '~/global/env'
|
||||||
|
|
||||||
|
export const appRegToken = 'app'
|
||||||
|
|
||||||
|
export const AppConfig = registerAs(appRegToken, () => ({
|
||||||
|
name: env('APP_NAME'),
|
||||||
|
port: envNumber('APP_PORT', 3000),
|
||||||
|
baseUrl: env('APP_BASE_URL'),
|
||||||
|
globalPrefix: env('GLOBAL_PREFIX', 'api'),
|
||||||
|
locale: env('APP_LOCALE', 'zh-CN'),
|
||||||
|
|
||||||
|
logger: {
|
||||||
|
level: env('LOGGER_LEVEL'),
|
||||||
|
maxFiles: envNumber('LOGGER_MAX_FILES'),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
export type IAppConfig = ConfigType<typeof AppConfig>
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { ConfigType, registerAs } from '@nestjs/config'
|
||||||
|
|
||||||
|
import { DataSource, DataSourceOptions } from 'typeorm'
|
||||||
|
|
||||||
|
import { env, envBoolean, envNumber } from '~/global/env'
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/order
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
dotenv.config({ path: `.env.${process.env.NODE_ENV}` })
|
||||||
|
|
||||||
|
// 当前通过 npm scripts 执行的命令
|
||||||
|
const currentScript = process.env.npm_lifecycle_event
|
||||||
|
|
||||||
|
const dataSourceOptions: DataSourceOptions = {
|
||||||
|
type: 'mysql',
|
||||||
|
host: env('DB_HOST', '127.0.0.1'),
|
||||||
|
port: envNumber('DB_PORT', 3306),
|
||||||
|
username: env('DB_USERNAME'),
|
||||||
|
password: env('DB_PASSWORD'),
|
||||||
|
database: env('DB_DATABASE'),
|
||||||
|
synchronize: envBoolean('DB_SYNCHRONIZE', false),
|
||||||
|
// 解决通过 pnpm migration:run 初始化数据时,遇到的 SET FOREIGN_KEY_CHECKS = 0; 等语句报错问题, 仅在执行数据迁移操作时设为 true
|
||||||
|
multipleStatements: currentScript === 'typeorm',
|
||||||
|
entities: ['dist/modules/**/*.entity{.ts,.js}'],
|
||||||
|
migrations: ['dist/migrations/*{.ts,.js}'],
|
||||||
|
subscribers: ['dist/modules/**/*.subscriber{.ts,.js}'],
|
||||||
|
}
|
||||||
|
export const dbRegToken = 'database'
|
||||||
|
|
||||||
|
export const DatabaseConfig = registerAs(
|
||||||
|
dbRegToken,
|
||||||
|
(): DataSourceOptions => dataSourceOptions,
|
||||||
|
)
|
||||||
|
|
||||||
|
export type IDatabaseConfig = ConfigType<typeof DatabaseConfig>
|
||||||
|
|
||||||
|
const dataSource = new DataSource(dataSourceOptions)
|
||||||
|
|
||||||
|
export default dataSource
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { AppConfig, IAppConfig, appRegToken } from './app.config'
|
||||||
|
import { DatabaseConfig, IDatabaseConfig, dbRegToken } from './database.config'
|
||||||
|
import { IMailerConfig, MailerConfig, mailerRegToken } from './mailer.config'
|
||||||
|
import { IOssConfig, OssConfig, ossRegToken } from './oss.config'
|
||||||
|
import { IRedisConfig, RedisConfig, redisRegToken } from './redis.config'
|
||||||
|
import { ISecurityConfig, SecurityConfig, securityRegToken } from './security.config'
|
||||||
|
import { ISwaggerConfig, SwaggerConfig, swaggerRegToken } from './swagger.config'
|
||||||
|
|
||||||
|
export * from './app.config'
|
||||||
|
export * from './redis.config'
|
||||||
|
export * from './database.config'
|
||||||
|
export * from './swagger.config'
|
||||||
|
export * from './security.config'
|
||||||
|
export * from './mailer.config'
|
||||||
|
export * from './oss.config'
|
||||||
|
|
||||||
|
export interface AllConfigType {
|
||||||
|
[appRegToken]: IAppConfig
|
||||||
|
[dbRegToken]: IDatabaseConfig
|
||||||
|
[mailerRegToken]: IMailerConfig
|
||||||
|
[redisRegToken]: IRedisConfig
|
||||||
|
[securityRegToken]: ISecurityConfig
|
||||||
|
[swaggerRegToken]: ISwaggerConfig
|
||||||
|
[ossRegToken]: IOssConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConfigKeyPaths = RecordNamePaths<AllConfigType>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
AppConfig,
|
||||||
|
DatabaseConfig,
|
||||||
|
MailerConfig,
|
||||||
|
OssConfig,
|
||||||
|
RedisConfig,
|
||||||
|
SecurityConfig,
|
||||||
|
SwaggerConfig,
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { ConfigType, registerAs } from '@nestjs/config'
|
||||||
|
|
||||||
|
import { env, envNumber } from '~/global/env'
|
||||||
|
|
||||||
|
export const mailerRegToken = 'mailer'
|
||||||
|
|
||||||
|
export const MailerConfig = registerAs(mailerRegToken, () => ({
|
||||||
|
host: env('SMTP_HOST'),
|
||||||
|
port: envNumber('SMTP_PORT'),
|
||||||
|
ignoreTLS: true,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: env('SMTP_USER'),
|
||||||
|
pass: env('SMTP_PASS'),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
export type IMailerConfig = ConfigType<typeof MailerConfig>
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { ConfigType, registerAs } from '@nestjs/config'
|
||||||
|
import * as qiniu from 'qiniu'
|
||||||
|
|
||||||
|
import { env } from '~/global/env'
|
||||||
|
|
||||||
|
function parseZone(zone: string) {
|
||||||
|
switch (zone) {
|
||||||
|
case 'Zone_as0':
|
||||||
|
return qiniu.zone.Zone_as0
|
||||||
|
case 'Zone_na0':
|
||||||
|
return qiniu.zone.Zone_na0
|
||||||
|
case 'Zone_z0':
|
||||||
|
return qiniu.zone.Zone_z0
|
||||||
|
case 'Zone_z1':
|
||||||
|
return qiniu.zone.Zone_z1
|
||||||
|
case 'Zone_z2':
|
||||||
|
return qiniu.zone.Zone_z2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ossRegToken = 'oss'
|
||||||
|
|
||||||
|
export const OssConfig = registerAs(ossRegToken, () => ({
|
||||||
|
accessKey: env('OSS_ACCESSKEY'),
|
||||||
|
secretKey: env('OSS_SECRETKEY'),
|
||||||
|
domain: env('OSS_DOMAIN'),
|
||||||
|
bucket: env('OSS_BUCKET'),
|
||||||
|
zone: parseZone(env('OSS_ZONE') || 'Zone_z2'),
|
||||||
|
access: (env('OSS_ACCESS_TYPE') as any) || 'public',
|
||||||
|
}))
|
||||||
|
|
||||||
|
export type IOssConfig = ConfigType<typeof OssConfig>
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { ConfigType, registerAs } from '@nestjs/config'
|
||||||
|
|
||||||
|
import { env, envNumber } from '~/global/env'
|
||||||
|
|
||||||
|
export const redisRegToken = 'redis'
|
||||||
|
|
||||||
|
export const RedisConfig = registerAs(redisRegToken, () => ({
|
||||||
|
host: env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
port: envNumber('REDIS_PORT', 6379),
|
||||||
|
password: env('REDIS_PASSWORD'),
|
||||||
|
db: envNumber('REDIS_DB'),
|
||||||
|
}))
|
||||||
|
|
||||||
|
export type IRedisConfig = ConfigType<typeof RedisConfig>
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { ConfigType, registerAs } from '@nestjs/config'
|
||||||
|
|
||||||
|
import { env, envNumber } from '~/global/env'
|
||||||
|
|
||||||
|
export const securityRegToken = 'security'
|
||||||
|
|
||||||
|
export const SecurityConfig = registerAs(securityRegToken, () => ({
|
||||||
|
jwtSecret: env('JWT_SECRET'),
|
||||||
|
jwtExprire: envNumber('JWT_EXPIRE'),
|
||||||
|
refreshSecret: env('REFRESH_TOKEN_SECRET'),
|
||||||
|
refreshExpire: envNumber('REFRESH_TOKEN_EXPIRE'),
|
||||||
|
}))
|
||||||
|
|
||||||
|
export type ISecurityConfig = ConfigType<typeof SecurityConfig>
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { ConfigType, registerAs } from '@nestjs/config'
|
||||||
|
|
||||||
|
import { env, envBoolean } from '~/global/env'
|
||||||
|
|
||||||
|
export const swaggerRegToken = 'swagger'
|
||||||
|
|
||||||
|
export const SwaggerConfig = registerAs(swaggerRegToken, () => ({
|
||||||
|
enable: envBoolean('SWAGGER_ENABLE'),
|
||||||
|
path: env('SWAGGER_PATH'),
|
||||||
|
}))
|
||||||
|
|
||||||
|
export type ISwaggerConfig = ConfigType<typeof SwaggerConfig>
|
|
@ -0,0 +1,8 @@
|
||||||
|
export enum RedisKeys {
|
||||||
|
AccessIp = 'access_ip',
|
||||||
|
CAPTCHA_IMG_PREFIX = 'captcha:img:',
|
||||||
|
AUTH_TOKEN_PREFIX = 'auth:token:',
|
||||||
|
AUTH_PERM_PREFIX = 'auth:permission:',
|
||||||
|
AUTH_PASSWORD_V_PREFIX = 'auth:passwordVersion:',
|
||||||
|
}
|
||||||
|
export const API_CACHE_PREFIX = 'api-cache:'
|
|
@ -0,0 +1,49 @@
|
||||||
|
export enum ErrorEnum {
|
||||||
|
DEFAULT = '0:未知错误',
|
||||||
|
SERVER_ERROR = '500:服务繁忙,请稍后再试',
|
||||||
|
|
||||||
|
SYSTEM_USER_EXISTS = '1001:系统用户已存在',
|
||||||
|
INVALID_VERIFICATION_CODE = '1002:验证码填写有误',
|
||||||
|
INVALID_USERNAME_PASSWORD = '1003:用户名密码有误',
|
||||||
|
NODE_ROUTE_EXISTS = '1004:节点路由已存在',
|
||||||
|
PERMISSION_REQUIRES_PARENT = '1005:权限必须包含父节点',
|
||||||
|
ILLEGAL_OPERATION_DIRECTORY_PARENT = '1006:非法操作:该节点仅支持目录类型父节点',
|
||||||
|
ILLEGAL_OPERATION_CANNOT_CONVERT_NODE_TYPE = '1007:非法操作:节点类型无法直接转换',
|
||||||
|
ROLE_HAS_ASSOCIATED_USERS = '1008:该角色存在关联用户,请先删除关联用户',
|
||||||
|
DEPARTMENT_HAS_ASSOCIATED_USERS = '1009:该部门存在关联用户,请先删除关联用户',
|
||||||
|
DEPARTMENT_HAS_ASSOCIATED_ROLES = '1010:该部门存在关联角色,请先删除关联角色',
|
||||||
|
PASSWORD_MISMATCH = '1011:旧密码与原密码不一致',
|
||||||
|
LOGOUT_OWN_SESSION = '1012:如想下线自身可右上角退出',
|
||||||
|
NOT_ALLOWED_TO_LOGOUT_USER = '1013:不允许下线该用户',
|
||||||
|
PARENT_MENU_NOT_FOUND = '1014:父级菜单不存在',
|
||||||
|
DEPARTMENT_HAS_CHILD_DEPARTMENTS = '1015:该部门存在子部门,请先删除子部门',
|
||||||
|
SYSTEM_BUILTIN_FUNCTION_NOT_ALLOWED = '1016:系统内置功能不允许操作',
|
||||||
|
USER_NOT_FOUND = '1017:用户不存在',
|
||||||
|
UNABLE_TO_FIND_DEPARTMENT_FOR_USER = '1018:无法查找当前用户所属部门',
|
||||||
|
DEPARTMENT_NOT_FOUND = '1019:部门不存在',
|
||||||
|
DICT_NAME_EXISTS = '1020: 已存在相同名称的字典',
|
||||||
|
PARAMETER_CONFIG_KEY_EXISTS = '1021:参数配置键值对已存在',
|
||||||
|
DEFAULT_ROLE_NOT_FOUND = '1022:所分配的默认角色不存在',
|
||||||
|
|
||||||
|
INVALID_LOGIN = '1101:登录无效,请重新登录',
|
||||||
|
NO_PERMISSION = '1102:无权限访问',
|
||||||
|
ONLY_ADMIN_CAN_LOGIN = '1103:不是管理员,无法登录',
|
||||||
|
REQUEST_INVALIDATED = '1104:当前请求已失效',
|
||||||
|
ACCOUNT_LOGGED_IN_ELSEWHERE = '1105:您的账号已在其他地方登录',
|
||||||
|
GUEST_ACCOUNT_RESTRICTED_OPERATION = '1106:游客账号不允许操作',
|
||||||
|
REQUESTED_RESOURCE_NOT_FOUND = '1107:所请求的资源不存在',
|
||||||
|
|
||||||
|
TOO_MANY_REQUESTS = '1201:请求频率过快,请一分钟后再试',
|
||||||
|
MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY = '1202:一天最多发送5条验证码',
|
||||||
|
VERIFICATION_CODE_SEND_FAILED = '1203:验证码发送失败',
|
||||||
|
|
||||||
|
INSECURE_MISSION = '1301:不安全的任务,确保执行的加入@Mission注解',
|
||||||
|
EXECUTED_MISSION_NOT_FOUND = '1302:所执行的任务不存在',
|
||||||
|
MISSION_EXECUTION_FAILED = '1303:任务执行失败',
|
||||||
|
MISSION_NOT_FOUND = '1304:任务不存在',
|
||||||
|
|
||||||
|
// OSS相关
|
||||||
|
OSS_FILE_OR_DIR_EXIST = '1401:当前创建的文件或目录已存在',
|
||||||
|
OSS_NO_OPERATION_REQUIRED = '1402:无需操作',
|
||||||
|
OSS_EXCEE_MAXIMUM_QUANTITY = '1403:已超出支持的最大处理数量',
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export enum EventBusEvents {
|
||||||
|
TokenExpired = 'token.expired',
|
||||||
|
SystemException = 'system.exception',
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export const OSS_CONFIG = 'admin_module:qiniu_config'
|
||||||
|
export const OSS_API = 'http://api.qiniu.com'
|
||||||
|
|
||||||
|
// 目录分隔符
|
||||||
|
export const NETDISK_DELIMITER = '/'
|
||||||
|
export const NETDISK_LIMIT = 100
|
||||||
|
export const NETDISK_HANDLE_MAX_ITEM = 1000
|
||||||
|
export const NETDISK_COPY_SUFFIX = '的副本'
|
|
@ -0,0 +1,15 @@
|
||||||
|
export const RESPONSE_SUCCESS_CODE = 200
|
||||||
|
|
||||||
|
export const RESPONSE_SUCCESS_MSG = 'success'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: contentType
|
||||||
|
*/
|
||||||
|
export enum ContentTypeEnum {
|
||||||
|
// json
|
||||||
|
JSON = 'application/json;charset=UTF-8',
|
||||||
|
// form-data qs
|
||||||
|
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
|
// form-data upload
|
||||||
|
FORM_DATA = 'multipart/form-data;charset=UTF-8',
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const SYS_USER_INITPASSWORD = 'sys_user_initPassword'
|
||||||
|
export const SYS_API_TOKEN = 'sys_api_token'
|
||||||
|
/** 超级管理员用户 id */
|
||||||
|
export const ROOT_USER_ID = 1
|
||||||
|
/** 超级管理员角色 id */
|
||||||
|
export const ROOT_ROLE_ID = 1
|
|
@ -0,0 +1,62 @@
|
||||||
|
import cluster from 'node:cluster'
|
||||||
|
|
||||||
|
export const isMainCluster
|
||||||
|
= process.env.NODE_APP_INSTANCE && Number.parseInt(process.env.NODE_APP_INSTANCE) === 0
|
||||||
|
export const isMainProcess = cluster.isPrimary || isMainCluster
|
||||||
|
|
||||||
|
export const isDev = process.env.NODE_ENV === 'development'
|
||||||
|
|
||||||
|
export const isTest = !!process.env.TEST
|
||||||
|
export const cwd = process.cwd()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础类型接口
|
||||||
|
*/
|
||||||
|
export type BaseType = boolean | number | string | undefined | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化环境变量
|
||||||
|
* @param key 环境变量的键值
|
||||||
|
* @param defaultValue 默认值
|
||||||
|
* @param callback 格式化函数
|
||||||
|
*/
|
||||||
|
function fromatValue<T extends BaseType = string>(key: string, defaultValue: T, callback?: (value: string) => T): T {
|
||||||
|
const value: string | undefined = process.env[key]
|
||||||
|
if (typeof value === 'undefined')
|
||||||
|
return defaultValue
|
||||||
|
|
||||||
|
if (!callback)
|
||||||
|
return value as unknown as T
|
||||||
|
|
||||||
|
return callback(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function env(key: string, defaultValue: string = '') {
|
||||||
|
return fromatValue(key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function envString(key: string, defaultValue: string = '') {
|
||||||
|
return fromatValue(key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function envNumber(key: string, defaultValue: number = 0) {
|
||||||
|
return fromatValue(key, defaultValue, (value) => {
|
||||||
|
try {
|
||||||
|
return Number(value)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
throw new Error(`${key} environment variable is not a number`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function envBoolean(key: string, defaultValue: boolean = false) {
|
||||||
|
return fromatValue(key, defaultValue, (value) => {
|
||||||
|
try {
|
||||||
|
return Boolean(JSON.parse(value))
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
throw new Error(`${key} environment variable is not a boolean`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export function catchError() {
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Promise: ', p, 'Reason: ', reason)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { NotFoundException } from '@nestjs/common'
|
||||||
|
import { ObjectLiteral, Repository } from 'typeorm'
|
||||||
|
|
||||||
|
import { PagerDto } from '~/common/dto/pager.dto'
|
||||||
|
|
||||||
|
import { paginate } from '../paginate'
|
||||||
|
import { Pagination } from '../paginate/pagination'
|
||||||
|
|
||||||
|
export class BaseService<E extends ObjectLiteral, R extends Repository<E> = Repository<E>> {
|
||||||
|
constructor(private repository: R) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async list({
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
}: PagerDto): Promise<Pagination<E>> {
|
||||||
|
return paginate(this.repository, { page, pageSize })
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: number): Promise<E> {
|
||||||
|
const item = await this.repository.createQueryBuilder().where({ id }).getOne()
|
||||||
|
if (!item)
|
||||||
|
throw new NotFoundException('未找到该记录')
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(dto: any): Promise<E> {
|
||||||
|
return await this.repository.save(dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, dto: any): Promise<void> {
|
||||||
|
await this.repository.update(id, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: number): Promise<void> {
|
||||||
|
const item = await this.findOne(id)
|
||||||
|
await this.repository.remove(item)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
import type { Type } from '@nestjs/common'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Query,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { ApiBody, IntersectionType, PartialType } from '@nestjs/swagger'
|
||||||
|
import pluralize from 'pluralize'
|
||||||
|
|
||||||
|
import { ApiResult } from '~/common/decorators/api-result.decorator'
|
||||||
|
import { IdParam } from '~/common/decorators/id-param.decorator'
|
||||||
|
import { PagerDto } from '~/common/dto/pager.dto'
|
||||||
|
|
||||||
|
import { BaseService } from './base.service'
|
||||||
|
|
||||||
|
export function BaseCrudFactory<
|
||||||
|
E extends new (...args: any[]) => any,
|
||||||
|
>({ entity, dto, permissions }: { entity: E, dto?: Type<any>, permissions?: Record<string, string> }): Type<any> {
|
||||||
|
const prefix = entity.name.toLowerCase().replace(/entity$/, '')
|
||||||
|
const pluralizeName = pluralize(prefix) as string
|
||||||
|
|
||||||
|
dto = dto ?? class extends entity {}
|
||||||
|
|
||||||
|
class Dto extends dto {}
|
||||||
|
class UpdateDto extends PartialType(Dto) {}
|
||||||
|
class QueryDto extends IntersectionType(PagerDto, PartialType(Dto)) {}
|
||||||
|
|
||||||
|
permissions = permissions ?? {
|
||||||
|
LIST: `${prefix}:list`,
|
||||||
|
CREATE: `${prefix}:create`,
|
||||||
|
READ: `${prefix}:read`,
|
||||||
|
UPDATE: `${prefix}:update`,
|
||||||
|
DELETE: `${prefix}:delete`,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
@Controller(pluralizeName)
|
||||||
|
class BaseController<S extends BaseService<E>> {
|
||||||
|
constructor(private service: S) { }
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiResult({ type: [entity], isPage: true })
|
||||||
|
async list(@Query() pager: QueryDto) {
|
||||||
|
return await this.service.list(pager)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiResult({ type: entity })
|
||||||
|
async get(@IdParam() id: number) {
|
||||||
|
return await this.service.findOne(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiBody({ type: dto })
|
||||||
|
async create(@Body() dto: Dto) {
|
||||||
|
return await this.service.create(dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id')
|
||||||
|
async update(@IdParam() id: number, @Body() dto: UpdateDto) {
|
||||||
|
return await this.service.update(id, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
async patch(@IdParam() id: number, @Body() dto: UpdateDto) {
|
||||||
|
await this.service.update(id, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async delete(@IdParam() id: number) {
|
||||||
|
await this.service.delete(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BaseController
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { RedisKeys } from '~/constants/cache.constant'
|
||||||
|
|
||||||
|
/** 生成验证码 redis key */
|
||||||
|
export function genCaptchaImgKey(val: string | number) {
|
||||||
|
return `${RedisKeys.CAPTCHA_IMG_PREFIX}${String(val)}` as const
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 生成 auth token redis key */
|
||||||
|
export function genAuthTokenKey(val: string | number) {
|
||||||
|
return `${RedisKeys.AUTH_TOKEN_PREFIX}${String(val)}` as const
|
||||||
|
}
|
||||||
|
/** 生成 auth permission redis key */
|
||||||
|
export function genAuthPermKey(val: string | number) {
|
||||||
|
return `${RedisKeys.AUTH_PERM_PREFIX}${String(val)}` as const
|
||||||
|
}
|
||||||
|
/** 生成 auth passwordVersion redis key */
|
||||||
|
export function genAuthPVKey(val: string | number) {
|
||||||
|
return `${RedisKeys.AUTH_PASSWORD_V_PREFIX}${String(val)}` as const
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { IPaginationMeta } from './interface'
|
||||||
|
import { Pagination } from './pagination'
|
||||||
|
|
||||||
|
export function createPaginationObject<T>({
|
||||||
|
items,
|
||||||
|
totalItems,
|
||||||
|
currentPage,
|
||||||
|
limit,
|
||||||
|
}: {
|
||||||
|
items: T[]
|
||||||
|
totalItems?: number
|
||||||
|
currentPage: number
|
||||||
|
limit: number
|
||||||
|
}): Pagination<T> {
|
||||||
|
const totalPages
|
||||||
|
= totalItems !== undefined ? Math.ceil(totalItems / limit) : undefined
|
||||||
|
|
||||||
|
const meta: IPaginationMeta = {
|
||||||
|
totalItems,
|
||||||
|
itemCount: items.length,
|
||||||
|
itemsPerPage: limit,
|
||||||
|
totalPages,
|
||||||
|
currentPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Pagination<T>(items, meta)
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
import {
|
||||||
|
FindManyOptions,
|
||||||
|
FindOptionsWhere,
|
||||||
|
ObjectLiteral,
|
||||||
|
Repository,
|
||||||
|
SelectQueryBuilder,
|
||||||
|
} from 'typeorm'
|
||||||
|
|
||||||
|
import { createPaginationObject } from './create-pagination'
|
||||||
|
import { IPaginationOptions, PaginationTypeEnum } from './interface'
|
||||||
|
import { Pagination } from './pagination'
|
||||||
|
|
||||||
|
const DEFAULT_LIMIT = 10
|
||||||
|
const DEFAULT_PAGE = 1
|
||||||
|
|
||||||
|
function resolveOptions(
|
||||||
|
options: IPaginationOptions,
|
||||||
|
): [number, number, PaginationTypeEnum] {
|
||||||
|
const { page, pageSize, paginationType } = options
|
||||||
|
|
||||||
|
return [
|
||||||
|
page || DEFAULT_PAGE,
|
||||||
|
pageSize || DEFAULT_LIMIT,
|
||||||
|
paginationType || PaginationTypeEnum.TAKE_AND_SKIP,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function paginateRepository<T>(
|
||||||
|
repository: Repository<T>,
|
||||||
|
options: IPaginationOptions,
|
||||||
|
searchOptions?: FindOptionsWhere<T> | FindManyOptions<T>,
|
||||||
|
): Promise<Pagination<T>> {
|
||||||
|
const [page, limit] = resolveOptions(options)
|
||||||
|
|
||||||
|
const promises: [Promise<T[]>, Promise<number> | undefined] = [
|
||||||
|
repository.find({
|
||||||
|
skip: limit * (page - 1),
|
||||||
|
take: limit,
|
||||||
|
...searchOptions,
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
]
|
||||||
|
|
||||||
|
const [items, total] = await Promise.all(promises)
|
||||||
|
|
||||||
|
return createPaginationObject<T>({
|
||||||
|
items,
|
||||||
|
totalItems: total,
|
||||||
|
currentPage: page,
|
||||||
|
limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function paginateQueryBuilder<T>(
|
||||||
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
|
options: IPaginationOptions,
|
||||||
|
): Promise<Pagination<T>> {
|
||||||
|
const [page, limit, paginationType] = resolveOptions(options)
|
||||||
|
|
||||||
|
if (paginationType === PaginationTypeEnum.TAKE_AND_SKIP)
|
||||||
|
queryBuilder.take(limit).skip((page - 1) * limit)
|
||||||
|
else
|
||||||
|
queryBuilder.limit(limit).offset((page - 1) * limit)
|
||||||
|
|
||||||
|
const [items, total] = await queryBuilder.getManyAndCount()
|
||||||
|
|
||||||
|
return createPaginationObject<T>({
|
||||||
|
items,
|
||||||
|
totalItems: total,
|
||||||
|
currentPage: page,
|
||||||
|
limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function paginateRaw<T>(
|
||||||
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
|
options: IPaginationOptions,
|
||||||
|
): Promise<Pagination<T>> {
|
||||||
|
const [page, limit, paginationType] = resolveOptions(options)
|
||||||
|
|
||||||
|
const promises: [Promise<T[]>, Promise<number> | undefined] = [
|
||||||
|
(paginationType === PaginationTypeEnum.LIMIT_AND_OFFSET
|
||||||
|
? queryBuilder.limit(limit).offset((page - 1) * limit)
|
||||||
|
: queryBuilder.take(limit).skip((page - 1) * limit)
|
||||||
|
).getRawMany<T>(),
|
||||||
|
queryBuilder.getCount(),
|
||||||
|
]
|
||||||
|
|
||||||
|
const [items, total] = await Promise.all(promises)
|
||||||
|
|
||||||
|
return createPaginationObject<T>({
|
||||||
|
items,
|
||||||
|
totalItems: total,
|
||||||
|
currentPage: page,
|
||||||
|
limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function paginateRawAndEntities<T>(
|
||||||
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
|
options: IPaginationOptions,
|
||||||
|
): Promise<[Pagination<T>, Partial<T>[]]> {
|
||||||
|
const [page, limit, paginationType] = resolveOptions(options)
|
||||||
|
|
||||||
|
const promises: [
|
||||||
|
Promise<{ entities: T[], raw: T[] }>,
|
||||||
|
Promise<number> | undefined,
|
||||||
|
] = [
|
||||||
|
(paginationType === PaginationTypeEnum.LIMIT_AND_OFFSET
|
||||||
|
? queryBuilder.limit(limit).offset((page - 1) * limit)
|
||||||
|
: queryBuilder.take(limit).skip((page - 1) * limit)
|
||||||
|
).getRawAndEntities<T>(),
|
||||||
|
queryBuilder.getCount(),
|
||||||
|
]
|
||||||
|
|
||||||
|
const [itemObject, total] = await Promise.all(promises)
|
||||||
|
|
||||||
|
return [
|
||||||
|
createPaginationObject<T>({
|
||||||
|
items: itemObject.entities,
|
||||||
|
totalItems: total,
|
||||||
|
currentPage: page,
|
||||||
|
limit,
|
||||||
|
}),
|
||||||
|
itemObject.raw,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function paginate<T extends ObjectLiteral>(
|
||||||
|
repository: Repository<T>,
|
||||||
|
options: IPaginationOptions,
|
||||||
|
searchOptions?: FindOptionsWhere<T> | FindManyOptions<T>,
|
||||||
|
): Promise<Pagination<T>>
|
||||||
|
export async function paginate<T>(
|
||||||
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
|
options: IPaginationOptions,
|
||||||
|
): Promise<Pagination<T>>
|
||||||
|
|
||||||
|
export async function paginate<T extends ObjectLiteral>(
|
||||||
|
repositoryOrQueryBuilder: Repository<T> | SelectQueryBuilder<T>,
|
||||||
|
options: IPaginationOptions,
|
||||||
|
searchOptions?: FindOptionsWhere<T> | FindManyOptions<T>,
|
||||||
|
) {
|
||||||
|
return repositoryOrQueryBuilder instanceof Repository
|
||||||
|
? paginateRepository<T>(repositoryOrQueryBuilder, options, searchOptions)
|
||||||
|
: paginateQueryBuilder<T>(repositoryOrQueryBuilder, options)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { ObjectLiteral } from 'typeorm'
|
||||||
|
|
||||||
|
export enum PaginationTypeEnum {
|
||||||
|
LIMIT_AND_OFFSET = 'limit',
|
||||||
|
TAKE_AND_SKIP = 'take',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPaginationOptions {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
paginationType?: PaginationTypeEnum
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPaginationMeta extends ObjectLiteral {
|
||||||
|
itemCount: number
|
||||||
|
totalItems?: number
|
||||||
|
itemsPerPage: number
|
||||||
|
totalPages?: number
|
||||||
|
currentPage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPaginationLinks {
|
||||||
|
first?: string
|
||||||
|
previous?: string
|
||||||
|
next?: string
|
||||||
|
last?: string
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { ObjectLiteral } from 'typeorm'
|
||||||
|
|
||||||
|
import { IPaginationMeta } from './interface'
|
||||||
|
|
||||||
|
export class Pagination<
|
||||||
|
PaginationObject,
|
||||||
|
T extends ObjectLiteral = IPaginationMeta,
|
||||||
|
> {
|
||||||
|
constructor(
|
||||||
|
public readonly items: PaginationObject[],
|
||||||
|
|
||||||
|
public readonly meta: T,
|
||||||
|
) {}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
import cluster from 'node:cluster'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import {
|
||||||
|
HttpStatus,
|
||||||
|
Logger,
|
||||||
|
UnprocessableEntityException,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
import { NestFactory } from '@nestjs/core'
|
||||||
|
import { NestFastifyApplication } from '@nestjs/platform-fastify'
|
||||||
|
|
||||||
|
import { useContainer } from 'class-validator'
|
||||||
|
|
||||||
|
import { AppModule } from './app.module'
|
||||||
|
|
||||||
|
import { fastifyApp } from './common/adapters/fastify.adapter'
|
||||||
|
import { RedisIoAdapter } from './common/adapters/socket.adapter'
|
||||||
|
import { LoggingInterceptor } from './common/interceptors/logging.interceptor'
|
||||||
|
import type { ConfigKeyPaths } from './config'
|
||||||
|
import { isDev, isMainProcess } from './global/env'
|
||||||
|
import { setupSwagger } from './setup-swagger'
|
||||||
|
import { LoggerService } from './shared/logger/logger.service'
|
||||||
|
|
||||||
|
declare const module: any
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create<NestFastifyApplication>(
|
||||||
|
AppModule,
|
||||||
|
fastifyApp,
|
||||||
|
{
|
||||||
|
bufferLogs: true,
|
||||||
|
snapshot: true,
|
||||||
|
// forceCloseConnections: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const configService = app.get(ConfigService<ConfigKeyPaths>)
|
||||||
|
|
||||||
|
const { port, globalPrefix } = configService.get('app', { infer: true })
|
||||||
|
|
||||||
|
// class-validator 的 DTO 类中注入 nest 容器的依赖 (用于自定义验证器)
|
||||||
|
useContainer(app.select(AppModule), { fallbackOnErrors: true })
|
||||||
|
|
||||||
|
app.enableCors({ origin: '*', credentials: true })
|
||||||
|
app.setGlobalPrefix(globalPrefix)
|
||||||
|
app.useStaticAssets({ root: path.join(__dirname, '..', 'public') })
|
||||||
|
// Starts listening for shutdown hooks
|
||||||
|
!isDev && app.enableShutdownHooks()
|
||||||
|
|
||||||
|
if (isDev)
|
||||||
|
app.useGlobalInterceptors(new LoggingInterceptor())
|
||||||
|
|
||||||
|
app.useGlobalPipes(
|
||||||
|
new ValidationPipe({
|
||||||
|
transform: true,
|
||||||
|
whitelist: true,
|
||||||
|
transformOptions: { enableImplicitConversion: true },
|
||||||
|
// forbidNonWhitelisted: true, // 禁止 无装饰器验证的数据通过
|
||||||
|
errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
stopAtFirstError: true,
|
||||||
|
exceptionFactory: errors =>
|
||||||
|
new UnprocessableEntityException(
|
||||||
|
errors.map((e) => {
|
||||||
|
const rule = Object.keys(e.constraints!)[0]
|
||||||
|
const msg = e.constraints![rule]
|
||||||
|
return msg
|
||||||
|
})[0],
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.useWebSocketAdapter(new RedisIoAdapter(app))
|
||||||
|
|
||||||
|
setupSwagger(app, configService)
|
||||||
|
|
||||||
|
await app.listen(port, '0.0.0.0', async () => {
|
||||||
|
app.useLogger(app.get(LoggerService))
|
||||||
|
const url = await app.getUrl()
|
||||||
|
const { pid } = process
|
||||||
|
const env = cluster.isPrimary
|
||||||
|
const prefix = env ? 'P' : 'W'
|
||||||
|
|
||||||
|
if (!isMainProcess)
|
||||||
|
return
|
||||||
|
|
||||||
|
const logger = new Logger('NestApplication')
|
||||||
|
logger.log(`[${prefix + pid}] Server running on ${url}`)
|
||||||
|
|
||||||
|
if (isDev)
|
||||||
|
logger.log(`[${prefix + pid}] OpenAPI: ${url}/api-docs`)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept()
|
||||||
|
module.hot.dispose(() => app.close())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap()
|
|
@ -0,0 +1,15 @@
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
const sql = fs.readFileSync(path.join(__dirname, '../../deploy/sql/nest_admin.sql'), 'utf8')
|
||||||
|
|
||||||
|
export class InitData1707996695540 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
export const PUBLIC_KEY = '__public_key__'
|
||||||
|
|
||||||
|
export const PERMISSION_KEY = '__permission_key__'
|
||||||
|
|
||||||
|
export const RESOURCE_KEY = '__resource_key__'
|
||||||
|
|
||||||
|
export const ALLOW_ANON_KEY = '__allow_anon_permission_key__'
|
||||||
|
|
||||||
|
export const AuthStrategy = {
|
||||||
|
LOCAL: 'local',
|
||||||
|
LOCAL_EMAIL: 'local_email',
|
||||||
|
LOCAL_PHONE: 'local_phone',
|
||||||
|
|
||||||
|
JWT: 'jwt',
|
||||||
|
|
||||||
|
GITHUB: 'github',
|
||||||
|
GOOGLE: 'google',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const Roles = {
|
||||||
|
ADMIN: 'admin',
|
||||||
|
USER: 'user',
|
||||||
|
// GUEST: 'guest',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type Role = (typeof Roles)[keyof typeof Roles]
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common'
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger'
|
||||||
|
|
||||||
|
import { ApiResult } from '~/common/decorators/api-result.decorator'
|
||||||
|
import { Ip } from '~/common/decorators/http.decorator'
|
||||||
|
|
||||||
|
import { UserService } from '../user/user.service'
|
||||||
|
|
||||||
|
import { AuthService } from './auth.service'
|
||||||
|
import { Public } from './decorators/public.decorator'
|
||||||
|
import { LoginDto, RegisterDto } from './dto/auth.dto'
|
||||||
|
import { LocalGuard } from './guards/local.guard'
|
||||||
|
import { LoginToken } from './models/auth.model'
|
||||||
|
import { CaptchaService } from './services/captcha.service'
|
||||||
|
|
||||||
|
@ApiTags('Auth - 认证模块')
|
||||||
|
@UseGuards(LocalGuard)
|
||||||
|
@Public()
|
||||||
|
@Controller('auth')
|
||||||
|
export class AuthController {
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private userService: UserService,
|
||||||
|
private captchaService: CaptchaService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post('login')
|
||||||
|
@ApiOperation({ summary: '登录' })
|
||||||
|
@ApiResult({ type: LoginToken })
|
||||||
|
async login(
|
||||||
|
@Body() dto: LoginDto, @Ip() ip: string, @Headers('user-agent') ua: string): Promise<LoginToken> {
|
||||||
|
await this.captchaService.checkImgCaptcha(dto.captchaId, dto.verifyCode)
|
||||||
|
const token = await this.authService.login(
|
||||||
|
dto.username,
|
||||||
|
dto.password,
|
||||||
|
ip,
|
||||||
|
ua,
|
||||||
|
)
|
||||||
|
return { token }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('register')
|
||||||
|
@ApiOperation({ summary: '注册' })
|
||||||
|
async register(@Body() dto: RegisterDto): Promise<void> {
|
||||||
|
await this.userService.register(dto)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config'
|
||||||
|
import { JwtModule } from '@nestjs/jwt'
|
||||||
|
import { PassportModule } from '@nestjs/passport'
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm'
|
||||||
|
|
||||||
|
import { ConfigKeyPaths, ISecurityConfig } from '~/config'
|
||||||
|
import { isDev } from '~/global/env'
|
||||||
|
|
||||||
|
import { LogModule } from '../system/log/log.module'
|
||||||
|
import { MenuModule } from '../system/menu/menu.module'
|
||||||
|
import { RoleModule } from '../system/role/role.module'
|
||||||
|
import { UserModule } from '../user/user.module'
|
||||||
|
|
||||||
|
import { AuthController } from './auth.controller'
|
||||||
|
import { AuthService } from './auth.service'
|
||||||
|
import { AccountController } from './controllers/account.controller'
|
||||||
|
import { CaptchaController } from './controllers/captcha.controller'
|
||||||
|
import { EmailController } from './controllers/email.controller'
|
||||||
|
import { AccessTokenEntity } from './entities/access-token.entity'
|
||||||
|
import { RefreshTokenEntity } from './entities/refresh-token.entity'
|
||||||
|
import { CaptchaService } from './services/captcha.service'
|
||||||
|
import { TokenService } from './services/token.service'
|
||||||
|
import { JwtStrategy } from './strategies/jwt.strategy'
|
||||||
|
import { LocalStrategy } from './strategies/local.strategy'
|
||||||
|
|
||||||
|
const controllers = [
|
||||||
|
AuthController,
|
||||||
|
AccountController,
|
||||||
|
CaptchaController,
|
||||||
|
EmailController,
|
||||||
|
]
|
||||||
|
const providers = [AuthService, TokenService, CaptchaService]
|
||||||
|
const strategies = [LocalStrategy, JwtStrategy]
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([AccessTokenEntity, RefreshTokenEntity]),
|
||||||
|
PassportModule,
|
||||||
|
JwtModule.registerAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
useFactory: (configService: ConfigService<ConfigKeyPaths>) => {
|
||||||
|
const { jwtSecret, jwtExprire }
|
||||||
|
= configService.get<ISecurityConfig>('security')
|
||||||
|
|
||||||
|
return {
|
||||||
|
secret: jwtSecret,
|
||||||
|
expires: jwtExprire,
|
||||||
|
ignoreExpiration: isDev,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
UserModule,
|
||||||
|
RoleModule,
|
||||||
|
MenuModule,
|
||||||
|
LogModule,
|
||||||
|
],
|
||||||
|
controllers: [...controllers],
|
||||||
|
providers: [...providers, ...strategies],
|
||||||
|
exports: [TypeOrmModule, JwtModule, ...providers],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { InjectRedis } from '@liaoliaots/nestjs-redis'
|
||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
|
||||||
|
import Redis from 'ioredis'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
|
||||||
|
import { BusinessException } from '~/common/exceptions/biz.exception'
|
||||||
|
|
||||||
|
import { ErrorEnum } from '~/constants/error-code.constant'
|
||||||
|
import { genAuthPVKey, genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey'
|
||||||
|
|
||||||
|
import { UserService } from '~/modules/user/user.service'
|
||||||
|
|
||||||
|
import { md5 } from '~/utils'
|
||||||
|
|
||||||
|
import { LoginLogService } from '../system/log/services/login-log.service'
|
||||||
|
import { MenuService } from '../system/menu/menu.service'
|
||||||
|
import { RoleService } from '../system/role/role.service'
|
||||||
|
|
||||||
|
import { TokenService } from './services/token.service'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(
|
||||||
|
@InjectRedis() private readonly redis: Redis,
|
||||||
|
private menuService: MenuService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
private userService: UserService,
|
||||||
|
private loginLogService: LoginLogService,
|
||||||
|
private tokenService: TokenService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async validateUser(credential: string, password: string): Promise<any> {
|
||||||
|
const user = await this.userService.findUserByUserName(credential)
|
||||||
|
|
||||||
|
if (isEmpty(user))
|
||||||
|
throw new BusinessException(ErrorEnum.USER_NOT_FOUND)
|
||||||
|
|
||||||
|
const comparePassword = md5(`${password}${user.psalt}`)
|
||||||
|
if (user.password !== comparePassword)
|
||||||
|
throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD)
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const { password, ...result } = user
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录JWT
|
||||||
|
* 返回null则账号密码有误,不存在该用户
|
||||||
|
*/
|
||||||
|
async login(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
ip: string,
|
||||||
|
ua: string,
|
||||||
|
): Promise<string> {
|
||||||
|
const user = await this.userService.findUserByUserName(username)
|
||||||
|
if (isEmpty(user))
|
||||||
|
throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD)
|
||||||
|
|
||||||
|
const comparePassword = md5(`${password}${user.psalt}`)
|
||||||
|
if (user.password !== comparePassword)
|
||||||
|
throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD)
|
||||||
|
|
||||||
|
const roleIds = await this.roleService.getRoleIdsByUser(user.id)
|
||||||
|
|
||||||
|
const roles = await this.roleService.getRoleValues(roleIds)
|
||||||
|
|
||||||
|
// 包含access_token和refresh_token
|
||||||
|
const token = await this.tokenService.generateAccessToken(user.id, roles)
|
||||||
|
|
||||||
|
await this.redis.set(genAuthTokenKey(user.id), token.accessToken)
|
||||||
|
|
||||||
|
// 设置密码版本号 当密码修改时,版本号+1
|
||||||
|
await this.redis.set(genAuthPVKey(user.id), 1)
|
||||||
|
|
||||||
|
// 设置菜单权限
|
||||||
|
const permissions = await this.menuService.getPermissions(user.id)
|
||||||
|
await this.setPermissionsCache(user.id, permissions)
|
||||||
|
|
||||||
|
await this.loginLogService.create(user.id, ip, ua)
|
||||||
|
|
||||||
|
return token.accessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 效验账号密码
|
||||||
|
*/
|
||||||
|
async checkPassword(username: string, password: string) {
|
||||||
|
const user = await this.userService.findUserByUserName(username)
|
||||||
|
|
||||||
|
const comparePassword = md5(`${password}${user.psalt}`)
|
||||||
|
if (user.password !== comparePassword)
|
||||||
|
throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD)
|
||||||
|
}
|
||||||
|
|
||||||
|
async loginLog(uid: number, ip: string, ua: string) {
|
||||||
|
await this.loginLogService.create(uid, ip, ua)
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout(uid: number) {
|
||||||
|
// 删除token
|
||||||
|
await this.userService.forbidden(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置密码
|
||||||
|
*/
|
||||||
|
async resetPassword(username: string, password: string) {
|
||||||
|
const user = await this.userService.findUserByUserName(username)
|
||||||
|
|
||||||
|
await this.userService.forceUpdatePassword(user.id, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除登录状态信息
|
||||||
|
*/
|
||||||
|
async clearLoginStatus(uid: number): Promise<void> {
|
||||||
|
await this.userService.forbidden(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜单列表
|
||||||
|
*/
|
||||||
|
async getMenus(uid: number): Promise<string[]> {
|
||||||
|
return this.menuService.getMenus(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取权限列表
|
||||||
|
*/
|
||||||
|
async getPermissions(uid: number): Promise<string[]> {
|
||||||
|
return this.menuService.getPermissions(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPermissionsCache(uid: number): Promise<string[]> {
|
||||||
|
const permissionString = await this.redis.get(genAuthPermKey(uid))
|
||||||
|
return permissionString ? JSON.parse(permissionString) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
async setPermissionsCache(uid: number, permissions: string[]): Promise<void> {
|
||||||
|
await this.redis.set(genAuthPermKey(uid), JSON.stringify(permissions))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPasswordVersionByUid(uid: number): Promise<string> {
|
||||||
|
return this.redis.get(genAuthPVKey(uid))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTokenByUid(uid: number): Promise<string> {
|
||||||
|
return this.redis.get(genAuthTokenKey(uid))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { Body, Controller, Get, Post, Put, UseGuards } from '@nestjs/common'
|
||||||
|
import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger'
|
||||||
|
|
||||||
|
import { ApiResult } from '~/common/decorators/api-result.decorator'
|
||||||
|
|
||||||
|
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator'
|
||||||
|
import { AllowAnon } from '~/modules/auth/decorators/allow-anon.decorator'
|
||||||
|
import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator'
|
||||||
|
|
||||||
|
import { PasswordUpdateDto } from '~/modules/user/dto/password.dto'
|
||||||
|
|
||||||
|
import { AccountInfo } from '../../user/user.model'
|
||||||
|
import { UserService } from '../../user/user.service'
|
||||||
|
import { AuthService } from '../auth.service'
|
||||||
|
import { AccountMenus, AccountUpdateDto } from '../dto/account.dto'
|
||||||
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard'
|
||||||
|
|
||||||
|
@ApiTags('Account - 账户模块')
|
||||||
|
@ApiSecurityAuth()
|
||||||
|
@ApiExtraModels(AccountInfo)
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Controller('account')
|
||||||
|
export class AccountController {
|
||||||
|
constructor(
|
||||||
|
private userService: UserService,
|
||||||
|
private authService: AuthService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('profile')
|
||||||
|
@ApiOperation({ summary: '获取账户资料' })
|
||||||
|
@ApiResult({ type: AccountInfo })
|
||||||
|
@AllowAnon()
|
||||||
|
async profile(@AuthUser() user: IAuthUser): Promise<AccountInfo> {
|
||||||
|
return this.userService.getAccountInfo(user.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('logout')
|
||||||
|
@ApiOperation({ summary: '账户登出' })
|
||||||
|
@AllowAnon()
|
||||||
|
async logout(@AuthUser() user: IAuthUser): Promise<void> {
|
||||||
|
await this.authService.clearLoginStatus(user.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('menus')
|
||||||
|
@ApiOperation({ summary: '获取菜单列表' })
|
||||||
|
@ApiResult({ type: [AccountMenus] })
|
||||||
|
@AllowAnon()
|
||||||
|
async menu(@AuthUser() user: IAuthUser): Promise<string[]> {
|
||||||
|
return this.authService.getMenus(user.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('permissions')
|
||||||
|
@ApiOperation({ summary: '获取权限列表' })
|
||||||
|
@ApiResult({ type: [String] })
|
||||||
|
@AllowAnon()
|
||||||
|
async permissions(@AuthUser() user: IAuthUser): Promise<string[]> {
|
||||||
|
return this.authService.getPermissions(user.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('update')
|
||||||
|
@ApiOperation({ summary: '更改账户资料' })
|
||||||
|
@AllowAnon()
|
||||||
|
async update(
|
||||||
|
@AuthUser() user: IAuthUser, @Body() dto: AccountUpdateDto): Promise<void> {
|
||||||
|
await this.userService.updateAccountInfo(user.uid, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('password')
|
||||||
|
@ApiOperation({ summary: '更改账户密码' })
|
||||||
|
@AllowAnon()
|
||||||
|
async password(
|
||||||
|
@AuthUser() user: IAuthUser, @Body() dto: PasswordUpdateDto): Promise<void> {
|
||||||
|
await this.userService.updatePassword(user.uid, dto)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { InjectRedis } from '@liaoliaots/nestjs-redis'
|
||||||
|
import { Controller, Get, Query } from '@nestjs/common'
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger'
|
||||||
|
|
||||||
|
import Redis from 'ioredis'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import * as svgCaptcha from 'svg-captcha'
|
||||||
|
|
||||||
|
import { ApiResult } from '~/common/decorators/api-result.decorator'
|
||||||
|
import { genCaptchaImgKey } from '~/helper/genRedisKey'
|
||||||
|
import { generateUUID } from '~/utils'
|
||||||
|
|
||||||
|
import { Public } from '../decorators/public.decorator'
|
||||||
|
|
||||||
|
import { ImageCaptchaDto } from '../dto/captcha.dto'
|
||||||
|
import { ImageCaptcha } from '../models/auth.model'
|
||||||
|
|
||||||
|
@ApiTags('Captcha - 验证码模块')
|
||||||
|
// @UseGuards(ThrottlerGuard)
|
||||||
|
@Controller('auth/captcha')
|
||||||
|
export class CaptchaController {
|
||||||
|
constructor(@InjectRedis() private redis: Redis) {}
|
||||||
|
|
||||||
|
@Get('img')
|
||||||
|
@ApiOperation({ summary: '获取登录图片验证码' })
|
||||||
|
@ApiResult({ type: ImageCaptcha })
|
||||||
|
@Public()
|
||||||
|
// @Throttle({ default: { limit: 2, ttl: 600000 } })
|
||||||
|
async captchaByImg(@Query() dto: ImageCaptchaDto): Promise<ImageCaptcha> {
|
||||||
|
const { width, height } = dto
|
||||||
|
|
||||||
|
const svg = svgCaptcha.create({
|
||||||
|
size: 4,
|
||||||
|
color: true,
|
||||||
|
noise: 4,
|
||||||
|
width: isEmpty(width) ? 100 : width,
|
||||||
|
height: isEmpty(height) ? 50 : height,
|
||||||
|
charPreset: '1234567890',
|
||||||
|
})
|
||||||
|
const result = {
|
||||||
|
img: `data:image/svg+xml;base64,${Buffer.from(svg.data).toString(
|
||||||
|
'base64',
|
||||||
|
)}`,
|
||||||
|
id: generateUUID(),
|
||||||
|
}
|
||||||
|
// 5分钟过期时间
|
||||||
|
await this.redis.set(genCaptchaImgKey(result.id), svg.text, 'EX', 60 * 5)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { Body, Controller, Post, UseGuards } from '@nestjs/common'
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger'
|
||||||
|
|
||||||
|
import { Throttle, ThrottlerGuard } from '@nestjs/throttler'
|
||||||
|
|
||||||
|
import { Ip } from '~/common/decorators/http.decorator'
|
||||||
|
|
||||||
|
import { MailerService } from '~/shared/mailer/mailer.service'
|
||||||
|
|
||||||
|
import { Public } from '../decorators/public.decorator'
|
||||||
|
|
||||||
|
import { SendEmailCodeDto } from '../dto/captcha.dto'
|
||||||
|
|
||||||
|
@ApiTags('Auth - 认证模块')
|
||||||
|
@UseGuards(ThrottlerGuard)
|
||||||
|
@Controller('auth/email')
|
||||||
|
export class EmailController {
|
||||||
|
constructor(private mailerService: MailerService) {}
|
||||||
|
|
||||||
|
@Post('send')
|
||||||
|
@ApiOperation({ summary: '发送邮箱验证码' })
|
||||||
|
@Public()
|
||||||
|
@Throttle({ default: { limit: 2, ttl: 600000 } })
|
||||||
|
async sendEmailCode(
|
||||||
|
@Body() dto: SendEmailCodeDto,
|
||||||
|
@Ip() ip: string,
|
||||||
|
): Promise<void> {
|
||||||
|
// await this.authService.checkImgCaptcha(dto.captchaId, dto.verifyCode);
|
||||||
|
const { email } = dto
|
||||||
|
|
||||||
|
await this.mailerService.checkLimit(email, ip)
|
||||||
|
const { code } = await this.mailerService.sendVerificationCode(email)
|
||||||
|
|
||||||
|
await this.mailerService.log(email, code, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Post()
|
||||||
|
// async authWithEmail(@AuthUser() user: IAuthUser) {
|
||||||
|
// // TODO:
|
||||||
|
// }
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { ALLOW_ANON_KEY } from '../auth.constant'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当接口不需要检测用户是否具有操作权限时添加该装饰器
|
||||||
|
*/
|
||||||
|
export const AllowAnon = () => SetMetadata(ALLOW_ANON_KEY, true)
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { ExecutionContext, createParamDecorator } from '@nestjs/common'
|
||||||
|
import { FastifyRequest } from 'fastify'
|
||||||
|
|
||||||
|
type Payload = keyof IAuthUser
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取当前登录用户信息, 并挂载到request上
|
||||||
|
*/
|
||||||
|
export const AuthUser = createParamDecorator(
|
||||||
|
(data: Payload, ctx: ExecutionContext) => {
|
||||||
|
const request = ctx.switchToHttp().getRequest<FastifyRequest>()
|
||||||
|
// auth guard will mount this
|
||||||
|
const user = request.user as IAuthUser
|
||||||
|
|
||||||
|
return data ? user?.[data] : user
|
||||||
|
},
|
||||||
|
)
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { SetMetadata, applyDecorators } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { isPlainObject } from 'lodash'
|
||||||
|
|
||||||
|
import { PERMISSION_KEY } from '../auth.constant'
|
||||||
|
|
||||||
|
type TupleToObject<T extends string, P extends ReadonlyArray<string>> = {
|
||||||
|
[K in Uppercase<P[number]>]: `${T}:${Lowercase<K>}`
|
||||||
|
}
|
||||||
|
type AddPrefixToObjectValue<T extends string, P extends Record<string, string>> = {
|
||||||
|
[K in keyof P]: K extends string ? `${T}:${P[K]}` : never
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 资源操作需要特定的权限 */
|
||||||
|
export function Perm(permission: string | string[]) {
|
||||||
|
return applyDecorators(SetMetadata(PERMISSION_KEY, permission))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** (此举非必需)保存通过 definePermission 定义的所有权限,可用于前端开发人员开发阶段的 ts 类型提示,避免前端权限定义与后端定义不匹配 */
|
||||||
|
let permissions: string[] = []
|
||||||
|
/**
|
||||||
|
* 定义权限,同时收集所有被定义的权限
|
||||||
|
*
|
||||||
|
* - 通过对象形式定义, eg:
|
||||||
|
* ```ts
|
||||||
|
* definePermission('app:health', {
|
||||||
|
* NETWORK: 'network'
|
||||||
|
* };
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* - 通过字符串数组形式定义, eg:
|
||||||
|
* ```ts
|
||||||
|
* definePermission('app:health', ['network']);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function definePermission<T extends string, U extends Record<string, string>>(modulePrefix: T, actionMap: U): AddPrefixToObjectValue<T, U>
|
||||||
|
export function definePermission<T extends string, U extends ReadonlyArray<string>>(modulePrefix: T, actions: U): TupleToObject<T, U>
|
||||||
|
export function definePermission(modulePrefix: string, actions) {
|
||||||
|
if (isPlainObject(actions)) {
|
||||||
|
Object.entries(actions).forEach(([key, action]) => {
|
||||||
|
actions[key] = `${modulePrefix}:${action}`
|
||||||
|
})
|
||||||
|
permissions = [...new Set([...permissions, ...Object.values<string>(actions)])]
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
else if (Array.isArray(actions)) {
|
||||||
|
const permissionFormats = actions.map(action => `${modulePrefix}:${action}`)
|
||||||
|
permissions = [...new Set([...permissions, ...permissionFormats])]
|
||||||
|
|
||||||
|
return actions.reduce((prev, action) => {
|
||||||
|
prev[action.toUpperCase()] = `${modulePrefix}:${action}`
|
||||||
|
return prev
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取所有通过 definePermission 定义的权限 */
|
||||||
|
export const getDefinePermissions = () => permissions
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { PUBLIC_KEY } from '../auth.constant'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当接口不需要检测用户登录时添加该装饰器
|
||||||
|
*/
|
||||||
|
export const Public = () => SetMetadata(PUBLIC_KEY, true)
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { SetMetadata, applyDecorators } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { ObjectLiteral, ObjectType, Repository } from 'typeorm'
|
||||||
|
|
||||||
|
import { RESOURCE_KEY } from '../auth.constant'
|
||||||
|
|
||||||
|
export type Condition<E extends ObjectLiteral = any> = (Repository: Repository<E>, items: number[], user: IAuthUser) => Promise<boolean>
|
||||||
|
|
||||||
|
export interface ResourceObject { entity: ObjectType<any>, condition: Condition }
|
||||||
|
export function Resource<E extends ObjectLiteral = any>(entity: ObjectType<E>, condition?: Condition<E>) {
|
||||||
|
return applyDecorators(SetMetadata(RESOURCE_KEY, { entity, condition }))
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { ApiProperty, OmitType, PartialType, PickType } from '@nestjs/swagger'
|
||||||
|
import {
|
||||||
|
IsEmail,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Matches,
|
||||||
|
MaxLength,
|
||||||
|
MinLength,
|
||||||
|
} from 'class-validator'
|
||||||
|
|
||||||
|
import { MenuEntity } from '~/modules/system/menu/menu.entity'
|
||||||
|
|
||||||
|
export class AccountUpdateDto {
|
||||||
|
@ApiProperty({ description: '用户呢称' })
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
nickname: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户邮箱' })
|
||||||
|
@IsEmail()
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户QQ' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@Matches(/^[0-9]+$/)
|
||||||
|
@MinLength(5)
|
||||||
|
@MaxLength(11)
|
||||||
|
qq: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户手机号' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
phone: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户头像' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
avatar: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户备注' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
remark: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ResetPasswordDto {
|
||||||
|
@ApiProperty({ description: '临时token', example: 'uuid' })
|
||||||
|
@IsString()
|
||||||
|
accessToken: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '密码', example: 'a123456' })
|
||||||
|
@IsString()
|
||||||
|
@Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/)
|
||||||
|
@MinLength(6)
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MenuMeta extends PartialType(OmitType(MenuEntity, ['parentId', 'createdAt', 'updatedAt', 'id', 'roles', 'path', 'name'] as const)) {
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
export class AccountMenus extends PickType(MenuEntity, ['id', 'path', 'name', 'component'] as const) {
|
||||||
|
meta: MenuMeta
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
|
||||||
|
import { IsString, Matches, MaxLength, MinLength } from 'class-validator'
|
||||||
|
|
||||||
|
export class LoginDto {
|
||||||
|
@ApiProperty({ description: '手机号/邮箱' })
|
||||||
|
@IsString()
|
||||||
|
@MinLength(4)
|
||||||
|
username: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '密码', example: 'a123456' })
|
||||||
|
@IsString()
|
||||||
|
@Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/)
|
||||||
|
@MinLength(6)
|
||||||
|
password: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '验证码标识' })
|
||||||
|
@IsString()
|
||||||
|
captchaId: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户输入的验证码' })
|
||||||
|
@IsString()
|
||||||
|
@MinLength(4)
|
||||||
|
@MaxLength(4)
|
||||||
|
verifyCode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RegisterDto {
|
||||||
|
@ApiProperty({ description: '账号' })
|
||||||
|
@IsString()
|
||||||
|
username: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '密码' })
|
||||||
|
@IsString()
|
||||||
|
@Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/)
|
||||||
|
@MinLength(6)
|
||||||
|
@MaxLength(16)
|
||||||
|
password: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '语言', examples: ['EN', 'ZH'] })
|
||||||
|
@IsString()
|
||||||
|
lang: string
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
import { Type } from 'class-transformer'
|
||||||
|
import {
|
||||||
|
IsEmail,
|
||||||
|
IsInt,
|
||||||
|
IsMobilePhone,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
} from 'class-validator'
|
||||||
|
|
||||||
|
export class ImageCaptchaDto {
|
||||||
|
@ApiProperty({
|
||||||
|
required: false,
|
||||||
|
default: 100,
|
||||||
|
description: '验证码宽度',
|
||||||
|
})
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
readonly width: number = 100
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
required: false,
|
||||||
|
default: 50,
|
||||||
|
description: '验证码宽度',
|
||||||
|
})
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
readonly height: number = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SendEmailCodeDto {
|
||||||
|
@ApiProperty({ description: '邮箱' })
|
||||||
|
@IsEmail({}, { message: '邮箱格式不正确' })
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SendSmsCodeDto {
|
||||||
|
@ApiProperty({ description: '手机号' })
|
||||||
|
@IsMobilePhone('zh-CN', {}, { message: '手机号格式不正确' })
|
||||||
|
phone: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CheckCodeDto {
|
||||||
|
@ApiProperty({ description: '手机号/邮箱' })
|
||||||
|
@IsString()
|
||||||
|
account: string
|
||||||
|
|
||||||
|
@ApiProperty({ description: '验证码' })
|
||||||
|
@IsString()
|
||||||
|
code: string
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm'
|
||||||
|
|
||||||
|
import { UserEntity } from '~/modules/user/user.entity'
|
||||||
|
|
||||||
|
import { RefreshTokenEntity } from './refresh-token.entity'
|
||||||
|
|
||||||
|
@Entity('user_access_tokens')
|
||||||
|
export class AccessTokenEntity extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string
|
||||||
|
|
||||||
|
@Column({ length: 500 })
|
||||||
|
value!: string
|
||||||
|
|
||||||
|
@Column({ comment: '令牌过期时间' })
|
||||||
|
expired_at!: Date
|
||||||
|
|
||||||
|
@CreateDateColumn({ comment: '令牌创建时间' })
|
||||||
|
created_at!: Date
|
||||||
|
|
||||||
|
@OneToOne(() => RefreshTokenEntity, refreshToken => refreshToken.accessToken, {
|
||||||
|
cascade: true,
|
||||||
|
})
|
||||||
|
refreshToken!: RefreshTokenEntity
|
||||||
|
|
||||||
|
@ManyToOne(() => UserEntity, user => user.accessTokens, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
user!: UserEntity
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
OneToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm'
|
||||||
|
|
||||||
|
import { AccessTokenEntity } from './access-token.entity'
|
||||||
|
|
||||||
|
@Entity('user_refresh_tokens')
|
||||||
|
export class RefreshTokenEntity extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string
|
||||||
|
|
||||||
|
@Column({ length: 500 })
|
||||||
|
value!: string
|
||||||
|
|
||||||
|
@Column({ comment: '令牌过期时间' })
|
||||||
|
expired_at!: Date
|
||||||
|
|
||||||
|
@CreateDateColumn({ comment: '令牌创建时间' })
|
||||||
|
created_at!: Date
|
||||||
|
|
||||||
|
@OneToOne(() => AccessTokenEntity, accessToken => accessToken.refreshToken, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
accessToken!: AccessTokenEntity
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue