├── .commitlintrc.js ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.js ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .versionrc.js ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── README.md ├── deploy ├── docker-build.sh ├── docker-run.sh ├── ecosystem.config.js ├── handleGiteeJenkins.mjs ├── handleSyncCodeup.mjs └── node-pm2.sh ├── package.json ├── pnpm-lock.yaml ├── src ├── app │ ├── app.middleware.ts │ ├── auth │ │ ├── authJwt.ts │ │ ├── verifyEnv.ts │ │ └── verifyUserAuth.ts │ ├── handler │ │ ├── error-handle.ts │ │ └── success-handle.ts │ └── verify.middleware.ts ├── config │ ├── mysql │ │ └── index.ts │ ├── redis │ │ ├── index.ts │ │ └── pub.ts │ └── websocket │ │ ├── constant.ts │ │ ├── index.ts │ │ ├── redis.controller.ts │ │ └── ws.ts ├── constant.ts ├── controller │ ├── article.controller.ts │ ├── articleTag.controller.ts │ ├── auth.controller.ts │ ├── backend.controller.ts │ ├── blacklist.controller.ts │ ├── buryingPoint.controller.ts │ ├── comment.controller.ts │ ├── emailUser.controller.ts │ ├── frontend.controller.ts │ ├── githubUser.controller.ts │ ├── init.controller.ts │ ├── interaction.controller.ts │ ├── interactionStatis.controller.ts │ ├── link.controller.ts │ ├── log.controller.ts │ ├── monit.controller.ts │ ├── music.controller.ts │ ├── other.controller.ts │ ├── position.controller.ts │ ├── qiniuData.controller.ts │ ├── qqUser.controller.ts │ ├── redis.controller.ts │ ├── role.controller.ts │ ├── roleAuth.controller.ts │ ├── schedule.controller.ts │ ├── star.controller.ts │ ├── tag.controller.ts │ ├── theme.controller.ts │ ├── thirdUser.controller.ts │ ├── type.controller.ts │ ├── user.controller.ts │ ├── visitorLog.controller.ts │ └── works.controller.ts ├── index.ts ├── init │ ├── alias.ts │ ├── initData.ts │ ├── initDb.ts │ ├── initFile.ts │ └── monit │ │ ├── index.ts │ │ ├── monitBackupsDb.ts │ │ ├── monitDeleteLog.ts │ │ ├── monitMemory.ts │ │ ├── monitProcess.ts │ │ └── monitQiniuCDN.ts ├── interface.ts ├── middleware │ ├── article.middleware.ts │ ├── articleTag.middleware.ts │ ├── auth.middleware.ts │ ├── comment.middleware.ts │ ├── csrf.middleware.ts │ ├── emailUser.middleware.ts │ ├── link.middleware.ts │ ├── log.middleware.ts │ ├── music.middleware.ts │ ├── qqUser.middleware.ts │ ├── role.middleware.ts │ ├── star.middleware.ts │ ├── tag.middleware.ts │ ├── theme.middleware.ts │ ├── type.middleware.ts │ ├── user.middleware.ts │ ├── utils.middleware.ts │ ├── visitorLog.middleware.ts │ └── works.middleware.ts ├── model │ ├── article.model.ts │ ├── articleTag.model.ts │ ├── articleType.model.ts │ ├── auth.model.ts │ ├── backend.model.ts │ ├── blacklist.model.ts │ ├── buryingPoint.model.ts │ ├── comment.model.ts │ ├── customError.model.ts │ ├── dayData.model.ts │ ├── emailUser.model.ts │ ├── frontend.model.ts │ ├── githubUser.model.ts │ ├── interaction.model.ts │ ├── interactionStatis.model.ts │ ├── link.model.ts │ ├── log.model.ts │ ├── monit.model.ts │ ├── music.model.ts │ ├── qiniuData.model.ts │ ├── qqUser.model.ts │ ├── relation.ts │ ├── role.model.ts │ ├── roleAuth.model.ts │ ├── star.model.ts │ ├── tag.model.ts │ ├── theme.model.ts │ ├── thirdUser.model.ts │ ├── type.model.ts │ ├── user.model.ts │ ├── userArticle.model.ts │ ├── userRole.model.ts │ ├── visitorLog.model.ts │ └── works.model.ts ├── public │ └── index2.html ├── router │ ├── article.router.ts │ ├── articleTag.router.ts │ ├── auth.router.ts │ ├── backend.router.ts │ ├── blacklist.router.ts │ ├── buryingPoint.router.ts │ ├── comment.router.ts │ ├── csrf.router.ts │ ├── emailUser.router.ts │ ├── frontend.router.ts │ ├── githubUser.router.ts │ ├── index.ts │ ├── init.router.ts │ ├── interaction.router.ts │ ├── interactionStatis.router.ts │ ├── link.router.ts │ ├── log.router.ts │ ├── monit.router.ts │ ├── music.router.ts │ ├── other.router.ts │ ├── position.router.ts │ ├── qiniuData.router.ts │ ├── qqUser.router.ts │ ├── role.router.ts │ ├── roleAuth.router.ts │ ├── schedule.route.ts │ ├── star.router.ts │ ├── statis.router.ts │ ├── tag.router.ts │ ├── theme.router.ts │ ├── type.router.ts │ ├── user.router.ts │ ├── visitorLog.router.ts │ └── works.router.ts ├── secret │ └── secretTemp.ts ├── service │ ├── article.service.ts │ ├── articleTag.service.ts │ ├── articleType.service.ts │ ├── auth.service.ts │ ├── backend.service.ts │ ├── blacklist.service.ts │ ├── buryingPoint.service.ts │ ├── comment.service.ts │ ├── emailUser.service.ts │ ├── frontend.service.ts │ ├── githubUser.service.ts │ ├── interaction.service.ts │ ├── interactionStatis.service.ts │ ├── link.service.ts │ ├── log.service.ts │ ├── monit.service.ts │ ├── music.service.ts │ ├── position.service.ts │ ├── qiniuData.service.ts │ ├── qqUser.service.ts │ ├── role.service.ts │ ├── roleAuth.service.ts │ ├── star.service.ts │ ├── tag.service.ts │ ├── theme.service.ts │ ├── thirdUser.service.ts │ ├── type.service.ts │ ├── user.service.ts │ ├── visitorLog.service.ts │ └── works.service.ts ├── setup.ts └── utils │ ├── backup-qiniu.ts │ ├── chalkTip.ts │ ├── clearCache.ts │ ├── index.ts │ ├── qiniu.ts │ ├── request.ts │ └── tmp.ts ├── tsconfig.json └── tsconfig.prod.json /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | console.log( 2 | '\x1B[0;37;44m INFO \x1B[0m', 3 | '\x1B[0;;34m ' + 4 | `读取了: ${__filename.slice(__dirname.length + 1)}` + 5 | ' \x1B[0m' 6 | ); 7 | 8 | module.exports = { 9 | extends: ['@commitlint/config-conventional'], 10 | }; 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | node_modules 3 | .DS_Store 4 | .eslintcache 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | # 控制编辑器代码规范,如按下回车的时候,缩进几个空格等等。 3 | # 最顶层的EditorConfig文件 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_style = space # 缩进样式为空格,也可以设置成:tab 9 | indent_size = 2 # 一个缩进2个空格 10 | # tab_width默认为indent_size的值,通常不需要指定。 11 | end_of_line = lf # 结尾符,也可以设置成:crlf 12 | insert_final_newline = true # 设置为true以确保文件在保存时以换行符结尾,设置为false以确保文件不以换行符号结尾。 13 | trim_trailing_whitespace = true # 设置为true以删除换行符之前的任何空白字符,设置为false以确保不会。 14 | 15 | [*.md] 16 | insert_final_newline = false 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | pnpm-lock.yaml 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # !!!!!!这里面的配置会影响git的行为,会导致你本地的文件和提交的文件不一致!!!!!!!! 2 | 3 | # * text eol=crlf优点是会在git add .的时候,不会修改本地文件,但是会修改提交到暂存区的文件 4 | # 会将提交到暂存区的所有文件的结尾符换成crlf。 5 | # * text eol=crlf缺点是会在git add .的时候,将提交到暂存区的图片的lf也换成crlf,但是不会更改工作区的图片, 6 | # 这样就会有问题,因为你本地的图片没改,但是你提交的图片却改了,可能会导致这个图片不显示(可以看提交记录, 7 | # 图片会不显示),然后你提交到代码仓库后,代码仓库的图片也是有问题的,就会有毛病 8 | # * text eol=lf,即设置成和.editorconfig的end_of_line一样的值,就会不管是win还是mac,都统一 9 | # 使用eol设置的换行符,就不会有换行符的问题了(不会受autocrlf和safecrlf的影响) 10 | # * text eol=lf 11 | 12 | # 设置* text=auto的话,能兼容win和mac,绝大部分情况下没问题,但是如果git配置了safecrlf = true,就会导致问题 13 | # 因为safecrlf = true不允许提交包含混合的换行符文件,建议设置safecrlf = false,然后使用* text=auto 14 | # PS: 很多开源项目(react、angular、webpack...)都是使用* text=auto。 15 | * text=auto 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | /src/secret/secret.ts 5 | src/upload/**/* 6 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm exec lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | console.log( 2 | '\x1B[0;37;44m INFO \x1B[0m', 3 | '\x1B[0;;34m ' + 4 | `读取了: ${__filename.slice(__dirname.length + 1)}` + 5 | ' \x1B[0m' 6 | ); 7 | 8 | module.exports = { 9 | // 只对这几种格式的代码进行prettier美化 10 | '*.{js,ts}': ['prettier --write'], 11 | }; 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # 将所有依赖提升到最外层 2 | shamefully-hoist=true 3 | # 设置@billd*包使用的镜像 4 | @billd:registry=https://registry.hsslive.cn/ 5 | # 设置淘宝镜像 6 | registry=https://registry.npmmirror.com/ 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | pnpm-lock.yaml 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | console.log( 2 | '\x1B[0;37;44m INFO \x1B[0m', 3 | '\x1B[0;;34m ' + 4 | `读取了: ${__filename.slice(__dirname.length + 1)}` + 5 | ' \x1B[0m' 6 | ); 7 | 8 | module.exports = { 9 | bracketSpacing: true, // 默认为true。即要求:{ foo: bar };可改为false,即要求{foo: bar} 10 | singleQuote: true, // 默认为false。即要求:const a = "1";可改为true,即要求const a = '1' 11 | semi: true, // 默认值true,即要求在所有代码语句的末尾添加分号;可改为false,即要求仅在可能导致 ASI 失败的行的开头添加分号。 12 | singleAttributePerLine: true, // 默认false。即在HTML、Vue和JSX中不要每行强制执行单个属性;可改为true,即要求每行强制执行单个属性。 13 | 14 | /** 15 | * jsxBracketSameLine 16 | * 注意是多行,如果是类似这种:1,基本不会触发换行,因此也就不会触发这个bracketSameLine 17 | * 但是如果是类似这种:1,它有多个属性,或者说他的一个属性值很长,可能会导致换行, 18 | * 如果换行了,那么就会触发bracketSameLine,将最后的>单独放在一行或者最后一行的末尾 19 | */ 20 | bracketSameLine: false, // 默认为false。即将多行HTML(HTML、JSX、Vue、Angular)元素的 > 单独放在下一行;可改为true,即将 > 放在最后一行的末尾。 21 | // jsxBracketSameLine: false, // 此选项已在v2.4.0中弃用,使用bracketSameLine替换,https://prettier.io/blog/2021/09/09/2.4.0.html 22 | 23 | /** 24 | * trailingComma 25 | * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Trailing_commas 26 | * 尾后逗号 (有时叫做“终止逗号”)在向 JavaScript 代码添加元素、参数、属性时十分有用。 27 | * 如果你想要添加新的属性,并且上一行已经使用了尾后逗号,你可以仅仅添加新的一行,而不需要修改上一行。 28 | * 这使得版本控制的代码比较(diff)更加清晰,代码编辑过程中遇到的麻烦更少。 29 | */ 30 | trailingComma: 'es5', // 默认值在v2.0.0中none更改为es5。即在ES5中有效的尾随逗号(对象、数组等)。可选:"none":没有尾随逗号;"all":尽可能尾随逗号 31 | 32 | /** 33 | * printWidth 34 | * 如果设置了printWidth值,则以设置的printWidth值为准 35 | * 如果没有设置printWidth值,且.editorconfig文件有设置max_line_length值,则使用.editorconfig文件的max_line_length 36 | */ 37 | printWidth: 80, // 默认80,printWidth不是硬性的允许行长度上限,不要试图将 printWidth 当作 ESLint 的max-len 来使用——它们不一样 38 | 39 | /** 40 | * tabWidth 41 | * 如果设置了tabWidth值,则以设置的tabWidth值为准 42 | * 如果没有设置tabWidth值,且.editorconfig文件有设置indent_size或者tab_width值,则使用.editorconfig文件的indent_size或者tab_width 43 | */ 44 | tabWidth: 2, // 指定每个缩进级别的空格数。 45 | // parser: 'babel', // 指定要使用的解析器。Prettier 会自动从输入文件路径推断解析器,因此您不必更改此设置。 46 | }; 47 | -------------------------------------------------------------------------------- /.versionrc.js: -------------------------------------------------------------------------------- 1 | console.log( 2 | '\x1B[0;37;44m INFO \x1B[0m', 3 | '\x1B[0;;34m ' + 4 | `读取了: ${__filename.slice(__dirname.length + 1)}` + 5 | ' \x1B[0m' 6 | ); 7 | 8 | module.exports = { 9 | types: [ 10 | { type: 'feat', section: '✨ 新特性', hidden: false }, 11 | { type: 'fix', section: '🐛 Bug修复', hidden: false }, 12 | { type: 'docs', section: '📝 文档更改', hidden: false }, 13 | { type: 'style', section: '🎨 样式更改', hidden: false }, 14 | { type: 'refactor', section: '🔨 代码重构', hidden: false }, 15 | { 16 | type: 'perf', 17 | section: '⚡️ 优化性能', 18 | hidden: false, 19 | }, 20 | { type: 'test', section: '🧪 测试', hidden: false }, 21 | { type: 'build', section: '🚀 构建', hidden: false }, 22 | { type: 'ci', section: '👷 CI', hidden: false }, 23 | { type: 'chore', section: '🏗 其他', hidden: false }, 24 | { type: 'revert', section: '⏪ 回退', hidden: false }, 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // 指定行尾序列为\n(LF)或者\r\n(CRLF)或者auto 3 | // "files.eol": "\n", 4 | 5 | // 在保存时格式化 6 | "editor.formatOnSave": true, 7 | 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": "explicit", 10 | "source.organizeImports": "explicit" 11 | }, 12 | // "eslint.autoFixOnSave": true, // 废弃,使用editor.codeActionsOnSave替代 13 | 14 | // Path Autocomplete,这个插件能够支持路径补全,默认vsc默认的路径提示可能不会提示一些css或者jpg等资源,用这个插件可以完善vscode的路径提示 15 | // 主要作用是你输入@、components、layouts的时候,会有路径提示 16 | "path-autocomplete.pathMappings": { 17 | "@": "${folder}/src", 18 | "components": "${folder}/src/components", 19 | "layouts": "${folder}/src/layouts" 20 | }, 21 | 22 | // 别名路径跳转,这个插件可以完善vscode的跳转 23 | "alias-skip.allowedsuffix": [ 24 | "css", 25 | "less", 26 | "sass", 27 | "scss", 28 | "png", 29 | "jpg", 30 | "jpeg", 31 | "webp", 32 | "gif", 33 | "svg", 34 | "js", 35 | "jsx", 36 | "ts", 37 | "tsx", 38 | "vue" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.19.0 2 | 3 | ARG BILLD_JOBNAME 4 | ARG BILLD_ENV 5 | ARG BILLD_WORKSPACE 6 | ARG BILLD_PORT 7 | ARG BILLD_TAG 8 | ARG BILLD_PUBLICDIR 9 | 10 | # https://medium.com/@yangcar/arg-in-dockerfile-cmd-db22c2bc7b62 11 | # https://stackoverflow.com/questions/35560894/is-docker-arg-allowed-within-cmd-instruction/35562189#35562189 12 | ENV BILLD_JOBNAME ${BILLD_JOBNAME} 13 | ENV BILLD_ENV ${BILLD_ENV} 14 | ENV BILLD_WORKSPACE ${BILLD_WORKSPACE} 15 | ENV BILLD_PORT ${BILLD_PORT} 16 | ENV BILLD_TAG ${BILLD_TAG} 17 | ENV BILLD_PUBLICDIR ${BILLD_PUBLICDIR} 18 | 19 | ENV NODE_APP_RELEASE_PROJECT_NAME ${BILLD_JOBNAME} 20 | ENV NODE_APP_RELEASE_PROJECT_ENV ${BILLD_ENV} 21 | ENV NODE_APP_RELEASE_PROJECT_PORT ${BILLD_PORT} 22 | 23 | # https://github.com/pnpm/pnpm/issues/4495 24 | ENV PNPM_HOME="/pnpm/share/pnpm" 25 | ENV PATH="${PATH}:${PNPM_HOME}" 26 | 27 | EXPOSE ${BILLD_PORT} 28 | 29 | RUN mkdir -p ${BILLD_PUBLICDIR}/${BILLD_JOBNAME}/${BILLD_ENV} 30 | 31 | RUN cd ${BILLD_PUBLICDIR}/${BILLD_JOBNAME}/${BILLD_ENV} 32 | 33 | WORKDIR ${BILLD_PUBLICDIR}/${BILLD_JOBNAME}/${BILLD_ENV} 34 | 35 | COPY . . 36 | 37 | RUN echo 设置npm淘宝镜像: 38 | RUN npm config set registry https://registry.npmmirror.com/ 39 | 40 | RUN echo 开始全局安装pnpm: 41 | RUN npm i pnpm@9.1.3 -g 42 | 43 | RUN echo 设置pnpm淘宝镜像: 44 | RUN pnpm config set registry https://registry.npmmirror.com/ 45 | RUN pnpm config set @billd:registry https://registry.hsslive.cn/ 46 | 47 | # RUN echo 开始全局安装pm2: 48 | # RUN pnpm i pm2@5.4.2 -g 49 | 50 | RUN echo node版本: $(node -v) 51 | RUN echo npm版本: $(npm -v) 52 | RUN echo pnpm版本: $(pnpm -v) 53 | # RUN echo pm2版本: $(pm2 -v) 54 | RUN echo git版本: $(git --version) 55 | 56 | RUN echo 当前路径: $(pwd) 57 | RUN echo 当前文件: $(ls -al) 58 | 59 | RUN echo JOBNAME: ${BILLD_JOBNAME} 60 | RUN echo ENV: ${BILLD_ENV} 61 | RUN echo WORKSPACE: ${BILLD_WORKSPACE} 62 | RUN echo TAG: ${BILLD_TAG} 63 | RUN echo PUBLICDIR: ${BILLD_PUBLICDIR} 64 | 65 | RUN echo 开始安装依赖: 66 | RUN pnpm i 67 | 68 | RUN echo 开始打包: 69 | RUN npm run build 70 | 71 | # VOLUME [ ${BILLD_PUBLICDIR}/ ] 72 | 73 | # pm2环境变量管理:https://pm2.io/docs/runtime/best-practices/environment-variables/ 74 | # CMD pm2-runtime start './dist/index.js' -i 1 --name ${BILLD_JOBNAME}-${BILLD_ENV}-${BILLD_PORT} 75 | CMD node './dist/index.js' --name ${BILLD_JOBNAME}-${BILLD_ENV}-${BILLD_PORT} 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目简介 2 | 3 | 基于 Typescript + Node + Koa2 搭建的博客后端 4 | 5 | # 线上地址 6 | 7 | [https://admin.hsslive.cn](https://admin.hsslive.cn) 8 | 9 | # 接口文档 10 | 11 | [apifox](https://apifox.com/apidoc/shared-e73bb55d-ccdd-40a2-ab41-961e1ffcfa10),绝大部分接口都写了,可能还有一些接口没写~ 12 | 13 | # 安装依赖 14 | 15 | ```bash 16 | pnpm install 17 | ``` 18 | 19 | > 更新 billd 依赖: 20 | 21 | ```bash 22 | pnpm i billd-utils@latest billd-html-webpack-plugin@latest 23 | ``` 24 | 25 | # 调试 26 | 27 | > 注意:项目启动后,会默认在 src/config/secret.ts 生成秘钥文件,请在该文件里面填写本项目所需的秘钥信息~ 28 | 29 | ```bash 30 | # npm run dev,运行在3300端口 31 | npm run dev 32 | # 或者npm run dev:beta,运行在3300端口 33 | npm run dev:beta 34 | # 或者npm run dev:prod,运行在3200端口 35 | npm run dev:prod 36 | ``` 37 | 38 | # 部署(Docker) 39 | 40 | 1.执行 docker-build.sh: 41 | 42 | ```bash 43 | sh ./deploy/docker-build.sh vue3-blog-server beta /Users/huangshuisheng/Desktop/hss/galaxy-s10 3300 v0.0.1 44 | ``` 45 | 46 | 2.执行 docker-run.sh: 47 | 48 | ```bash 49 | sh ./deploy/docker-run.sh vue3-blog-server beta /Users/huangshuisheng/Desktop/hss/galaxy-s10/vue3-blog-server 3300 v0.0.1 50 | ``` 51 | 52 | # 服务器环境 53 | 54 | > 注意:这是服务器的环境信息,并非实际运行时的环境信息(因为使用了 Docker) 55 | 56 | - 操作系统:CentOS Linux release 8.2.2004 57 | - nginx 版本:1.21.4 58 | - node 版本:14.19.0 59 | - redis 版本:5.0.3 60 | - mysql 版本:8.0.26 61 | - pm2 版本:5.1.2 62 | -------------------------------------------------------------------------------- /deploy/docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ### 3 | # Author: shuisheng 4 | # Date: 2022-08-09 12:55:47 5 | # Description: https://github.com/galaxy-s10/sh/ 6 | # Email: 2274751790@qq.com 7 | # FilePath: /vue3-blog-server/deploy/docker-build.sh 8 | # Github: https://github.com/galaxy-s10 9 | # LastEditors: shuisheng 10 | # LastEditTime: 2024-05-06 14:27:03 11 | ### 12 | 13 | # 生成头部文件快捷键: ctrl+cmd+i 14 | 15 | # docker项目, 一般流程是在jenkins里面执行项目里的docker-build.sh进行构建, 16 | # 构建完成后会连接ssh, 执行/node/sh/docker.sh, 并且执行/node/xxx/docker-run.sh 17 | # 最后, 服务器的/node/sh/docker.sh会执行清除buff/cache操作 18 | 19 | # 注意: JOBNAME=$1, 这个等号左右不能有空格! 20 | JOBNAME=$1 #约定$1为任务名 21 | ENV=$2 #约定$2为环境 22 | WORKSPACE=$3 #约定$3为Jenkins工作区 23 | PORT=$4 #约定$4为端口号 24 | TAG=$5 #约定$5为git标签 25 | PUBLICDIR=/node #约定公共目录为/node 26 | 27 | # echo 删除node_modules: 28 | # rm -rf node_modules 29 | 30 | # echo 查看node版本: 31 | # node -v 32 | 33 | # echo 查看npm版本: 34 | # npm -v 35 | 36 | # echo 设置npm淘宝镜像: 37 | # npm config set registry https://registry.npmmirror.com/ 38 | 39 | # echo 查看当前npm镜像: 40 | # npm get registry 41 | 42 | # if ! type pnpm >/dev/null 2>&1; then 43 | # echo 'pnpm未安装,先全局安装pnpm' 44 | # npm i pnpm -g 45 | # else 46 | # echo 'pnpm已安装' 47 | # fi 48 | 49 | # echo 查看pnpm版本: 50 | # pnpm -v 51 | 52 | # echo 设置pnpm淘宝镜像: 53 | # pnpm config set registry https://registry.npmmirror.com/ 54 | # pnpm config set @billd:registry https://registry.hsslive.cn/ 55 | 56 | # echo 查看当前pnpm镜像: 57 | # pnpm config get registry 58 | # pnpm config get @billd:registry 59 | 60 | # # 注意:要先进入项目所在的目录,然后再执行pm2命令!!! 61 | # # 否则的话约等于在其他目录执行npm run dev,如果所在的目录没有package.json文件就会报错! 62 | 63 | # if [ $ENV != 'null' ]; then 64 | # echo 当前环境:$ENV 65 | # else 66 | # echo 当前环境是null 67 | # fi 68 | 69 | # echo 开始安装依赖: 70 | # pnpm install 71 | 72 | # echo 开始打包: 73 | # pnpm run build 74 | 75 | # 本机写死测试: 76 | # sh ./deploy/docker-build.sh vue3-blog-server beta /Users/huangshuisheng/Desktop/hss/galaxy-s10 3300 v0.0.1 77 | 78 | # 服务器写死测试: 79 | # sh ./deploy/docker-build.sh vue3-blog-server beta /var/lib/jenkins/workspace/vue3-blog-server 3300 v0.0.1 80 | # sh ./deploy/docker-build.sh vue3-blog-server prod /var/lib/jenkins/workspace/vue3-blog-server 3200 v0.0.1 81 | 82 | echo 当前目录 83 | echo $(pwd) 84 | 85 | echo 执行docker-build.sh 86 | 87 | DOCKER_BUILDKIT=0 docker build -t $JOBNAME-$ENV-$PORT . \ 88 | --build-arg BILLD_JOBNAME=$JOBNAME \ 89 | --build-arg BILLD_ENV=$ENV \ 90 | --build-arg BILLD_WORKSPACE=$WORKSPACE \ 91 | --build-arg BILLD_PORT=$PORT \ 92 | --build-arg BILLD_TAG=$TAG \ 93 | --build-arg BILLD_PUBLICDIR=$PUBLICDIR 94 | -------------------------------------------------------------------------------- /deploy/docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ### 3 | # Author: shuisheng 4 | # Date: 2023-03-01 22:42:02 5 | # Description: https://github.com/galaxy-s10/sh/ 6 | # Email: 2274751790@qq.com 7 | # FilePath: /vue3-blog-server/deploy/docker-run.sh 8 | # Github: https://github.com/galaxy-s10 9 | # LastEditors: shuisheng 10 | # LastEditTime: 2025-01-27 23:21:28 11 | ### 12 | 13 | # 生成头部文件快捷键: ctrl+cmd+i 14 | 15 | # docker项目, 一般流程是在jenkins里面执行项目里的docker-build.sh进行构建, 16 | # 构建完成后会连接ssh, 执行/node/sh/docker.sh, 并且执行/node/xxx/docker-run.sh 17 | # 最后, 服务器的/node/sh/docker.sh会执行清除buff/cache操作 18 | 19 | # 注意: JOBNAME=$1, 这个等号左右不能有空格! 20 | JOBNAME=$1 #约定$1为任务名 21 | ENV=$2 #约定$2为环境 22 | WORKSPACE=$3 #约定$3为Jenkins工作区 23 | PORT=$4 #约定$4为端口号 24 | TAG=$5 #约定$5为git标签 25 | PUBLICDIR=/node #约定公共目录为/node 26 | 27 | echo JOBNAME: $JOBNAME 28 | echo ENV: $ENV 29 | echo WORKSPACE: $WORKSPACE 30 | echo PORT: $PORT 31 | echo TAG: $TAG 32 | 33 | echo 停掉旧的容器$JOBNAME-$ENV-$PORT: 34 | docker stop $JOBNAME-$ENV-$PORT 35 | 36 | echo 删掉旧的容器$JOBNAME-$ENV-$PORT: 37 | docker rm $JOBNAME-$ENV-$PORT 38 | 39 | echo 启动新的容器$JOBNAME-$ENV-$PORT: 40 | 41 | # 本机写死测试: 42 | # sh ./deploy/docker-run.sh vue3-blog-server beta /Users/huangshuisheng/Desktop/hss/galaxy-s10/vue3-blog-server 3300 v0.0.1 43 | 44 | # 服务器写死测试: 45 | # sh ./deploy/docker-run.sh vue3-blog-server beta /var/lib/jenkins/workspace/vue3-blog-server 3300 v0.0.1 46 | 47 | echo 执行docker-run.sh 48 | 49 | # 不使用volume 50 | # docker run --name $JOBNAME-$ENV-$PORT -d -p $PORT:$PORT $JOBNAME-$ENV-$PORT 51 | # 使用volume 52 | docker run --name $JOBNAME-$ENV-$PORT -d \ 53 | -p $PORT:$PORT \ 54 | -v $PUBLICDIR/backup/:/$PUBLICDIR/backup/ \ 55 | $JOBNAME-$ENV-$PORT 56 | 57 | # docker run --name $JOBNAME-$ENV-$PORT -d \ 58 | # -p $PORT:$PORT \ 59 | # -v $PUBLICDIR/$JOBNAME/$ENV/:$PUBLICDIR/$JOBNAME/$ENV/ \ 60 | # -v $PUBLICDIR/backup/:/node/backup/ \ 61 | # $JOBNAME-$ENV-$PORT 62 | -------------------------------------------------------------------------------- /deploy/ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'billd-blog-server', 5 | exec_mode: 'fork', // cluster,fork 6 | instances: '1', 7 | script: './dist/index.js', 8 | args: `start`, 9 | env: { 10 | NODE_ENV: 'production', 11 | NODE_APP_RELEASE_PROJECT_ENV: 'prod', 12 | NODE_APP_RELEASE_PROJECT_PORT: 3100, 13 | NODE_APP_RELEASE_PROJECT_NAME: 'vue3-blog-server', 14 | }, 15 | }, 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /deploy/handleGiteeJenkins.mjs: -------------------------------------------------------------------------------- 1 | // WARN 该文件只是方便我将当前项目复制一份到我电脑的另一个位置(gitee私有仓库的位置),其他人不需要管这个文件~ 2 | 3 | import { execSync } from 'node:child_process'; 4 | import fs from 'node:fs'; 5 | import path from 'node:path'; 6 | import trash from 'trash'; 7 | 8 | const allFile = []; 9 | const ignore = ['.DS_Store', '.git', '.gitignore', 'node_modules', 'dist']; 10 | const localDir = 11 | '/Users/huangshuisheng/Desktop/hss/galaxy-s10/vue3-blog-server'; 12 | const giteeDir = '/Users/huangshuisheng/Desktop/hss/jenkins/vue3-blog-server'; 13 | 14 | const dir = fs.readdirSync(localDir).filter((item) => { 15 | if (ignore.includes(item)) { 16 | return false; 17 | } 18 | return true; 19 | }); 20 | 21 | function findFile(inputDir) { 22 | for (let i = 0; i < inputDir.length; i += 1) { 23 | const file = inputDir[i]; 24 | const filePath = `${localDir}/${file}`; 25 | const stat = fs.statSync(filePath); 26 | const isDir = stat.isDirectory(); 27 | if (!isDir) { 28 | allFile.push(filePath); 29 | } else { 30 | findFile(fs.readdirSync(filePath).map((key) => `${file}/${key}`)); 31 | } 32 | } 33 | } 34 | 35 | function putFile() { 36 | for (let i = 0; i < allFile.length; i += 1) { 37 | const file = allFile[i]; 38 | const arr = []; 39 | const githubFile = file.replace(localDir, ''); 40 | const githubFileArr = githubFile.split('/').filter((item) => item !== ''); 41 | githubFileArr.forEach((item) => { 42 | if (arr.length) { 43 | arr.push(path.resolve(arr[arr.length - 1], item)); 44 | } else { 45 | arr.push(path.resolve(giteeDir, item)); 46 | } 47 | }); 48 | arr.forEach((item, index) => { 49 | // 数组的最后一个一定是文件,因此不需要判断它是不是目录 50 | if (index !== arr.length - 1) { 51 | const flag = fs.existsSync(item); 52 | // eslint-disable-next-line 53 | !flag && fs.mkdirSync(item); 54 | } 55 | }); 56 | fs.copyFileSync( 57 | file, 58 | path.join(giteeDir, './', file.replace(localDir, '')) 59 | ); 60 | } 61 | } 62 | 63 | async function clearOld() { 64 | const giteeDirAllFile = fs.readdirSync(giteeDir); 65 | const queue = []; 66 | giteeDirAllFile.forEach((url) => { 67 | const fullurl = `${giteeDir}/${url}`; 68 | if (!['node_modules', 'src', '.git'].includes(url)) { 69 | queue.push(trash(fullurl)); 70 | } 71 | }); 72 | await Promise.all(queue); 73 | const queue1 = []; 74 | const giteeDirSrcAllFile = fs.readdirSync(path.resolve(giteeDir, './src')); 75 | giteeDirSrcAllFile.forEach((url) => { 76 | const fullurl = `${giteeDir}/${url}`; 77 | if (!['secret'].includes(url)) { 78 | queue1.push(trash(fullurl)); 79 | } 80 | }); 81 | } 82 | 83 | if (process.cwd().indexOf('jenkins') !== -1) { 84 | console.log('当前目录错误'); 85 | } else { 86 | clearOld().then(() => { 87 | findFile(dir); 88 | putFile(); 89 | const gitignoreTxt = 'node_modules\n.DS_Store\ndist\nsrc/upload/**/*\n'; 90 | fs.writeFileSync(path.resolve(giteeDir, './.gitignore'), gitignoreTxt); 91 | execSync(`pnpm i`, { cwd: giteeDir }); 92 | execSync(`git add .`, { cwd: giteeDir }); 93 | execSync(`git commit -m 'feat: ${new Date().toLocaleString()}'`, { 94 | cwd: giteeDir, 95 | }); 96 | execSync(`git push`, { cwd: giteeDir }); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /deploy/handleSyncCodeup.mjs: -------------------------------------------------------------------------------- 1 | // WARN 该文件只是方便我将当前项目复制一份到我电脑的另一个位置(gitee私有仓库的位置),其他人不需要管这个文件~ 2 | 3 | import { execSync } from 'node:child_process'; 4 | import fs from 'node:fs'; 5 | import path from 'node:path'; 6 | import trash from 'trash'; 7 | 8 | const allFile = []; 9 | const ignore = ['.DS_Store', '.git', '.gitignore', 'node_modules', 'dist']; 10 | const localDir = 11 | '/Users/huangshuisheng/Desktop/hss/galaxy-s10/vue3-blog-server'; 12 | const targetDir = '/Users/huangshuisheng/Desktop/hss/codeup/billd-blog-server'; 13 | 14 | const dir = fs.readdirSync(localDir).filter((item) => { 15 | if (ignore.includes(item)) { 16 | return false; 17 | } 18 | return true; 19 | }); 20 | 21 | function findFile(inputDir) { 22 | for (let i = 0; i < inputDir.length; i += 1) { 23 | const file = inputDir[i]; 24 | const filePath = `${localDir}/${file}`; 25 | const stat = fs.statSync(filePath); 26 | const isDir = stat.isDirectory(); 27 | if (!isDir) { 28 | allFile.push(filePath); 29 | } else { 30 | findFile(fs.readdirSync(filePath).map((key) => `${file}/${key}`)); 31 | } 32 | } 33 | } 34 | 35 | function putFile() { 36 | for (let i = 0; i < allFile.length; i += 1) { 37 | const file = allFile[i]; 38 | const arr = []; 39 | const githubFile = file.replace(localDir, ''); 40 | const githubFileArr = githubFile.split('/').filter((item) => item !== ''); 41 | githubFileArr.forEach((item) => { 42 | if (arr.length) { 43 | arr.push(path.resolve(arr[arr.length - 1], item)); 44 | } else { 45 | arr.push(path.resolve(targetDir, item)); 46 | } 47 | }); 48 | arr.forEach((item, index) => { 49 | // 数组的最后一个一定是文件,因此不需要判断它是不是目录 50 | if (index !== arr.length - 1) { 51 | const flag = fs.existsSync(item); 52 | // eslint-disable-next-line 53 | !flag && fs.mkdirSync(item); 54 | } 55 | }); 56 | fs.copyFileSync( 57 | file, 58 | path.join(targetDir, './', file.replace(localDir, '')) 59 | ); 60 | } 61 | } 62 | 63 | async function clearOld() { 64 | const targetDirAllFile = fs.readdirSync(targetDir); 65 | const queue = []; 66 | targetDirAllFile.forEach((url) => { 67 | const fullurl = `${targetDir}/${url}`; 68 | if (!['node_modules', 'src', '.git'].includes(url)) { 69 | queue.push(trash(fullurl)); 70 | } 71 | }); 72 | await Promise.all(queue); 73 | const queue1 = []; 74 | const srcDir = path.resolve(targetDir, './src'); 75 | const targetDirSrcAllFile = fs.readdirSync(srcDir); 76 | targetDirSrcAllFile.forEach((url) => { 77 | const fullurl = `${srcDir}/${url}`; 78 | if (!['secret'].includes(url)) { 79 | queue1.push(trash(fullurl)); 80 | } 81 | }); 82 | await Promise.all(queue1); 83 | } 84 | 85 | if (process.cwd().indexOf('codeup') !== -1) { 86 | console.log('当前目录错误'); 87 | } else { 88 | clearOld().then(() => { 89 | findFile(dir); 90 | putFile(); 91 | const gitignoreArr = ['node_modules', 'dist', 'upload', '.DS_Store']; 92 | const gitignoreTxt = gitignoreArr.join('\n'); 93 | fs.writeFileSync(path.resolve(targetDir, './.gitignore'), gitignoreTxt); 94 | execSync(`pnpm i`, { cwd: targetDir }); 95 | execSync(`git rm -r --cached .`, { cwd: targetDir }); 96 | execSync(`git add .`, { cwd: targetDir }); 97 | execSync(`git commit -m 'feat: ${new Date().toLocaleString()}'`, { 98 | cwd: targetDir, 99 | }); 100 | execSync(`git push`, { cwd: targetDir }); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /deploy/node-pm2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ### 3 | # Author: shuisheng 4 | # Date: 2024-12-16 23:22:50 5 | # Description: 6 | # Email: 2274751790@qq.com 7 | # FilePath: /vue3-blog-server/deploy/node-pm2.sh 8 | # Github: https://github.com/galaxy-s10 9 | # LastEditors: shuisheng 10 | # LastEditTime: 2025-03-18 20:01:21 11 | ### 12 | 13 | # 云效读取不了node,会报错:node: command not found,加上这个环境变量。 14 | export NODE_HOME=/root/.nvm/versions/node/v18.19.0 15 | export PATH=$PATH:$NODE_HOME/bin 16 | 17 | pnpm i 18 | 19 | pm2 start ./deploy/ecosystem.config.js 20 | -------------------------------------------------------------------------------- /src/app/auth/authJwt.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | 3 | import { ALLOW_HTTP_CODE, COMMON_ERR_MSG } from '@/constant'; 4 | import { IUser } from '@/interface'; 5 | import { JWT_SECRET } from '@/secret/secret'; 6 | import userService from '@/service/user.service'; 7 | 8 | const authJwt = ( 9 | ctx 10 | ): Promise<{ code: number; message: string; userInfo?: IUser }> => { 11 | return new Promise((resolve) => { 12 | // 首先判断请求头有没有authorization 13 | if (ctx.req.headers.authorization === undefined) { 14 | resolve({ code: ALLOW_HTTP_CODE.unauthorized, message: '未登录!' }); 15 | return; 16 | } 17 | 18 | const token = ctx.req.headers.authorization?.split(' ')[1]; 19 | 20 | jwt.verify(token, JWT_SECRET, (err, decoded) => { 21 | // 判断非法/过期token 22 | if (err) { 23 | let { message } = err; 24 | if (err.message.indexOf('expired') !== -1) { 25 | message = COMMON_ERR_MSG.jwtExpired; 26 | } 27 | if (err.message.indexOf('invalid') !== -1) { 28 | message = COMMON_ERR_MSG.invalidToken; 29 | } 30 | resolve({ code: ALLOW_HTTP_CODE.unauthorized, message }); 31 | return; 32 | } 33 | async function main() { 34 | try { 35 | const userResult = await userService.findAndToken( 36 | decoded.userInfo.id 37 | ); 38 | if (!userResult) { 39 | // 这个用户已经被删除了 40 | resolve({ 41 | code: ALLOW_HTTP_CODE.unauthorized, 42 | message: '该用户不存在!', 43 | }); 44 | return; 45 | } 46 | if (userResult.token !== token) { 47 | // 单点登录(防止修改密码后,原本的token还能用) 48 | resolve({ 49 | code: ALLOW_HTTP_CODE.unauthorized, 50 | message: COMMON_ERR_MSG.jwtExpired, 51 | }); 52 | return; 53 | } 54 | if (userResult.status === 2) { 55 | // 账号被禁用了 56 | resolve({ 57 | code: ALLOW_HTTP_CODE.unauthorized, 58 | message: COMMON_ERR_MSG.adminDisableUser, 59 | }); 60 | return; 61 | } 62 | resolve({ 63 | code: ALLOW_HTTP_CODE.ok, 64 | message: '验证token通过!', 65 | userInfo: userResult, 66 | }); 67 | } catch (error: any) { 68 | resolve({ code: ALLOW_HTTP_CODE.paramsError, message: error }); 69 | } 70 | } 71 | // 如果token正确,解密token获取用户id,根据id查数据库的token判断是否一致。 72 | main(); 73 | }); 74 | }); 75 | }; 76 | 77 | // 生成jwt 78 | const signJwt = (value: { userInfo: any; exp: number }): string => { 79 | const res = jwt.sign( 80 | { ...value, exp: Math.floor(Date.now() / 1000) + 60 * 60 * value.exp }, 81 | JWT_SECRET 82 | ); 83 | return res; 84 | }; 85 | 86 | export { authJwt, signJwt }; 87 | -------------------------------------------------------------------------------- /src/app/auth/verifyEnv.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import { ALLOW_HTTP_CODE, PROJECT_ENV, PROJECT_ENV_ENUM } from '@/constant'; 4 | import { CustomError } from '@/model/customError.model'; 5 | 6 | export const betaError = async (ctx: ParameterizedContext, next) => { 7 | if (PROJECT_ENV === PROJECT_ENV_ENUM.beta) { 8 | throw new CustomError( 9 | `beta环境不允许操作${ctx.path}`, 10 | ALLOW_HTTP_CODE.forbidden, 11 | ALLOW_HTTP_CODE.forbidden 12 | ); 13 | } else { 14 | await next(); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/auth/verifyUserAuth.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import { authJwt } from './authJwt'; 4 | 5 | import { ALLOW_HTTP_CODE } from '@/constant'; 6 | import roleService from '@/service/role.service'; 7 | 8 | export const verifyUserAuth = async (ctx: ParameterizedContext) => { 9 | const { code, userInfo, message } = await authJwt(ctx); 10 | if (code !== ALLOW_HTTP_CODE.ok) { 11 | console.log(message); 12 | return false; 13 | } 14 | const result: any = await roleService.getMyRole(userInfo!.id!); 15 | const roles = result.map((v) => v.role_value); 16 | if (roles.includes('SUPER_ADMIN')) { 17 | return true; 18 | } 19 | return false; 20 | }; 21 | -------------------------------------------------------------------------------- /src/app/handler/error-handle.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import { ALLOW_HTTP_CODE, ERROR_HTTP_CODE, HTTP_ERROE_MSG } from '@/constant'; 4 | import { CustomError } from '@/model/customError.model'; 5 | import { isAdmin, strSlice } from '@/utils'; 6 | import { chalk, chalkERROR } from '@/utils/chalkTip'; 7 | 8 | const errorHandler = (error, ctx: ParameterizedContext) => { 9 | const admin = isAdmin(ctx); 10 | const { path, method } = ctx.request; 11 | const ip = strSlice(String(ctx.request.headers['x-real-ip']), 490); 12 | // eslint-disable-next-line 13 | const errorLog = (error) => { 14 | console.log(chalk.redBright('statusCode:'), error.statusCode); 15 | console.log(chalk.redBright('errorCode:'), error.errorCode); 16 | console.log(chalk.redBright('message:'), error.message); 17 | console.log(chalk.redBright('query:'), { ...ctx.request.query }); 18 | console.log(chalk.redBright('params:'), ctx.params); 19 | console.log(chalk.redBright('body:'), ctx.request.body); 20 | console.log(chalk.redBright('host:'), ctx.request.header.host); 21 | console.log(chalk.redBright('referer:'), ctx.request.header.referer); 22 | console.log(chalk.redBright('cookie:'), ctx.request.header.cookie); 23 | console.log(chalk.redBright('token:'), ctx.request.headers.authorization); 24 | console.log(chalk.redBright('error:'), error); 25 | console.log(chalk.redBright('ctx.body:'), ctx.body); 26 | }; 27 | function main() { 28 | if (!(error instanceof CustomError)) { 29 | console.log(chalkERROR(`收到非自定义错误!`)); 30 | const defaultError = { 31 | code: ALLOW_HTTP_CODE.serverError, 32 | errorCode: ERROR_HTTP_CODE.serverError, 33 | error: error.message, 34 | message: HTTP_ERROE_MSG.serverError, 35 | }; 36 | ctx.status = defaultError.code; 37 | ctx.body = { 38 | code: defaultError.errorCode, 39 | errorCode: defaultError.errorCode, 40 | error: defaultError.error, 41 | message: defaultError.message, 42 | }; 43 | errorLog(error); 44 | console.log( 45 | chalkERROR(`非自定义错误返回前端的数据,http状态码:${ctx.status}`), 46 | defaultError 47 | ); 48 | return; 49 | } 50 | 51 | console.log( 52 | chalkERROR( 53 | `=========== 收到自定义错误,ip:${ip},${ 54 | admin ? '后台接口' : '前台接口' 55 | } ${method} ${path} ===========` 56 | ) 57 | ); 58 | 59 | // 不手动设置状态的话,默认是404(delete方法返回400),因此,即使走到了error-handle,且ctx.body返回了数据 60 | // 但是没有手动设置status的话,一样返回不了数据,因为status状态码都返回404了。 61 | ctx.status = error.statusCode; 62 | ctx.body = { 63 | code: error.errorCode, 64 | errorCode: error.errorCode, 65 | message: error?.message || HTTP_ERROE_MSG[error.statusCode], 66 | }; 67 | 68 | errorLog(error); 69 | console.log( 70 | chalkERROR( 71 | `=========== 收到自定义错误,ip:${ip},${ 72 | admin ? '后台接口' : '前台接口' 73 | } ${method} ${path} ===========` 74 | ) 75 | ); 76 | } 77 | 78 | main(); 79 | }; 80 | 81 | export default errorHandler; 82 | -------------------------------------------------------------------------------- /src/app/handler/success-handle.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import { ALLOW_HTTP_CODE, HTTP_SUCCESS_MSG } from '@/constant'; 4 | import { chalkSUCCESS } from '@/utils/chalkTip'; 5 | 6 | const successHandler = ({ 7 | statusCode = ALLOW_HTTP_CODE.ok, 8 | code = ALLOW_HTTP_CODE.ok, 9 | ctx, 10 | data, 11 | message, 12 | }: { 13 | statusCode?: number; 14 | code?: number; 15 | ctx: ParameterizedContext; 16 | data?: any; 17 | message?: string; 18 | }) => { 19 | console.log(chalkSUCCESS(`=========== success-handle ===========`)); 20 | const methods = ctx.request.method; 21 | 22 | ctx.status = statusCode; // 不手动设置状态的话,koa默认方法返回404,delete方法返回400 23 | ctx.body = { 24 | code, 25 | data, 26 | message: message || HTTP_SUCCESS_MSG[methods], 27 | }; 28 | 29 | console.log(chalkSUCCESS(`=========== success-handle ===========`)); 30 | }; 31 | 32 | export default successHandler; 33 | -------------------------------------------------------------------------------- /src/config/mysql/index.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize'; 2 | 3 | import { initDb } from '@/init/initDb'; 4 | import { MYSQL_CONFIG } from '@/secret/secret'; 5 | import { 6 | chalkERROR, 7 | chalkINFO, 8 | chalkSUCCESS, 9 | chalkWARN, 10 | } from '@/utils/chalkTip'; 11 | 12 | export const dbName = MYSQL_CONFIG.database; 13 | 14 | export function newSequelize(db?) { 15 | return new Sequelize({ 16 | database: db, 17 | username: MYSQL_CONFIG.username, 18 | password: MYSQL_CONFIG.password, 19 | host: MYSQL_CONFIG.host, 20 | port: MYSQL_CONFIG.port, 21 | dialect: 'mysql', 22 | dialectOptions: { 23 | // 返回正确的时间戳字符串。 24 | dateStrings: true, 25 | typeCast: true, 26 | }, 27 | pool: { 28 | max: 5, 29 | min: 0, 30 | acquire: 30000, 31 | idle: 10000, 32 | }, 33 | timezone: '+08:00', 34 | logging: false, 35 | // logging: true, 36 | }); 37 | } 38 | 39 | const sequelize = newSequelize(dbName); 40 | 41 | const msg = (flag: boolean) => 42 | `连接${MYSQL_CONFIG.host}:${MYSQL_CONFIG.port}服务器的${dbName}数据库${ 43 | flag ? '成功' : '失败' 44 | }!`; 45 | 46 | async function handleMysqlInit() { 47 | const initSequelize = newSequelize(); 48 | try { 49 | await initSequelize.query(`USE ${dbName}`, { logging: false }); 50 | // await initDb('alert', sequelize); 51 | } catch (error: any) { 52 | if (error.message.indexOf('Access') !== -1) { 53 | console.log(chalkERROR(msg(false))); 54 | await initSequelize.close(); 55 | return; 56 | } 57 | if (error.message.indexOf('ECONNREFUSED') !== -1) { 58 | console.log(chalkERROR(msg(false))); 59 | await initSequelize.close(); 60 | return; 61 | } 62 | console.log(chalkWARN(`${dbName}数据库不存在,开始新建${dbName}数据库!`)); 63 | await initSequelize.query( 64 | `CREATE DATABASE ${dbName} CHARACTER SET = 'utf8mb4';`, 65 | { logging: false } 66 | ); 67 | console.log(chalkSUCCESS(`新建${dbName}数据库成功!`)); 68 | await initDb('force', sequelize); 69 | } 70 | await initSequelize.close(); 71 | } 72 | 73 | /** 连接数据库 */ 74 | export const connectMysql = async () => { 75 | console.log( 76 | chalkINFO( 77 | `开始连接${MYSQL_CONFIG.host}:${MYSQL_CONFIG.port}服务器的mysql数据库${MYSQL_CONFIG.database}...` 78 | ) 79 | ); 80 | await handleMysqlInit(); 81 | await sequelize.authenticate({ logging: false }); 82 | await initDb('load', sequelize); 83 | console.log(chalkSUCCESS(msg(true))); 84 | }; 85 | 86 | export default sequelize; 87 | -------------------------------------------------------------------------------- /src/config/redis/index.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis'; 2 | 3 | import { REDIS_CONFIG } from '@/secret/secret'; 4 | import { chalkERROR, chalkINFO, chalkSUCCESS } from '@/utils/chalkTip'; 5 | 6 | export const redisClient = createClient({ 7 | database: REDIS_CONFIG.database, 8 | socket: { 9 | port: REDIS_CONFIG.socket.port, 10 | host: REDIS_CONFIG.socket.host, 11 | }, 12 | username: REDIS_CONFIG.username, 13 | password: REDIS_CONFIG.password, 14 | }); 15 | 16 | export const connectRedis = async () => { 17 | const msg = (flag: boolean) => 18 | `连接${REDIS_CONFIG.socket.host}:${ 19 | REDIS_CONFIG.socket.port 20 | }服务器的redis数据库${flag ? '成功' : '失败'}!`; 21 | 22 | redisClient.on('error', (err) => { 23 | console.log(chalkERROR(msg(false))); 24 | console.log(err); 25 | }); 26 | 27 | try { 28 | console.log( 29 | chalkINFO( 30 | `开始连接${REDIS_CONFIG.socket.host}:${REDIS_CONFIG.socket.port}服务器的redis数据库...` 31 | ) 32 | ); 33 | await redisClient.connect(); 34 | console.log(chalkSUCCESS(msg(true))); 35 | return redisClient; 36 | } catch (error) { 37 | console.log(chalkERROR(msg(false))); 38 | console.log(error); 39 | throw new Error(msg(false)); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/config/redis/pub.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis'; 2 | 3 | import { REDIS_CONFIG } from '@/secret/secret'; 4 | import { chalkERROR, chalkINFO, chalkSUCCESS } from '@/utils/chalkTip'; 5 | 6 | export const pubClient = createClient({ 7 | database: REDIS_CONFIG.database, 8 | socket: { 9 | port: REDIS_CONFIG.socket.port, 10 | host: REDIS_CONFIG.socket.host, 11 | }, 12 | username: REDIS_CONFIG.username, 13 | password: REDIS_CONFIG.password, 14 | }); 15 | 16 | const subClient = pubClient.duplicate(); 17 | 18 | export const createPubSub = async () => { 19 | const msg = (flag: boolean) => 20 | `创建${REDIS_CONFIG.socket.host}:${ 21 | REDIS_CONFIG.socket.port 22 | }服务器的redis PubSub${flag ? '成功' : '失败'}!`; 23 | 24 | pubClient.on('error', (err) => { 25 | console.log(chalkERROR(msg(false))); 26 | console.log(err); 27 | }); 28 | 29 | try { 30 | console.log( 31 | chalkINFO( 32 | `开始创建${REDIS_CONFIG.socket.host}:${REDIS_CONFIG.socket.port}服务器的redis PubSub...` 33 | ) 34 | ); 35 | 36 | await Promise.all([pubClient.connect(), subClient.connect()]); 37 | // pubClient.connect(), subClient.connect()成功后,就不能pubClient.set()了,否则会报错Error: Cannot send commands in PubSub mode 38 | 39 | await pubClient.configSet('notify-keyspace-events', 'Ex'); 40 | // console.log(chalkINFO(`设置notify-keyspace-events: ${setRes}`)); 41 | await pubClient.configGet('notify-keyspace-events'); 42 | // console.log( 43 | // chalkINFO(`获取notify-keyspace-events: ${getRes['notify-keyspace-events']}`) 44 | // ); 45 | console.log(chalkSUCCESS(msg(true))); 46 | } catch (error) { 47 | console.log(chalkERROR(msg(false))); 48 | console.log(error); 49 | throw new Error(msg(false)); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /src/config/websocket/constant.ts: -------------------------------------------------------------------------------- 1 | // websocket消息类型 2 | export const wsMsgType = { 3 | /** 用户连接 */ 4 | connect: 'connect', 5 | /** 用户进入聊天 */ 6 | userInRoom: 'userInRoom', 7 | /** 游客切换头像 */ 8 | visitorSwitchAvatar: 'visitorSwitchAvatar', 9 | /** 用户退出聊天 */ 10 | userOutRoom: 'userOutRoom', 11 | /** 用户发送消息 */ 12 | userSendMsg: 'userSendMsg', 13 | /** 获取在线数据 */ 14 | getOnlineData: 'getOnlineData', 15 | /** 用户存活 */ 16 | live: 'live', 17 | /** 用户点歌 */ 18 | chooseSong: 'chooseSong', 19 | }; 20 | 21 | // websocket连接状态 22 | export const wsConnectStatus = { 23 | /** 已连接 */ 24 | connection: 'connection', 25 | /** 连接中 */ 26 | connecting: 'connecting', 27 | /** 已连接 */ 28 | connected: 'connected', 29 | /** 断开连接中 */ 30 | disconnecting: 'disconnecting', 31 | /** 已断开连接 */ 32 | disconnect: 'disconnect', 33 | /** 重新连接 */ 34 | reconnect: 'reconnect', 35 | }; 36 | 37 | // websocket用户类型 38 | export const wsUserType = { 39 | visitor: 1, // 游客 40 | user: 2, // 用户 41 | }; 42 | 43 | export const liveExp = 60 * 5; // 5分钟过期 44 | 45 | export interface IData { 46 | created_at: string; 47 | client_ip: string; 48 | exp?: number; 49 | data: T; 50 | } 51 | 52 | export interface IFrontendToBackendData extends IData { 53 | created_at: string; 54 | client_ip: string; 55 | exp?: number; 56 | data: T; 57 | } 58 | 59 | export interface IBackendToFrontendData extends IData { 60 | created_at: string; 61 | client_ip: string; 62 | exp?: number; 63 | data: T; 64 | } 65 | 66 | export interface IUserInfo { 67 | id: string; 68 | userType: number; 69 | username: string; 70 | avatar: string; 71 | } 72 | -------------------------------------------------------------------------------- /src/config/websocket/ws.ts: -------------------------------------------------------------------------------- 1 | export class WebsocketClass { 2 | socket; 3 | 4 | constructor(socket) { 5 | this.socket = socket; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/controller/articleTag.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import successHandler from '@/app/handler/success-handle'; 4 | import articleTagService from '@/service/articleTag.service'; 5 | 6 | class ArticleTagController { 7 | async create(ctx: ParameterizedContext, next) { 8 | const prop = ctx.request.body; 9 | const result = await articleTagService.create(prop); 10 | successHandler({ ctx, data: result }); 11 | await next(); 12 | } 13 | 14 | async getList(ctx: ParameterizedContext, next) { 15 | const result = await articleTagService.getList(); 16 | successHandler({ ctx, data: result }); 17 | await next(); 18 | } 19 | } 20 | 21 | export default new ArticleTagController(); 22 | -------------------------------------------------------------------------------- /src/controller/backend.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import successHandler from '@/app/handler/success-handle'; 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { IBackend, IList } from '@/interface'; 6 | import { CustomError } from '@/model/customError.model'; 7 | import backendService from '@/service/backend.service'; 8 | import { execCmd } from '@/utils/clearCache'; 9 | 10 | class BackendController { 11 | async getDetail(ctx: ParameterizedContext, next) { 12 | const result = await backendService.findAll(); 13 | const obj: any = {}; 14 | result.forEach((item) => { 15 | const val = item.get(); 16 | obj[val.key!] = { 17 | value: val.value, 18 | desc: val.desc, 19 | created_at: val.created_at, 20 | updated_at: val.updated_at, 21 | }; 22 | }); 23 | successHandler({ ctx, data: obj }); 24 | await next(); 25 | } 26 | 27 | async execCmd(ctx: ParameterizedContext, next) { 28 | const { cmd } = ctx.request.body; 29 | const result = await execCmd(cmd as string); 30 | successHandler({ ctx, data: result }); 31 | await next(); 32 | } 33 | 34 | async find(ctx: ParameterizedContext, next) { 35 | const id = +ctx.params.id; 36 | const result = await backendService.find(id); 37 | successHandler({ ctx, data: result }); 38 | await next(); 39 | } 40 | 41 | async create(ctx: ParameterizedContext, next) { 42 | const { type, key, value, desc }: IBackend = ctx.request.body; 43 | await backendService.create({ 44 | type, 45 | key, 46 | value, 47 | desc, 48 | }); 49 | successHandler({ ctx }); 50 | await next(); 51 | } 52 | 53 | async getList(ctx: ParameterizedContext, next) { 54 | const { 55 | id, 56 | orderBy = 'asc', 57 | orderName = 'id', 58 | nowPage, 59 | pageSize, 60 | keyWord, 61 | rangTimeType, 62 | rangTimeStart, 63 | rangTimeEnd, 64 | }: IList = ctx.request.query; 65 | const result = await backendService.getList({ 66 | id, 67 | orderBy, 68 | orderName, 69 | nowPage, 70 | pageSize, 71 | keyWord, 72 | rangTimeType, 73 | rangTimeStart, 74 | rangTimeEnd, 75 | }); 76 | successHandler({ ctx, data: result }); 77 | await next(); 78 | } 79 | 80 | async getStatis(ctx: ParameterizedContext, next) { 81 | const result = await backendService.static(); 82 | successHandler({ ctx, data: result }); 83 | await next(); 84 | } 85 | 86 | async update(ctx: ParameterizedContext, next) { 87 | const id = +ctx.params.id; 88 | const { key, value, desc }: IBackend = ctx.request.body; 89 | await backendService.update({ 90 | id, 91 | key, 92 | value, 93 | desc, 94 | }); 95 | successHandler({ ctx }); 96 | await next(); 97 | } 98 | 99 | async delete(ctx: ParameterizedContext, next) { 100 | const id = +ctx.params.id; 101 | const isExist = await backendService.isExist([id]); 102 | if (!isExist) { 103 | throw new CustomError( 104 | `不存在id为${id}的后台设置!`, 105 | ALLOW_HTTP_CODE.paramsError, 106 | ALLOW_HTTP_CODE.paramsError 107 | ); 108 | } 109 | await backendService.delete(id); 110 | successHandler({ ctx }); 111 | 112 | await next(); 113 | } 114 | } 115 | 116 | export default new BackendController(); 117 | -------------------------------------------------------------------------------- /src/controller/buryingPoint.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import successHandler from '@/app/handler/success-handle'; 4 | import { IBuryingPoint, IList } from '@/interface'; 5 | import buryingPointService from '@/service/buryingPoint.service'; 6 | import { strSlice } from '@/utils'; 7 | 8 | class BuryingPointController { 9 | async create(ctx: ParameterizedContext, next) { 10 | const ip = strSlice(String(ctx.request.headers['x-real-ip']), 490); 11 | const user_agent = strSlice(String(ctx.request.headers['user-agent']), 490); 12 | const data: IBuryingPoint = ctx.request.body; 13 | await buryingPointService.create({ ...data, ip, user_agent }); 14 | successHandler({ ctx }); 15 | await next(); 16 | } 17 | 18 | async getList(ctx: ParameterizedContext, next) { 19 | const { 20 | id, 21 | user_id, 22 | article_id, 23 | field_a, 24 | field_b, 25 | field_c, 26 | field_d, 27 | field_e, 28 | field_f, 29 | field_g, 30 | orderBy = 'asc', 31 | orderName = 'id', 32 | nowPage, 33 | pageSize, 34 | keyWord, 35 | rangTimeType, 36 | rangTimeStart, 37 | rangTimeEnd, 38 | }: IList = ctx.request.query; 39 | const result = await buryingPointService.getList({ 40 | id, 41 | user_id, 42 | article_id, 43 | field_a, 44 | field_b, 45 | field_c, 46 | field_d, 47 | field_e, 48 | field_f, 49 | field_g, 50 | orderBy, 51 | orderName, 52 | nowPage, 53 | pageSize, 54 | keyWord, 55 | rangTimeType, 56 | rangTimeStart, 57 | rangTimeEnd, 58 | }); 59 | successHandler({ ctx, data: result }); 60 | await next(); 61 | } 62 | } 63 | 64 | export default new BuryingPointController(); 65 | -------------------------------------------------------------------------------- /src/controller/monit.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import successHandler from '@/app/handler/success-handle'; 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { IList, IMonit } from '@/interface'; 6 | import { CustomError } from '@/model/customError.model'; 7 | import monitService from '@/service/monit.service'; 8 | 9 | class MonitController { 10 | async getList(ctx: ParameterizedContext, next) { 11 | const { 12 | id, 13 | type, 14 | orderBy = 'asc', 15 | orderName = 'id', 16 | nowPage, 17 | pageSize, 18 | keyWord, 19 | rangTimeType, 20 | rangTimeStart, 21 | rangTimeEnd, 22 | }: IList = ctx.request.query; 23 | const result = await monitService.getList({ 24 | id, 25 | type, 26 | nowPage, 27 | pageSize, 28 | orderBy, 29 | orderName, 30 | keyWord, 31 | rangTimeType, 32 | rangTimeStart, 33 | rangTimeEnd, 34 | }); 35 | successHandler({ ctx, data: result }); 36 | 37 | await next(); 38 | } 39 | 40 | async find(ctx: ParameterizedContext, next) { 41 | const id = +ctx.params.id; 42 | const result = await monitService.find(id); 43 | successHandler({ ctx, data: result }); 44 | 45 | await next(); 46 | } 47 | 48 | async update(ctx: ParameterizedContext, next) { 49 | const id = +ctx.params.id; 50 | const { type, info }: IMonit = ctx.request.body; 51 | const isExist = await monitService.isExist([id]); 52 | if (!isExist) { 53 | throw new CustomError( 54 | `不存在id为${id}的监控!`, 55 | ALLOW_HTTP_CODE.paramsError, 56 | ALLOW_HTTP_CODE.paramsError 57 | ); 58 | } 59 | const result = await monitService.update({ 60 | id, 61 | type, 62 | info, 63 | }); 64 | successHandler({ ctx, data: result }); 65 | 66 | await next(); 67 | } 68 | 69 | async create(ctx: ParameterizedContext, next) { 70 | const { type, info }: IMonit = ctx.request.body; 71 | const result = await monitService.create({ 72 | type, 73 | info, 74 | }); 75 | successHandler({ ctx, data: result }); 76 | 77 | await next(); 78 | } 79 | 80 | async delete(ctx: ParameterizedContext, next) { 81 | const id = +ctx.params.id; 82 | const isExist = await monitService.isExist([id]); 83 | if (!isExist) { 84 | throw new CustomError( 85 | `不存在id为${id}的监控!`, 86 | ALLOW_HTTP_CODE.paramsError, 87 | ALLOW_HTTP_CODE.paramsError 88 | ); 89 | } 90 | const result = await monitService.delete(id); 91 | successHandler({ ctx, data: result }); 92 | 93 | await next(); 94 | } 95 | } 96 | 97 | export default new MonitController(); 98 | -------------------------------------------------------------------------------- /src/controller/position.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import successHandler from '@/app/handler/success-handle'; 4 | import positionService from '@/service/position.service'; 5 | import { strSlice } from '@/utils'; 6 | 7 | class PositionController { 8 | async get(ctx: ParameterizedContext, next) { 9 | const ip = strSlice(String(ctx.request.headers['x-real-ip']), 490); 10 | const result = await positionService.get(ip); 11 | successHandler({ 12 | ctx, 13 | data: { gaode: result, ip: ip || 'ip错误' }, 14 | }); 15 | await next(); 16 | } 17 | } 18 | 19 | export default new PositionController(); 20 | -------------------------------------------------------------------------------- /src/controller/redis.controller.ts: -------------------------------------------------------------------------------- 1 | import { redisClient } from '@/config/redis'; 2 | 3 | class RedisController { 4 | /** 5 | * @description: 从 Redis 2.8 开始,-1代表key 存在,但没有设置剩余生存时间;-2代表key不存在 6 | * @param {*} param1 7 | * @return {*} 8 | */ 9 | getTTL = async ({ prefix = '', key = '' }) => { 10 | const res = await redisClient.ttl(`${prefix}-${key}`); 11 | return res; 12 | }; 13 | 14 | del = async ({ prefix = '', key = '' }) => { 15 | const res = await redisClient.del(`${prefix}-${key}`); 16 | return res; 17 | }; 18 | 19 | getVal = async ({ prefix = '', key = '' }) => { 20 | const res = await redisClient.get(`${prefix}-${key}`); 21 | return res; 22 | }; 23 | 24 | setVal = async ({ 25 | prefix, 26 | key, 27 | value, 28 | }: { 29 | prefix: string; 30 | key: string; 31 | value: string; 32 | }) => { 33 | await redisClient.set(`${prefix}-${key}`, value); // string类型 34 | }; 35 | 36 | setExVal = async ({ 37 | prefix, 38 | key, 39 | value, 40 | exp, 41 | }: { 42 | prefix: string; 43 | key: string; 44 | value: string; 45 | /** 有效期,单位:秒 */ 46 | exp: number; 47 | }) => { 48 | await redisClient.setEx(`${prefix}-${key}`, exp, value); // string类型 49 | }; 50 | 51 | setHashVal = async (key: string, field: string, value: any) => { 52 | const res = await redisClient.hSetNX(key, field, JSON.stringify(value)); 53 | return res; 54 | }; 55 | 56 | delHashVal = async (key: string, field: string) => { 57 | const res = await redisClient.hDel(key, field); 58 | return res; 59 | }; 60 | 61 | getHashVal = async (key: string, field: string) => { 62 | const res = await redisClient.hGet(key, field); 63 | return res; 64 | }; 65 | 66 | getAllHashVal = async (key: string) => { 67 | const res = await redisClient.hVals(key); 68 | return res; 69 | }; 70 | 71 | getHashLenVal = async (key: string) => { 72 | const res = await redisClient.hLen(key); 73 | return res; 74 | }; 75 | 76 | setSetVal = async (key: string, value: string) => { 77 | const res = await redisClient.sAdd(key, value); 78 | return res; 79 | }; 80 | 81 | getSetVal = async (key: string) => { 82 | const res = await redisClient.sMembers(key); 83 | return res; 84 | }; 85 | 86 | setListVal = async (key: string, value: string) => { 87 | const res = await redisClient.lPush(key, value); 88 | return res; 89 | }; 90 | 91 | getListVal = async (key: string) => { 92 | const res = await redisClient.lRange(key, 0, -1); 93 | return res; 94 | }; 95 | } 96 | 97 | export default new RedisController(); 98 | -------------------------------------------------------------------------------- /src/controller/roleAuth.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import successHandler from '@/app/handler/success-handle'; 4 | import roleAuthService from '@/service/roleAuth.service'; 5 | 6 | class RoleAuthController { 7 | async getList(ctx: ParameterizedContext, next) { 8 | const result = await roleAuthService.getList(); 9 | successHandler({ ctx, data: result }); 10 | await next(); 11 | } 12 | } 13 | 14 | export default new RoleAuthController(); 15 | -------------------------------------------------------------------------------- /src/controller/schedule.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | import schedule from 'node-schedule'; 3 | 4 | import { authJwt } from '@/app/auth/authJwt'; 5 | import successHandler from '@/app/handler/success-handle'; 6 | import { ALLOW_HTTP_CODE, MONIT_JOB } from '@/constant'; 7 | import { main } from '@/init/monit/monitBackupsDb'; 8 | import { CustomError } from '@/model/customError.model'; 9 | import { clearCache, restartPm2, showMemory } from '@/utils/clearCache'; 10 | 11 | class ScheduleController { 12 | async getDbJob(ctx: ParameterizedContext, next) { 13 | const dbJob = schedule.scheduledJobs[MONIT_JOB.BACKUPDB]; 14 | if (dbJob) { 15 | successHandler({ 16 | ctx, 17 | data: { status: 1, nextInvocation: dbJob.nextInvocation().getTime() }, 18 | }); 19 | } else { 20 | successHandler({ 21 | ctx, 22 | data: { status: 2, message: '任务异常' }, 23 | }); 24 | } 25 | 26 | await next(); 27 | } 28 | 29 | async invokeDbJob(ctx: ParameterizedContext, next) { 30 | const { code, userInfo, message } = await authJwt(ctx); 31 | if (code !== ALLOW_HTTP_CODE.ok) { 32 | throw new CustomError(message, code, code); 33 | } 34 | main(userInfo?.id); 35 | successHandler({ 36 | ctx, 37 | data: '开始执行备份任务,大约5分钟执行完成', 38 | }); 39 | await next(); 40 | } 41 | 42 | async invokeClearCacheJob(ctx: ParameterizedContext, next) { 43 | await clearCache(); 44 | successHandler({ 45 | ctx, 46 | message: '开始执行清除buff/cache任务', 47 | }); 48 | await next(); 49 | } 50 | 51 | async restartPm2(ctx: ParameterizedContext, next) { 52 | restartPm2(); 53 | successHandler({ 54 | ctx, 55 | message: '开始执行重启pm2任务', 56 | }); 57 | await next(); 58 | } 59 | 60 | async invokeShowMemoryJob(ctx: ParameterizedContext, next) { 61 | const data = await showMemory(); 62 | successHandler({ 63 | ctx, 64 | data, 65 | }); 66 | await next(); 67 | } 68 | } 69 | 70 | export default new ScheduleController(); 71 | -------------------------------------------------------------------------------- /src/controller/theme.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import { verifyUserAuth } from '@/app/auth/verifyUserAuth'; 4 | import successHandler from '@/app/handler/success-handle'; 5 | import { ALLOW_HTTP_CODE } from '@/constant'; 6 | import { IList, ITheme } from '@/interface'; 7 | import { CustomError } from '@/model/customError.model'; 8 | import themeService from '@/service/theme.service'; 9 | 10 | class ThemeController { 11 | async getList(ctx: ParameterizedContext, next) { 12 | const { 13 | id, 14 | orderBy = 'asc', 15 | orderName = 'id', 16 | nowPage, 17 | pageSize, 18 | keyWord, 19 | rangTimeType, 20 | rangTimeStart, 21 | rangTimeEnd, 22 | }: IList = ctx.request.query; 23 | const result = await themeService.getList({ 24 | id, 25 | orderBy, 26 | orderName, 27 | nowPage, 28 | pageSize, 29 | keyWord, 30 | rangTimeType, 31 | rangTimeStart, 32 | rangTimeEnd, 33 | }); 34 | successHandler({ ctx, data: result }); 35 | 36 | await next(); 37 | } 38 | 39 | async find(ctx: ParameterizedContext, next) { 40 | const id = +ctx.params.id; 41 | const result = await themeService.find(id); 42 | successHandler({ ctx, data: result }); 43 | 44 | await next(); 45 | } 46 | 47 | async update(ctx: ParameterizedContext, next) { 48 | const hasAuth = await verifyUserAuth(ctx); 49 | if (!hasAuth) { 50 | throw new CustomError( 51 | `权限不足!`, 52 | ALLOW_HTTP_CODE.forbidden, 53 | ALLOW_HTTP_CODE.forbidden 54 | ); 55 | } 56 | const id = +ctx.params.id; 57 | const { key, value, model, lang, desc }: ITheme = ctx.request.body; 58 | const isExist = await themeService.isExist([id]); 59 | if (!isExist) { 60 | throw new CustomError( 61 | `不存在id为${id}的主题!`, 62 | ALLOW_HTTP_CODE.paramsError, 63 | ALLOW_HTTP_CODE.paramsError 64 | ); 65 | } 66 | await themeService.update({ 67 | id, 68 | key, 69 | value, 70 | model, 71 | lang, 72 | desc, 73 | }); 74 | successHandler({ ctx }); 75 | 76 | await next(); 77 | } 78 | 79 | async create(ctx: ParameterizedContext, next) { 80 | const { key, value, model, lang, desc }: ITheme = ctx.request.body; 81 | await themeService.create({ 82 | key, 83 | value, 84 | model, 85 | lang, 86 | desc, 87 | }); 88 | successHandler({ ctx }); 89 | 90 | await next(); 91 | } 92 | 93 | async delete(ctx: ParameterizedContext, next) { 94 | const hasAuth = await verifyUserAuth(ctx); 95 | if (!hasAuth) { 96 | throw new CustomError( 97 | `权限不足!`, 98 | ALLOW_HTTP_CODE.forbidden, 99 | ALLOW_HTTP_CODE.forbidden 100 | ); 101 | } 102 | const id = +ctx.params.id; 103 | const isExist = await themeService.isExist([id]); 104 | if (!isExist) { 105 | throw new CustomError( 106 | `不存在id为${id}的主题!`, 107 | ALLOW_HTTP_CODE.paramsError, 108 | ALLOW_HTTP_CODE.paramsError 109 | ); 110 | } 111 | await themeService.delete(id); 112 | successHandler({ ctx }); 113 | 114 | await next(); 115 | } 116 | } 117 | 118 | export default new ThemeController(); 119 | -------------------------------------------------------------------------------- /src/controller/thirdUser.controller.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import successHandler from '@/app/handler/success-handle'; 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { IList, IThirdUser } from '@/interface'; 6 | import { CustomError } from '@/model/customError.model'; 7 | import thirdUserService from '@/service/thirdUser.service'; 8 | 9 | class ThirdUserController { 10 | async getList(ctx: ParameterizedContext, next) { 11 | const { 12 | id, 13 | orderBy = 'asc', 14 | orderName = 'id', 15 | nowPage, 16 | pageSize, 17 | keyWord, 18 | rangTimeType, 19 | rangTimeStart, 20 | rangTimeEnd, 21 | }: IList = ctx.request.query; 22 | const result = await thirdUserService.getList({ 23 | id, 24 | orderBy, 25 | orderName, 26 | nowPage, 27 | pageSize, 28 | keyWord, 29 | rangTimeType, 30 | rangTimeStart, 31 | rangTimeEnd, 32 | }); 33 | successHandler({ ctx, data: result }); 34 | 35 | await next(); 36 | } 37 | 38 | async find(ctx: ParameterizedContext, next) { 39 | const id = +ctx.params.id; 40 | const result = await thirdUserService.find(id); 41 | successHandler({ ctx, data: result }); 42 | 43 | await next(); 44 | } 45 | 46 | async update(ctx: ParameterizedContext, next) { 47 | const id = +ctx.params.id; 48 | const { user_id, third_platform, third_user_id }: IThirdUser = 49 | ctx.request.body; 50 | const isExist = await thirdUserService.isExist([id]); 51 | if (!isExist) { 52 | throw new CustomError( 53 | `不存在id为${id}的第三方用户记录!`, 54 | ALLOW_HTTP_CODE.paramsError, 55 | ALLOW_HTTP_CODE.paramsError 56 | ); 57 | } 58 | const result = await thirdUserService.update({ 59 | id, 60 | user_id, 61 | third_platform, 62 | third_user_id, 63 | }); 64 | successHandler({ ctx, data: result }); 65 | 66 | await next(); 67 | } 68 | 69 | async create(ctx: ParameterizedContext, next) { 70 | const { user_id, third_platform, third_user_id }: IThirdUser = 71 | ctx.request.body; 72 | const result = await thirdUserService.create({ 73 | user_id, 74 | third_platform, 75 | third_user_id, 76 | }); 77 | successHandler({ ctx, data: result }); 78 | 79 | await next(); 80 | } 81 | 82 | async delete(ctx: ParameterizedContext, next) { 83 | const id = +ctx.params.id; 84 | const isExist = await thirdUserService.isExist([id]); 85 | if (!isExist) { 86 | throw new CustomError( 87 | `不存在id为${id}的第三方用户记录!`, 88 | ALLOW_HTTP_CODE.paramsError, 89 | ALLOW_HTTP_CODE.paramsError 90 | ); 91 | } 92 | const result = await thirdUserService.delete(id); 93 | successHandler({ ctx, data: result }); 94 | 95 | await next(); 96 | } 97 | } 98 | 99 | export default new ThirdUserController(); 100 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // 一定要将import './init';放到最开头,因为它里面初始化了路径别名 2 | import './init/alias'; 3 | import './init/initFile'; 4 | 5 | import { performance } from 'perf_hooks'; 6 | 7 | import { connectMysql } from '@/config/mysql'; 8 | import { connectRedis } from '@/config/redis'; 9 | import { createPubSub } from '@/config/redis/pub'; 10 | import { PROJECT_ENV, PROJECT_NAME, PROJECT_PORT } from '@/constant'; 11 | import { MYSQL_CONFIG } from '@/secret/secret'; 12 | import { getIpAddress } from '@/utils'; 13 | import { chalkERROR, chalkSUCCESS, chalkWARN } from '@/utils/chalkTip'; 14 | 15 | const start = performance.now(); 16 | 17 | async function main() { 18 | try { 19 | await Promise.all([ 20 | connectMysql(), // 连接mysql 21 | connectRedis(), // 连接redis 22 | createPubSub(), // 创建redis的发布订阅 23 | ]); 24 | const port = +PROJECT_PORT; 25 | await (await import('./setup')).setupKoa({ port }); 26 | console.log(); 27 | 28 | console.log(chalkWARN(`监听端口: ${port}`)); 29 | console.log(chalkWARN(`项目名称: ${PROJECT_NAME}`)); 30 | console.log(chalkWARN(`项目环境: ${PROJECT_ENV}`)); 31 | console.log(chalkWARN(`mysql数据库: ${MYSQL_CONFIG.database}`)); 32 | console.log(chalkWARN(`mysql host: ${MYSQL_CONFIG.host}`)); 33 | getIpAddress().forEach((ip) => { 34 | console.log(chalkSUCCESS(`http://${ip}:${port}/`)); 35 | }); 36 | console.log( 37 | chalkSUCCESS( 38 | `项目启动成功!耗时:${Math.floor(performance.now() - start)}ms` 39 | ) 40 | ); 41 | console.log(); 42 | } catch (error) { 43 | console.log(chalkERROR(`项目启动失败!`)); 44 | console.log(error); 45 | } 46 | } 47 | 48 | main(); 49 | -------------------------------------------------------------------------------- /src/init/alias.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import moduleAlias from 'module-alias'; 4 | 5 | import { PROJECT_ENV, PROJECT_ENV_ENUM } from '../constant'; 6 | import { chalkSUCCESS } from '../utils/chalkTip'; 7 | 8 | if (PROJECT_ENV === PROJECT_ENV_ENUM.prod) { 9 | moduleAlias.addAlias('@', path.join(process.cwd(), 'dist')); 10 | } else { 11 | moduleAlias.addAlias('@', path.join(process.cwd(), 'src')); 12 | } 13 | 14 | export const aliasOk = () => { 15 | console.log(chalkSUCCESS('添加路径别名成功!')); 16 | }; 17 | -------------------------------------------------------------------------------- /src/init/initDb.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import { Sequelize } from 'sequelize'; 4 | 5 | import { PROJECT_ENV, PROJECT_ENV_ENUM, PROJECT_NODE_ENV } from '@/constant'; 6 | import { deleteForeignKeys, deleteIndexs } from '@/utils'; 7 | import { chalkERROR, chalkSUCCESS, chalkWARN } from '@/utils/chalkTip'; 8 | 9 | /** 加载所有model */ 10 | export const loadAllModel = () => { 11 | const modelDir = `${process.cwd()}/${ 12 | PROJECT_ENV === PROJECT_ENV_ENUM.prod ? 'dist' : 'src' 13 | }/model`; 14 | fs.readdirSync(modelDir).forEach((file: string) => { 15 | if (PROJECT_NODE_ENV === 'development') { 16 | if (file.indexOf('.model.ts') === -1) return; 17 | } else if (file.indexOf('.model.js') === -1) return; 18 | 19 | // eslint-disable-next-line 20 | require(`${modelDir}/${file}`).default; 21 | }); 22 | console.log(chalkSUCCESS(`加载所有数据库表成功!`)); 23 | }; 24 | 25 | /** 删除所有表 */ 26 | export const deleteAllTable = async (sequelizeInst: Sequelize) => { 27 | try { 28 | loadAllModel(); 29 | await sequelizeInst.drop(); 30 | console.log(chalkSUCCESS('删除所有表成功!')); 31 | } catch (err) { 32 | console.log(chalkERROR('删除所有表失败!')); 33 | } 34 | }; 35 | 36 | /** 37 | * 初始化数据库: 38 | * force:重置所有 39 | * alert:校正现有数据库 40 | * load:加载数据库表 41 | */ 42 | export const initDb = async ( 43 | type: 'force' | 'alert' | 'load', 44 | sequelizeInst: Sequelize 45 | ) => { 46 | switch (type) { 47 | case 'force': 48 | console.log(chalkWARN('开始初始化数据库所有表')); 49 | await deleteForeignKeys({ sequelizeInst }); 50 | await deleteIndexs({ sequelizeInst }); 51 | await deleteAllTable(sequelizeInst); 52 | await sequelizeInst.sync({ force: true }); // 将创建表,如果表已经存在,则将其首先删除 53 | console.log(chalkSUCCESS('初始化数据库所有表完成!')); 54 | break; 55 | case 'alert': 56 | console.log(chalkWARN('开始校正数据库所有表')); 57 | require('@/model/relation'); 58 | await sequelizeInst.sync({ alter: true }); // 这将检查数据库中表的当前状态(它具有哪些列,它们的数据类型等),然后在表中进行必要的更改以使其与模型匹配. 59 | console.log(chalkSUCCESS('校正数据库所有表完成!')); 60 | break; 61 | case 'load': 62 | require('@/model/relation'); 63 | break; 64 | default: 65 | throw new Error('initDb参数不正确!'); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/init/initFile.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import { SECRETTEMP_FILE, SECRET_FILE, UPLOAD_DIR } from '../constant'; 4 | 5 | function handleSecretFile() { 6 | const isExist = fs.existsSync(SECRET_FILE); 7 | if (!isExist) { 8 | const secretTemp = fs.readFileSync(SECRETTEMP_FILE); 9 | fs.writeFileSync(SECRET_FILE, secretTemp.toString()); 10 | } 11 | } 12 | 13 | function handleUploadDir() { 14 | const isExist = fs.existsSync(UPLOAD_DIR); 15 | if (!isExist) { 16 | fs.mkdirSync(UPLOAD_DIR); 17 | } 18 | } 19 | 20 | handleSecretFile(); // 处理秘钥文件(src.config/secret.ts) 21 | handleUploadDir(); // 处理文件上传目录(src/upload) 22 | -------------------------------------------------------------------------------- /src/init/monit/index.ts: -------------------------------------------------------------------------------- 1 | import { monitBackupDbJob } from './monitBackupsDb'; 2 | import { monitDeleteLogJob } from './monitDeleteLog'; 3 | import { monitMemoryJob } from './monitMemory'; 4 | import { monitProcessJob } from './monitProcess'; 5 | import { monitQiniuCDNJob } from './monitQiniuCDN'; 6 | 7 | export const initMonit = () => { 8 | monitMemoryJob(); // 监控服务器内存 9 | monitProcessJob(); // 监控node进程 10 | monitQiniuCDNJob(); // 监控七牛云cdn 11 | monitBackupDbJob(); // 监控备份数据库 12 | monitDeleteLogJob(); // 监控删除日志 13 | }; 14 | -------------------------------------------------------------------------------- /src/init/monit/monitDeleteLog.ts: -------------------------------------------------------------------------------- 1 | import schedule from 'node-schedule'; 2 | 3 | import { MONIT_JOB, PROJECT_ENV } from '@/constant'; 4 | import logController from '@/controller/log.controller'; 5 | import { chalkINFO, chalkSUCCESS, chalkWARN } from '@/utils/chalkTip'; 6 | 7 | const rule = new schedule.RecurrenceRule(); 8 | 9 | const allHour = 24; 10 | const allMinute = 60; 11 | const allSecond = 60; 12 | const allHourArr: number[] = []; 13 | const allMinuteArr: number[] = []; 14 | const allSecondArr: number[] = []; 15 | 16 | for (let i = 0; i < allHour; i += 1) { 17 | allHourArr.push(i); 18 | } 19 | for (let i = 0; i < allMinute; i += 1) { 20 | allMinuteArr.push(i); 21 | } 22 | for (let i = 0; i < allSecond; i += 1) { 23 | allSecondArr.push(i); 24 | } 25 | 26 | // 每12小时执行 27 | rule.hour = allHourArr.filter((v) => v % 12 === 0); 28 | // 每12小时10分执行 29 | rule.minute = 10; 30 | 31 | export const main = () => { 32 | logController.common.deleteRang(90); 33 | }; 34 | 35 | export const monitDeleteLogJob = () => { 36 | console.log(chalkSUCCESS('监控任务: 删除日志定时任务启动!')); 37 | const monitJobName = MONIT_JOB.DELETELOG; 38 | schedule.scheduleJob(monitJobName, rule, () => { 39 | if (PROJECT_ENV === 'prod') { 40 | console.log(chalkINFO(`执行${monitJobName}定时任务`)); 41 | main(); 42 | } else { 43 | console.log(chalkWARN(`当前非生产环境,不执行${monitJobName}定时任务`)); 44 | } 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /src/init/monit/monitProcess.ts: -------------------------------------------------------------------------------- 1 | import schedule from 'node-schedule'; 2 | 3 | import { MONIT_JOB, MONIT_TYPE, PROJECT_ENV } from '@/constant'; 4 | import monitService from '@/service/monit.service'; 5 | import { chalkINFO, chalkSUCCESS, chalkWARN } from '@/utils/chalkTip'; 6 | import { formatMemorySize } from '@/utils/index'; 7 | 8 | // https://github.com/node-schedule/node-schedule#cron-style-scheduling 9 | // 每分钟的第30秒触发: '30 * * * * *' 10 | // 每小时的1分30秒触发 :'30 1 * * * *' 11 | // 每天的凌晨1点1分30秒触发 :'30 1 1 * * *' 12 | // 每月的1日1点1分30秒触发 :'30 1 1 1 * *' 13 | // 2016年的1月1日1点1分30秒触发 :'30 1 1 1 2016 *' 14 | // 每周1的1点1分30秒触发 :'30 1 1 * * 1' 15 | // 上面的太反人类了。 16 | 17 | // 每隔10秒执行,设置 rule.second =[0,10,20,30,40,50]即可。 18 | // 每秒执行就是rule.second =[0,1,2,3......59] 19 | // 每分钟0秒执行就是rule.second =0 20 | // 每小时30分执行就是rule.minute =30;rule.second =0; 21 | // 每天0点执行就是rule.hour =0;rule.minute =0;rule.second =0; 22 | // 每月1号的10点就是rule.date =1;rule.hour =10;rule.minute =0;rule.second =0; 23 | // 每周1,3,5的0点和12点就是rule.dayOfWeek =[1,3,5];rule.hour =[0,12];rule.minute =0;rule.second =0; 24 | 25 | // const allSecond = []; // [0,1,2,3,4,5.....59] 26 | // for (let i = 0; i < 60; i += 1) { 27 | // allSecond.push(i); 28 | // } 29 | // rule.second = allSecond; 30 | /** 31 | * rule.hour = [0, 3, 6, 9, 12, 14, 16, 18, 20, 22] 32 | * 每三小时执行一次,切记还要添加rule.minute = 0,否则会造成如:到了00:00:00的时候,执行了任务 33 | * 00:01:00,又会一次执行任务,00:02:00,又会一次执行任务,以此类推,直到00:59:00,还会一次执行任务, 34 | * 等到了01:00:00时候才不会执行,后面的也是以此类推,因此得加上rule.minute = 0才可以, 35 | * 这样就代表了00:00:00,03:00:00,06:00:00,09:00:00等时间才执行一次 36 | */ 37 | 38 | const rule = new schedule.RecurrenceRule(); 39 | 40 | const allHour = 24; 41 | const allMinute = 60; 42 | const allSecond = 60; 43 | const allHourArr: number[] = []; 44 | const allMinuteArr: number[] = []; 45 | const allSecondArr: number[] = []; 46 | 47 | for (let i = 0; i < allHour; i += 1) { 48 | allHourArr.push(i); 49 | } 50 | for (let i = 0; i < allMinute; i += 1) { 51 | allMinuteArr.push(i); 52 | } 53 | for (let i = 0; i < allSecond; i += 1) { 54 | allSecondArr.push(i); 55 | } 56 | 57 | // 每1小时执行 58 | rule.hour = allHourArr.filter((v) => v % 1 === 0); 59 | rule.minute = 0; 60 | rule.second = 0; 61 | 62 | export const main = () => { 63 | const info = { 64 | pid: process.pid, 65 | memory: { 66 | rss: formatMemorySize(process.memoryUsage().rss), 67 | external: formatMemorySize(process.memoryUsage().external), 68 | heapTotal: formatMemorySize(process.memoryUsage().heapTotal), 69 | heapUsed: formatMemorySize(process.memoryUsage().heapUsed), 70 | arrayBuffers: formatMemorySize(process.memoryUsage().arrayBuffers), 71 | }, 72 | cpu: { 73 | system: formatMemorySize(process.cpuUsage().system), 74 | user: formatMemorySize(process.cpuUsage().user), 75 | }, 76 | }; 77 | monitService.create({ 78 | type: MONIT_TYPE.VUE3_BLOG_SERVER_NODE_PROCESS, 79 | info: JSON.stringify(info), 80 | }); 81 | }; 82 | 83 | export const monitProcessJob = () => { 84 | console.log(chalkSUCCESS('监控任务: node进程定时任务启动!')); 85 | const monitJobName = MONIT_JOB.PROCESS; 86 | schedule.scheduleJob(monitJobName, rule, () => { 87 | if (PROJECT_ENV === 'prod') { 88 | console.log(chalkINFO(`执行${monitJobName}定时任务`)); 89 | main(); 90 | } else { 91 | console.log(chalkWARN(`当前非生产环境,不执行${monitJobName}定时任务`)); 92 | } 93 | }); 94 | }; 95 | -------------------------------------------------------------------------------- /src/middleware/article.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | title: Joi.string().min(1).max(100), 10 | desc: [Joi.string().min(1).max(100), '', null], 11 | content: Joi.string(), 12 | priority: [Joi.number(), null], 13 | is_comment: [1, 2], 14 | status: [1, 2], 15 | head_img: [Joi.string().min(1).max(300), '', null], 16 | click: Joi.number(), 17 | visit: Joi.number(), 18 | tags: Joi.array().items(Joi.number()), 19 | types: Joi.array().items(Joi.number()), 20 | users: Joi.array().items(Joi.number()), 21 | created_at: [Joi.string(), null], 22 | updated_at: [Joi.string(), null], 23 | deleted_at: [Joi.string(), null], 24 | }); 25 | 26 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 27 | const prop = ctx.request.body; 28 | try { 29 | await schema.validateAsync(prop, { 30 | abortEarly: false, 31 | allowUnknown: false, 32 | convert: false, 33 | }); 34 | } catch (error: any) { 35 | console.log(error); 36 | throw new CustomError( 37 | error.message, 38 | ALLOW_HTTP_CODE.paramsError, 39 | ALLOW_HTTP_CODE.paramsError 40 | ); 41 | } 42 | await next(); 43 | }; 44 | -------------------------------------------------------------------------------- /src/middleware/articleTag.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | article_id: Joi.number(), 9 | tag_id: Joi.number(), 10 | }); 11 | 12 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 13 | try { 14 | const props = ctx.request.body; 15 | await schema.validateAsync(props, { 16 | abortEarly: false, 17 | allowUnknown: false, 18 | convert: false, 19 | }); 20 | await next(); 21 | } catch (error: any) { 22 | throw new CustomError( 23 | error.message, 24 | ALLOW_HTTP_CODE.paramsError, 25 | ALLOW_HTTP_CODE.paramsError 26 | ); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/middleware/auth.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | p_id: Joi.number(), 10 | auth_name: Joi.string().min(1).max(80), 11 | auth_value: Joi.string().min(1).max(80), 12 | type: [1, 2], 13 | priority: [Joi.number(), null], 14 | c_auths: Joi.array().items(Joi.number()), 15 | }); 16 | 17 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 18 | try { 19 | const props = ctx.request.body; 20 | await schema.validateAsync(props, { 21 | abortEarly: false, 22 | allowUnknown: false, 23 | convert: false, 24 | }); 25 | await next(); 26 | } catch (error: any) { 27 | throw new CustomError( 28 | error.message, 29 | ALLOW_HTTP_CODE.paramsError, 30 | ALLOW_HTTP_CODE.paramsError 31 | ); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/middleware/comment.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | article_id: Joi.number(), 10 | parent_comment_id: Joi.number(), 11 | reply_comment_id: Joi.number(), 12 | from_user_id: Joi.number(), 13 | to_user_id: Joi.number(), 14 | content: Joi.string().min(1).max(400).required(), 15 | }); 16 | 17 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 18 | try { 19 | const props = ctx.request.body; 20 | await schema.validateAsync(props, { 21 | abortEarly: false, 22 | allowUnknown: false, 23 | convert: false, 24 | }); 25 | await next(); 26 | } catch (error: any) { 27 | throw new CustomError( 28 | error.message, 29 | ALLOW_HTTP_CODE.paramsError, 30 | ALLOW_HTTP_CODE.paramsError 31 | ); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/middleware/csrf.middleware.ts: -------------------------------------------------------------------------------- 1 | // import { ParameterizedContext } from 'koa'; 2 | 3 | // import { authJwt } from '@/app/auth/authJwt'; 4 | // import { chalkINFO } from '@/app/chalkTip'; 5 | 6 | const csrf = async () => { 7 | /** 8 | * 1,验证referer 9 | * 2,验证origin 10 | */ 11 | }; 12 | 13 | export default csrf; 14 | -------------------------------------------------------------------------------- /src/middleware/emailUser.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | email: Joi.string().pattern( 9 | /^[A-Za-z0-9\u4E00-\u9FA5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/ 10 | ), 11 | code: Joi.string(), 12 | }); 13 | 14 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 15 | try { 16 | const props = ctx.request.body; 17 | await schema.validateAsync(props, { 18 | abortEarly: false, 19 | allowUnknown: false, 20 | convert: false, 21 | }); 22 | await next(); 23 | } catch (error: any) { 24 | throw new CustomError( 25 | error.message, 26 | ALLOW_HTTP_CODE.paramsError, 27 | ALLOW_HTTP_CODE.paramsError 28 | ); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/middleware/link.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | email: [Joi.string().min(1).max(100), '', null], 10 | name: Joi.string().min(1).max(100), 11 | avatar: Joi.string().min(1).max(300), 12 | desc: Joi.string().min(1).max(100), 13 | url: Joi.string().min(1).max(300), 14 | status: [1, 2], 15 | priority: [Joi.number(), null], 16 | }); 17 | 18 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 19 | try { 20 | const props = ctx.request.body; 21 | await schema.validateAsync(props, { 22 | abortEarly: false, 23 | allowUnknown: false, 24 | convert: false, 25 | }); 26 | await next(); 27 | } catch (error: any) { 28 | throw new CustomError( 29 | error.message, 30 | ALLOW_HTTP_CODE.paramsError, 31 | ALLOW_HTTP_CODE.paramsError 32 | ); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/middleware/log.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | user_id: Joi.number(), 9 | api_user_agent: Joi.string(), 10 | api_from: Joi.number(), 11 | api_real_ip: Joi.string(), 12 | api_host: Joi.string(), 13 | api_hostname: Joi.string(), 14 | api_method: Joi.string(), 15 | api_path: Joi.string(), 16 | api_query: Joi.string(), 17 | api_body: Joi.string(), 18 | }); 19 | 20 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 21 | try { 22 | const props = ctx.request.body; 23 | await schema.validateAsync(props, { 24 | abortEarly: false, 25 | allowUnknown: false, 26 | convert: false, 27 | }); 28 | await next(); 29 | } catch (error: any) { 30 | throw new CustomError( 31 | error.message, 32 | ALLOW_HTTP_CODE.paramsError, 33 | ALLOW_HTTP_CODE.paramsError 34 | ); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/middleware/music.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | name: Joi.string().min(1).max(100), 10 | cover_pic: Joi.string().min(1).max(300), 11 | author: Joi.string().min(1).max(100), 12 | audio_url: Joi.string().min(1).max(300), 13 | status: [1, 2], 14 | priority: [Joi.number(), null], 15 | }); 16 | 17 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 18 | try { 19 | const props = ctx.request.body; 20 | await schema.validateAsync(props, { 21 | abortEarly: false, 22 | allowUnknown: false, 23 | convert: false, 24 | }); 25 | await next(); 26 | } catch (error: any) { 27 | throw new CustomError( 28 | error.message, 29 | ALLOW_HTTP_CODE.paramsError, 30 | ALLOW_HTTP_CODE.paramsError 31 | ); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/middleware/qqUser.middleware.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 4 | await next(); 5 | }; 6 | -------------------------------------------------------------------------------- /src/middleware/role.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | p_id: Joi.number(), 10 | role_name: Joi.string().min(1).max(80), 11 | role_value: Joi.string().min(1).max(80), 12 | type: [1, 2], 13 | priority: [Joi.number(), null], 14 | role_auths: Joi.array().items(Joi.number()), 15 | c_roles: Joi.array().items(Joi.number()), 16 | }); 17 | 18 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 19 | try { 20 | const props = ctx.request.body; 21 | await schema.validateAsync(props, { 22 | abortEarly: false, 23 | allowUnknown: false, 24 | convert: false, 25 | }); 26 | await next(); 27 | } catch (error: any) { 28 | throw new CustomError( 29 | error.message, 30 | ALLOW_HTTP_CODE.paramsError, 31 | ALLOW_HTTP_CODE.paramsError 32 | ); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/middleware/star.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | article_id: Joi.number(), 10 | comment_id: Joi.number(), 11 | from_user_id: Joi.number(), 12 | to_user_id: Joi.number(), 13 | }); 14 | 15 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 16 | try { 17 | const props = ctx.request.body; 18 | await schema.validateAsync(props, { 19 | abortEarly: false, 20 | allowUnknown: false, 21 | convert: false, // 如果为true,则尝试将值强制转换为所需类型(例如,将字符串转换为数字) 22 | }); 23 | await next(); 24 | } catch (error: any) { 25 | throw new CustomError( 26 | error.message, 27 | ALLOW_HTTP_CODE.paramsError, 28 | ALLOW_HTTP_CODE.paramsError 29 | ); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/middleware/tag.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | name: Joi.string().min(1).max(80), 10 | color: Joi.string().min(1).max(80), 11 | priority: [Joi.number(), null], 12 | }); 13 | 14 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 15 | try { 16 | const props = ctx.request.body; 17 | await schema.validateAsync(props, { 18 | abortEarly: false, 19 | allowUnknown: false, 20 | convert: false, 21 | }); 22 | await next(); 23 | } catch (error: any) { 24 | throw new CustomError( 25 | error.message, 26 | ALLOW_HTTP_CODE.paramsError, 27 | ALLOW_HTTP_CODE.paramsError 28 | ); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/middleware/theme.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | model: Joi.string().min(1).max(100), 9 | key: Joi.string().min(1).max(100), 10 | value: Joi.string().min(1).max(100), 11 | lang: Joi.string().min(1).max(100), 12 | desc: Joi.string().min(1).max(100), 13 | }); 14 | // .required(); 15 | // .error(new Error('参数')); 16 | // .xor('id'); // 定义一组键之间的排他关系,其中一个是必需的,但不是同时需要 17 | 18 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 19 | try { 20 | const prop = ctx.request.body; 21 | await schema.validateAsync(prop, { 22 | abortEarly: false, // when true,在第一个错误时停止验证,否则返回找到的所有错误。默认为true. 23 | allowUnknown: false, // 当true,允许对象包含被忽略的未知键。默认为false. 24 | presence: 'required', // schema加上required()或者设置presence: 'required'。防止prop为undefined时也能通过验证 25 | }); 26 | await next(); 27 | } catch (error: any) { 28 | throw new CustomError( 29 | error.message, 30 | ALLOW_HTTP_CODE.paramsError, 31 | ALLOW_HTTP_CODE.paramsError 32 | ); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/middleware/type.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | name: Joi.string().min(1).max(80), 10 | priority: [Joi.number(), null], 11 | }); 12 | 13 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 14 | try { 15 | const props = ctx.request.body; 16 | await schema.validateAsync(props, { 17 | abortEarly: false, 18 | allowUnknown: false, 19 | convert: false, 20 | }); 21 | await next(); 22 | } catch (error: any) { 23 | throw new CustomError( 24 | error.message, 25 | ALLOW_HTTP_CODE.paramsError, 26 | ALLOW_HTTP_CODE.paramsError 27 | ); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/middleware/user.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | username: Joi.string().min(2).max(50), 10 | password: Joi.string().min(6).max(50), 11 | // username: Joi.string() 12 | // .pattern(/[0-9a-zA-Z_]{6,12}$/) 13 | // .required(), 14 | // password: Joi.string() 15 | // .pattern(/(?![0-9]+$)(?![a-zA-Z]+$)(?![_]+$)[0-9a-zA-A_]{8,16}/) 16 | // .required(), 17 | desc: [Joi.string().min(1).max(100), '', null], 18 | avatar: [Joi.string().min(1).max(300), '', null], 19 | email: [Joi.string().min(1).max(100), '', null], 20 | code: [Joi.string(), '', null], 21 | status: [1, 2], 22 | exp: Joi.number(), 23 | user_roles: Joi.array().items(Joi.number()), 24 | }); 25 | 26 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 27 | try { 28 | const prop = ctx.request.body; 29 | await schema.validateAsync(prop, { 30 | abortEarly: false, // when true,在第一个错误时停止验证,否则返回找到的所有错误。默认为true. 31 | allowUnknown: false, // 当true,允许对象包含被忽略的未知键。默认为false. 32 | // presence: 'required', // schema加上required()或者设置presence: 'required'。防止prop为undefined时也能通过验证 33 | convert: false, 34 | }); 35 | await next(); 36 | } catch (error: any) { 37 | throw new CustomError( 38 | error.message, 39 | ALLOW_HTTP_CODE.paramsError, 40 | ALLOW_HTTP_CODE.paramsError 41 | ); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/middleware/utils.middleware.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | 3 | import { authJwt } from '@/app/auth/authJwt'; 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | export const verifyIdOne = async (ctx: ParameterizedContext, next) => { 8 | const { code, userInfo, message } = await authJwt(ctx); 9 | if (code !== ALLOW_HTTP_CODE.ok) { 10 | throw new CustomError(message, code, code); 11 | } 12 | if (userInfo!.id !== 1) { 13 | throw new CustomError( 14 | `只允许id=1的用户操作!`, 15 | ALLOW_HTTP_CODE.forbidden, 16 | ALLOW_HTTP_CODE.forbidden 17 | ); 18 | } else { 19 | await next(); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/middleware/visitorLog.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | user_id: Joi.number(), 9 | ip: [Joi.string().min(1).max(200), '', null], 10 | ip_data: [Joi.string().min(1).max(200), '', null], 11 | page_url: [Joi.string().min(1).max(400), '', null], 12 | status: [1, 2], 13 | }); 14 | 15 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 16 | try { 17 | const props = ctx.request.body; 18 | await schema.validateAsync(props, { 19 | abortEarly: false, 20 | allowUnknown: false, 21 | convert: false, 22 | }); 23 | await next(); 24 | } catch (error: any) { 25 | throw new CustomError( 26 | error.message, 27 | ALLOW_HTTP_CODE.paramsError, 28 | ALLOW_HTTP_CODE.paramsError 29 | ); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/middleware/works.middleware.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import { ParameterizedContext } from 'koa'; 3 | 4 | import { ALLOW_HTTP_CODE } from '@/constant'; 5 | import { CustomError } from '@/model/customError.model'; 6 | 7 | const schema = Joi.object({ 8 | id: Joi.number(), 9 | name: Joi.string().min(1).max(100), 10 | desc: [Joi.string().min(1).max(100), '', null], 11 | url: [Joi.string().min(1).max(300), '', null], 12 | bg_url: [Joi.string().min(1).max(300), '', null], 13 | priority: [Joi.number(), null], 14 | status: [1, 2], 15 | }); 16 | 17 | export const verifyProp = async (ctx: ParameterizedContext, next) => { 18 | try { 19 | const props = ctx.request.body; 20 | await schema.validateAsync(props, { 21 | abortEarly: false, 22 | allowUnknown: false, 23 | convert: false, 24 | }); 25 | await next(); 26 | } catch (error: any) { 27 | throw new CustomError( 28 | error.message, 29 | ALLOW_HTTP_CODE.paramsError, 30 | ALLOW_HTTP_CODE.paramsError 31 | ); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/model/article.model.ts: -------------------------------------------------------------------------------- 1 | // import Sequelize from 'sequelize';//这种写法没有提示。 2 | // import * as Sequelize from 'sequelize'; // 这种写法有提示。 3 | // import Sequelize = require('sequelize'); // 这种写法有提示。 4 | import { 5 | DataTypes, 6 | InferAttributes, 7 | InferCreationAttributes, 8 | Model, 9 | } from 'sequelize'; 10 | 11 | import sequelize from '@/config/mysql'; 12 | import { IArticle } from '@/interface'; 13 | import { initTable } from '@/utils'; 14 | 15 | // const Sequelize = require('sequelize'); 16 | 17 | interface ArticleModel 18 | extends Model< 19 | InferAttributes, 20 | InferCreationAttributes 21 | >, 22 | IArticle {} 23 | 24 | // https://sequelize.org/docs/v6/other-topics/typescript/#usage-of-sequelizedefine 25 | const model = sequelize.define( 26 | // 这将控制自动生成的foreignKey和关联命名的名称 27 | 'article', // 模型名称 28 | { 29 | id: { 30 | type: DataTypes.INTEGER, 31 | primaryKey: true, 32 | allowNull: false, 33 | autoIncrement: true, 34 | }, 35 | title: { 36 | type: DataTypes.STRING(100), 37 | allowNull: false, 38 | }, 39 | desc: { 40 | type: DataTypes.STRING(100), 41 | }, 42 | is_comment: { 43 | type: DataTypes.INTEGER, 44 | defaultValue: 1, // 1:开启评论 2:关闭评论 45 | }, 46 | priority: { 47 | type: DataTypes.INTEGER, 48 | }, 49 | status: { 50 | type: DataTypes.INTEGER, 51 | defaultValue: 2, // 1:已审核 2:未审核 52 | }, 53 | head_img: { 54 | type: DataTypes.STRING(500), 55 | }, 56 | content: { 57 | type: DataTypes.TEXT('medium'), 58 | allowNull: false, 59 | }, 60 | click: { 61 | type: DataTypes.INTEGER, 62 | defaultValue: 0, 63 | }, 64 | visit: { 65 | type: DataTypes.INTEGER, 66 | defaultValue: 0, 67 | }, 68 | }, 69 | { 70 | paranoid: true, 71 | freezeTableName: true, // 你可以使用 freezeTableName: true 参数停止 Sequelize 执行自动复数化. 这样,Sequelize 将推断表名称等于模型名称, 72 | createdAt: 'created_at', 73 | updatedAt: 'updated_at', 74 | deletedAt: 'deleted_at', 75 | } 76 | ); 77 | 78 | function updateVisit() { 79 | sequelize.query(`UPDATE ${model.name} SET visit = click;`); 80 | } 81 | 82 | initTable({ model, sequelize }).then(() => { 83 | // updateVisit(); 84 | }); 85 | 86 | export default model; 87 | -------------------------------------------------------------------------------- /src/model/articleTag.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IArticleTag } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface ArticleTagModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IArticleTag {} 18 | 19 | const model = sequelize.define( 20 | 'article_tag', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | article_id: { 29 | type: DataTypes.INTEGER, 30 | allowNull: false, 31 | }, 32 | tag_id: { 33 | type: DataTypes.INTEGER, 34 | allowNull: false, 35 | }, 36 | }, 37 | { 38 | indexes: [ 39 | { 40 | name: 'article_id', 41 | fields: ['article_id'], 42 | }, 43 | { 44 | name: 'tag_id', 45 | fields: ['tag_id'], 46 | }, 47 | ], 48 | paranoid: true, 49 | freezeTableName: true, 50 | createdAt: 'created_at', 51 | updatedAt: 'updated_at', 52 | deletedAt: 'deleted_at', 53 | } 54 | ); 55 | 56 | initTable({ model, sequelize }); 57 | export default model; 58 | -------------------------------------------------------------------------------- /src/model/articleType.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IArticleType } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface ArticleTypeModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IArticleType {} 18 | 19 | const model = sequelize.define( 20 | 'article_type', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | article_id: { 29 | type: DataTypes.INTEGER, 30 | allowNull: false, 31 | }, 32 | type_id: { 33 | type: DataTypes.INTEGER, 34 | allowNull: false, 35 | }, 36 | }, 37 | { 38 | indexes: [ 39 | { 40 | name: 'article_id', 41 | fields: ['article_id'], 42 | }, 43 | { 44 | name: 'type_id', 45 | fields: ['type_id'], 46 | }, 47 | ], 48 | paranoid: true, 49 | freezeTableName: true, 50 | createdAt: 'created_at', 51 | updatedAt: 'updated_at', 52 | deletedAt: 'deleted_at', 53 | } 54 | ); 55 | 56 | initTable({ model, sequelize }); 57 | export default model; 58 | -------------------------------------------------------------------------------- /src/model/auth.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IAuth } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface AuthModel 13 | extends Model, InferCreationAttributes>, 14 | IAuth {} 15 | 16 | const model = sequelize.define( 17 | 'auth', 18 | { 19 | id: { 20 | type: DataTypes.INTEGER, 21 | primaryKey: true, 22 | allowNull: false, 23 | autoIncrement: true, 24 | }, 25 | auth_name: { 26 | type: DataTypes.STRING(100), 27 | allowNull: false, 28 | }, 29 | auth_value: { 30 | type: DataTypes.STRING(100), 31 | allowNull: false, 32 | }, 33 | type: { 34 | type: DataTypes.INTEGER, // 1:默认,2:自定义 35 | }, 36 | priority: { 37 | type: DataTypes.INTEGER, 38 | }, 39 | p_id: { 40 | type: DataTypes.INTEGER, 41 | defaultValue: 0, 42 | }, 43 | }, 44 | { 45 | hooks: { 46 | /** 47 | * 诸如 bulkCreate, update 和 destroy 之类的方法一次编辑多个记录会触发: 48 | * beforeBulkCreate 49 | * beforeBulkUpdate 50 | * beforeBulkDestroy 51 | * afterBulkCreate 52 | * afterBulkUpdate 53 | * afterBulkDestroy 54 | * 默认情况下,类似 bulkCreate 的方法不会触发单独的 hook - 仅会触发批量 hook.(因为可能where的时候可能会改了几条记录) 但是,如果你还希望触发单个 hook,则可以将 { individualHooks: true } 55 | * 即如果destroy的话,不会触发afterDestroy,beforeDestroy等hooks,如果希望destroy触发单个hook的话,需要添加{ individualHooks: true } 56 | */ 57 | afterDestroy() { 58 | // beforeDestroy 和 afterDestroy hook 只会在具有 onDelete: 'CASCADE' 和 hooks: true 的关联上被调用. 59 | console.log('afterDestroy'); 60 | // @ts-ignore 61 | // const item = await instance.getAuth(); 62 | // item && item.destroy(); 63 | }, 64 | afterBulkDestroy() { 65 | console.log('afterBulkDestroy'); 66 | }, 67 | afterBulkUpdate() { 68 | console.log('afterBulkUpdate'); 69 | }, 70 | }, 71 | indexes: [ 72 | { 73 | name: 'p_id', 74 | fields: ['p_id'], 75 | }, 76 | ], 77 | paranoid: true, 78 | freezeTableName: true, 79 | createdAt: 'created_at', 80 | updatedAt: 'updated_at', 81 | deletedAt: 'deleted_at', 82 | } 83 | ); 84 | 85 | initTable({ model, sequelize }); 86 | export default model; 87 | -------------------------------------------------------------------------------- /src/model/backend.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IBackend } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface BackendModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IBackend {} 18 | 19 | const model = sequelize.define( 20 | 'backend', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | key: { 29 | type: DataTypes.STRING(100), 30 | allowNull: false, 31 | }, 32 | value: { 33 | type: DataTypes.TEXT, 34 | allowNull: false, 35 | }, 36 | desc: { 37 | type: DataTypes.STRING(100), 38 | }, 39 | type: { 40 | type: DataTypes.STRING(100), 41 | allowNull: false, 42 | }, 43 | }, 44 | { 45 | paranoid: true, 46 | freezeTableName: true, 47 | createdAt: 'created_at', 48 | updatedAt: 'updated_at', 49 | deletedAt: 'deleted_at', 50 | } 51 | ); 52 | 53 | initTable({ model, sequelize }); 54 | export default model; 55 | -------------------------------------------------------------------------------- /src/model/blacklist.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IBlacklist } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface BlacklistModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IBlacklist {} 18 | 19 | const model = sequelize.define( 20 | 'blacklist', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | ip: { 29 | type: DataTypes.STRING(500), 30 | }, 31 | user_id: { 32 | type: DataTypes.INTEGER, 33 | }, 34 | type: { 35 | type: DataTypes.INTEGER, // 禁用类型,1:频繁操作;2:管理员手动禁用 36 | }, 37 | msg: { 38 | type: DataTypes.STRING(500), 39 | }, 40 | user_agent: { 41 | // qq浏览器的user_agent能达到四百多字符 42 | type: DataTypes.STRING(500), 43 | }, 44 | }, 45 | { 46 | paranoid: true, 47 | freezeTableName: true, 48 | createdAt: 'created_at', 49 | updatedAt: 'updated_at', 50 | deletedAt: 'deleted_at', 51 | } 52 | ); 53 | 54 | initTable({ model, sequelize }); 55 | export default model; 56 | -------------------------------------------------------------------------------- /src/model/buryingPoint.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IBuryingPoint } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface BuryingPointModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IBuryingPoint {} 18 | 19 | const model = sequelize.define( 20 | 'burying_point', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | ip: { 29 | type: DataTypes.STRING(500), 30 | }, 31 | article_id: { 32 | type: DataTypes.INTEGER, 33 | }, 34 | user_id: { 35 | type: DataTypes.INTEGER, 36 | }, 37 | user_agent: { 38 | // qq浏览器的user_agent能达到四百多字符 39 | type: DataTypes.STRING(500), 40 | }, 41 | field_a: { 42 | type: DataTypes.STRING(500), 43 | }, 44 | field_b: { 45 | type: DataTypes.STRING(500), 46 | }, 47 | field_c: { 48 | type: DataTypes.STRING(500), 49 | }, 50 | field_d: { 51 | type: DataTypes.STRING(500), 52 | }, 53 | field_e: { 54 | type: DataTypes.STRING(500), 55 | }, 56 | field_f: { 57 | type: DataTypes.STRING(500), 58 | }, 59 | field_g: { 60 | type: DataTypes.STRING(500), 61 | }, 62 | remark: { 63 | type: DataTypes.STRING, 64 | }, 65 | }, 66 | { 67 | paranoid: true, 68 | freezeTableName: true, 69 | createdAt: 'created_at', 70 | updatedAt: 'updated_at', 71 | deletedAt: 'deleted_at', 72 | } 73 | ); 74 | 75 | function renameColumn() { 76 | sequelize.query( 77 | `ALTER TABLE ${model.name} RENAME COLUMN extend_field_a TO field_a;` 78 | ); 79 | sequelize.query( 80 | `ALTER TABLE ${model.name} RENAME COLUMN extend_field_b TO field_b;` 81 | ); 82 | sequelize.query( 83 | `ALTER TABLE ${model.name} RENAME COLUMN extend_field_c TO field_c;` 84 | ); 85 | sequelize.query( 86 | `ALTER TABLE ${model.name} RENAME COLUMN extend_field_d TO field_d;` 87 | ); 88 | sequelize.query( 89 | `ALTER TABLE ${model.name} RENAME COLUMN extend_field_e TO field_e;` 90 | ); 91 | sequelize.query( 92 | `ALTER TABLE ${model.name} RENAME COLUMN extend_field_f TO field_f;` 93 | ); 94 | sequelize.query( 95 | `ALTER TABLE ${model.name} RENAME COLUMN extend_field_g TO field_g;` 96 | ); 97 | } 98 | 99 | initTable({ model, sequelize }).then(() => { 100 | // renameColumn(); 101 | }); 102 | 103 | export default model; 104 | -------------------------------------------------------------------------------- /src/model/comment.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IComment } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface CommentModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IComment {} 18 | 19 | const model = sequelize.define( 20 | 'comment', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | article_id: { 29 | type: DataTypes.INTEGER, 30 | defaultValue: -1, // -1:留言板的评论 非-1:文章的评论 31 | }, 32 | parent_comment_id: { 33 | type: DataTypes.INTEGER, 34 | defaultValue: -1, // -1:楼主 非-1: 35 | }, 36 | reply_comment_id: { 37 | type: DataTypes.INTEGER, 38 | defaultValue: -1, // -1:楼主 非-1: 39 | }, 40 | from_user_id: { 41 | type: DataTypes.INTEGER, 42 | allowNull: false, 43 | }, 44 | to_user_id: { 45 | type: DataTypes.INTEGER, 46 | defaultValue: -1, // -1:楼主 非-1:在楼主下回复的用户 47 | }, 48 | content: { 49 | type: DataTypes.TEXT, 50 | }, 51 | children_comment_total: { 52 | type: DataTypes.INTEGER, 53 | defaultValue: 0, 54 | }, 55 | star_total: { 56 | type: DataTypes.INTEGER, 57 | defaultValue: 0, 58 | }, 59 | status: { 60 | type: DataTypes.INTEGER, 61 | defaultValue: 1, // 状态:1显示;2不显示 62 | }, 63 | user_agent: { 64 | // qq浏览器的user_agent能达到四百多字符 65 | type: DataTypes.STRING(500), 66 | }, 67 | priority: { 68 | type: DataTypes.INTEGER, 69 | }, 70 | ip: { 71 | type: DataTypes.STRING(500), 72 | }, 73 | ip_data: { 74 | type: DataTypes.STRING(500), 75 | }, 76 | }, 77 | { 78 | indexes: [ 79 | { 80 | name: 'article_id', 81 | fields: ['article_id'], 82 | }, 83 | { 84 | name: 'parent_comment_id', 85 | fields: ['parent_comment_id'], 86 | }, 87 | { 88 | name: 'reply_comment_id', 89 | fields: ['reply_comment_id'], 90 | }, 91 | { 92 | name: 'from_user_id', 93 | fields: ['from_user_id'], 94 | }, 95 | { 96 | name: 'to_user_id', 97 | fields: ['to_user_id'], 98 | }, 99 | ], 100 | paranoid: true, 101 | freezeTableName: true, 102 | createdAt: 'created_at', 103 | updatedAt: 'updated_at', 104 | deletedAt: 'deleted_at', 105 | } 106 | ); 107 | 108 | initTable({ model, sequelize }); 109 | export default model; 110 | -------------------------------------------------------------------------------- /src/model/customError.model.ts: -------------------------------------------------------------------------------- 1 | import { ALLOW_HTTP_CODE, ERROR_HTTP_CODE } from '@/constant'; 2 | 3 | export class CustomError extends Error { 4 | statusCode: number; 5 | 6 | errorCode: number; 7 | 8 | constructor( 9 | message = '服务器错误', 10 | statusCode = ALLOW_HTTP_CODE.serverError, 11 | errorCode = ERROR_HTTP_CODE.serverError 12 | ) { 13 | super(); 14 | this.message = message; 15 | this.statusCode = statusCode; 16 | this.errorCode = errorCode; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/model/dayData.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IDayData } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface DayDataModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IDayData {} 18 | 19 | const model = sequelize.define( 20 | 'day_data', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | today: { 29 | type: DataTypes.DATE, 30 | }, 31 | }, 32 | { 33 | // indexes: [ 34 | // { 35 | // name: 'today', 36 | // fields: ['today'], 37 | // }, 38 | // ], 39 | paranoid: true, 40 | freezeTableName: true, 41 | createdAt: 'created_at', 42 | updatedAt: 'updated_at', 43 | deletedAt: 'deleted_at', 44 | } 45 | ); 46 | 47 | initTable({ model, sequelize }); 48 | export default model; 49 | -------------------------------------------------------------------------------- /src/model/emailUser.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IEmailUser } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface EmailUserModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IEmailUser {} 18 | 19 | const model = sequelize.define( 20 | 'email_user', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | email: { 29 | type: DataTypes.STRING(100), 30 | }, 31 | }, 32 | { 33 | paranoid: true, 34 | freezeTableName: true, 35 | createdAt: 'created_at', 36 | updatedAt: 'updated_at', 37 | deletedAt: 'deleted_at', 38 | } 39 | ); 40 | 41 | initTable({ model, sequelize }); 42 | export default model; 43 | -------------------------------------------------------------------------------- /src/model/frontend.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IFrontend } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface FrontendModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IFrontend {} 18 | 19 | const model = sequelize.define( 20 | 'frontend', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | // 废弃 29 | // frontend_login: { 30 | // type: DataTypes.INTEGER, 31 | // defaultValue: 1, // 1:关闭站内登录 2:开启站内登录 32 | // }, 33 | // 废弃 34 | // frontend_register: { 35 | // type: DataTypes.INTEGER, 36 | // defaultValue: 1, // 1:关闭站内注册 2:开启站内注册 37 | // }, 38 | key: { 39 | type: DataTypes.STRING(300), 40 | allowNull: false, 41 | }, 42 | value: { 43 | type: DataTypes.TEXT, 44 | allowNull: false, 45 | }, 46 | desc: { 47 | type: DataTypes.STRING(300), 48 | }, 49 | type: { 50 | type: DataTypes.STRING(300), 51 | allowNull: false, 52 | }, 53 | }, 54 | { 55 | paranoid: true, 56 | freezeTableName: true, 57 | createdAt: 'created_at', 58 | updatedAt: 'updated_at', 59 | deletedAt: 'deleted_at', 60 | } 61 | ); 62 | 63 | initTable({ model, sequelize }); 64 | export default model; 65 | -------------------------------------------------------------------------------- /src/model/githubUser.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IGithubUser } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface GithubUserModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IGithubUser {} 18 | 19 | const model = sequelize.define( 20 | 'github_user', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | client_id: { 29 | // https://github.com/settings/applications里面的Client ID 30 | type: DataTypes.STRING(500), 31 | }, 32 | login: { 33 | type: DataTypes.STRING(500), 34 | }, 35 | github_id: { 36 | type: DataTypes.INTEGER, 37 | }, 38 | node_id: { 39 | type: DataTypes.STRING(500), 40 | }, 41 | avatar_url: { 42 | type: DataTypes.STRING(500), 43 | }, 44 | gravatar_id: { 45 | type: DataTypes.STRING(500), 46 | }, 47 | url: { 48 | type: DataTypes.STRING(500), 49 | }, 50 | html_url: { 51 | type: DataTypes.STRING(500), 52 | }, 53 | type: { 54 | type: DataTypes.STRING(500), 55 | }, 56 | site_admin: { 57 | type: DataTypes.STRING(500), 58 | }, 59 | name: { 60 | // 用户的新名称。 61 | type: DataTypes.STRING(500), 62 | }, 63 | company: { 64 | // 用户的新公司。 65 | type: DataTypes.STRING(500), 66 | }, 67 | blog: { 68 | // 用户的新博客 URL。 69 | type: DataTypes.STRING(500), 70 | }, 71 | location: { 72 | // 用户的新位置。 73 | type: DataTypes.STRING(500), 74 | }, 75 | email: { 76 | // 用户公开可见的电子邮件地址。 77 | type: DataTypes.STRING(500), 78 | }, 79 | hireable: { 80 | // 用户的新招聘可用性。 81 | type: DataTypes.STRING(500), 82 | }, 83 | bio: { 84 | // 用户的新短传。 85 | type: DataTypes.STRING(500), 86 | }, 87 | twitter_username: { 88 | // 用户的新 Twitter 用户名。 89 | type: DataTypes.STRING(500), 90 | }, 91 | public_repos: { 92 | type: DataTypes.INTEGER, 93 | }, 94 | public_gists: { 95 | type: DataTypes.INTEGER, 96 | }, 97 | followers: { 98 | type: DataTypes.INTEGER, 99 | }, 100 | following: { 101 | type: DataTypes.INTEGER, 102 | }, 103 | github_created_at: { 104 | type: DataTypes.STRING(500), 105 | }, 106 | github_updated_at: { 107 | type: DataTypes.STRING(500), 108 | }, 109 | private_gists: { 110 | type: DataTypes.INTEGER, 111 | }, 112 | total_private_repos: { 113 | type: DataTypes.INTEGER, 114 | }, 115 | owned_private_repos: { 116 | type: DataTypes.INTEGER, 117 | }, 118 | disk_usage: { 119 | type: DataTypes.INTEGER, 120 | }, 121 | collaborators: { 122 | type: DataTypes.INTEGER, 123 | }, 124 | two_factor_authentication: { 125 | type: DataTypes.STRING(500), 126 | }, 127 | }, 128 | { 129 | paranoid: true, 130 | freezeTableName: true, 131 | createdAt: 'created_at', 132 | updatedAt: 'updated_at', 133 | deletedAt: 'deleted_at', 134 | } 135 | ); 136 | 137 | initTable({ model, sequelize }); 138 | export default model; 139 | -------------------------------------------------------------------------------- /src/model/interaction.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IInteraction } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface IInteractionModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IInteraction {} 18 | 19 | const model = sequelize.define( 20 | 'interaction', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | // ip 29 | ip: { 30 | type: DataTypes.STRING(500), 31 | }, 32 | // ip信息 33 | ip_data: { 34 | type: DataTypes.STRING(500), 35 | }, 36 | // 用户类型 37 | user_type: { 38 | type: DataTypes.INTEGER, 39 | }, 40 | // 用户信息 41 | user_info: { 42 | type: DataTypes.STRING(500), 43 | }, 44 | // 消息类型 45 | type: { 46 | type: DataTypes.STRING(500), 47 | }, 48 | // 消息内容 49 | value: { 50 | type: DataTypes.STRING(500), 51 | }, 52 | }, 53 | { 54 | paranoid: true, 55 | freezeTableName: true, 56 | createdAt: 'created_at', 57 | updatedAt: 'updated_at', 58 | deletedAt: 'deleted_at', 59 | } 60 | ); 61 | 62 | function renameColumn() { 63 | // sequelize.query( 64 | // `ALTER TABLE ${model.name} RENAME COLUMN ip_info TO ip_data;` 65 | // ); 66 | sequelize.query(`ALTER TABLE ${model.name} RENAME COLUMN client_ip TO ip;`); 67 | sequelize.query(`ALTER TABLE ${model.name} RENAME COLUMN client TO ip_data;`); 68 | } 69 | 70 | initTable({ model, sequelize }).then(() => { 71 | // renameColumn(); 72 | }); 73 | 74 | export default model; 75 | -------------------------------------------------------------------------------- /src/model/interactionStatis.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IInteractionStatis } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface IInteractionStatisModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IInteractionStatis {} 18 | 19 | const model = sequelize.define( 20 | 'interaction_static', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | key: { 29 | type: DataTypes.STRING(500), 30 | allowNull: false, 31 | }, 32 | value: { 33 | type: DataTypes.STRING(500), 34 | allowNull: false, 35 | }, 36 | desc: { 37 | type: DataTypes.STRING(500), 38 | }, 39 | type: { 40 | type: DataTypes.STRING(500), 41 | }, 42 | }, 43 | { 44 | paranoid: true, 45 | freezeTableName: true, 46 | createdAt: 'created_at', 47 | updatedAt: 'updated_at', 48 | deletedAt: 'deleted_at', 49 | } 50 | ); 51 | 52 | initTable({ model, sequelize }); 53 | export default model; 54 | -------------------------------------------------------------------------------- /src/model/link.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { ILink } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface LinkModel 13 | extends Model, InferCreationAttributes>, 14 | ILink {} 15 | 16 | const model = sequelize.define( 17 | 'link', 18 | { 19 | id: { 20 | type: DataTypes.INTEGER, 21 | primaryKey: true, 22 | allowNull: false, 23 | autoIncrement: true, 24 | }, 25 | email: { 26 | type: DataTypes.STRING(100), 27 | }, 28 | name: { 29 | type: DataTypes.STRING(100), 30 | }, 31 | avatar: { 32 | type: DataTypes.STRING(150), 33 | }, 34 | desc: { 35 | type: DataTypes.STRING(100), 36 | }, 37 | url: { 38 | type: DataTypes.STRING(100), 39 | }, 40 | status: { 41 | type: DataTypes.INTEGER, 42 | defaultValue: 2, // 1:已审核 2:未审核 43 | }, 44 | priority: { 45 | type: DataTypes.INTEGER, 46 | }, 47 | }, 48 | { 49 | paranoid: true, 50 | freezeTableName: true, 51 | createdAt: 'created_at', 52 | updatedAt: 'updated_at', 53 | deletedAt: 'deleted_at', 54 | } 55 | ); 56 | 57 | initTable({ model, sequelize }); 58 | export default model; 59 | -------------------------------------------------------------------------------- /src/model/log.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { ILog } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface LogModel 13 | extends Model, InferCreationAttributes>, 14 | ILog {} 15 | 16 | const model = sequelize.define( 17 | 'log', 18 | { 19 | id: { 20 | type: DataTypes.INTEGER, 21 | primaryKey: true, 22 | allowNull: false, 23 | autoIncrement: true, 24 | }, 25 | user_id: { 26 | type: DataTypes.INTEGER, 27 | defaultValue: -1, // -1:游客;非-1:用户 28 | }, 29 | status: { 30 | type: DataTypes.INTEGER, 31 | defaultValue: 1, // 1:正常;2:禁止删除 32 | }, 33 | api_duration: { 34 | type: DataTypes.INTEGER, 35 | }, 36 | api_user_agent: { 37 | // qq浏览器的user_agent能达到四百多字符 38 | type: DataTypes.STRING(500), 39 | }, 40 | api_from: { 41 | type: DataTypes.INTEGER, // 1:前台 2:后台 42 | }, 43 | api_forwarded_for: { 44 | type: DataTypes.STRING(500), 45 | }, 46 | api_referer: { 47 | type: DataTypes.STRING(500), 48 | }, 49 | api_real_ip: { 50 | type: DataTypes.STRING(500), 51 | }, 52 | api_host: { 53 | type: DataTypes.STRING(500), 54 | }, 55 | api_hostname: { 56 | type: DataTypes.STRING(500), 57 | }, 58 | api_method: { 59 | type: DataTypes.STRING(50), 60 | }, 61 | api_path: { 62 | type: DataTypes.STRING(500), 63 | }, 64 | api_query: { 65 | type: DataTypes.TEXT, 66 | }, 67 | api_body: { 68 | type: DataTypes.TEXT, 69 | }, 70 | api_status_code: { 71 | type: DataTypes.INTEGER, 72 | }, 73 | api_error: { 74 | type: DataTypes.TEXT, 75 | }, 76 | api_err_msg: { 77 | type: DataTypes.TEXT, 78 | }, 79 | api_err_code: { 80 | type: DataTypes.INTEGER, 81 | }, 82 | }, 83 | { 84 | indexes: [ 85 | { 86 | name: 'user_id', 87 | fields: ['user_id'], 88 | }, 89 | ], 90 | paranoid: true, 91 | freezeTableName: true, 92 | createdAt: 'created_at', 93 | updatedAt: 'updated_at', 94 | deletedAt: 'deleted_at', 95 | } 96 | ); 97 | 98 | initTable({ model, sequelize }); 99 | export default model; 100 | -------------------------------------------------------------------------------- /src/model/monit.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IMonit } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface MonitModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IMonit {} 18 | 19 | const model = sequelize.define( 20 | 'monit', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | type: { 29 | type: DataTypes.INTEGER, 30 | }, 31 | info: { 32 | type: DataTypes.TEXT, 33 | }, 34 | }, 35 | { 36 | paranoid: true, 37 | freezeTableName: true, 38 | createdAt: 'created_at', 39 | updatedAt: 'updated_at', 40 | deletedAt: 'deleted_at', 41 | } 42 | ); 43 | 44 | initTable({ model, sequelize }); 45 | export default model; 46 | -------------------------------------------------------------------------------- /src/model/music.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IMusic } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface MusicModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IMusic {} 18 | 19 | const model = sequelize.define( 20 | 'music', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | name: { 29 | type: DataTypes.STRING(100), 30 | }, 31 | cover_pic: { 32 | type: DataTypes.STRING(300), 33 | }, 34 | author: { 35 | type: DataTypes.STRING(100), 36 | }, 37 | audio_url: { 38 | type: DataTypes.STRING(300), 39 | }, 40 | status: { 41 | type: DataTypes.INTEGER, 42 | defaultValue: 2, // 1:已审核 2:未审核 43 | }, 44 | priority: { 45 | type: DataTypes.INTEGER, 46 | }, 47 | }, 48 | { 49 | paranoid: true, 50 | freezeTableName: true, 51 | createdAt: 'created_at', 52 | updatedAt: 'updated_at', 53 | deletedAt: 'deleted_at', 54 | } 55 | ); 56 | 57 | initTable({ model, sequelize }); 58 | export default model; 59 | -------------------------------------------------------------------------------- /src/model/qiniuData.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IQiniuData } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface QiniuDataModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IQiniuData {} 18 | 19 | const model = sequelize.define( 20 | 'qiniu_data', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | user_id: { 29 | type: DataTypes.INTEGER, 30 | }, 31 | prefix: { 32 | type: DataTypes.STRING(100), 33 | }, 34 | bucket: { 35 | type: DataTypes.STRING(100), 36 | }, 37 | qiniu_key: { 38 | type: DataTypes.STRING(200), 39 | }, 40 | qiniu_hash: { 41 | type: DataTypes.STRING(100), 42 | }, 43 | qiniu_fsize: { 44 | type: DataTypes.STRING(100), 45 | }, 46 | qiniu_mimeType: { 47 | type: DataTypes.STRING(100), 48 | }, 49 | qiniu_putTime: { 50 | // 会返回:16511776862952760,超出DataTypes.INTEGER大小,可以使用DataTypes.BIGINT 51 | type: DataTypes.STRING(100), 52 | }, 53 | qiniu_type: { 54 | type: DataTypes.STRING(100), 55 | }, 56 | qiniu_status: { 57 | type: DataTypes.STRING(100), 58 | }, 59 | qiniu_md5: { 60 | type: DataTypes.STRING(100), 61 | }, 62 | }, 63 | { 64 | paranoid: true, 65 | freezeTableName: true, 66 | createdAt: 'created_at', 67 | updatedAt: 'updated_at', 68 | deletedAt: 'deleted_at', 69 | } 70 | ); 71 | 72 | initTable({ model, sequelize }); 73 | export default model; 74 | -------------------------------------------------------------------------------- /src/model/qqUser.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IQqUser } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface QqUserModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IQqUser {} 18 | 19 | const model = sequelize.define( 20 | 'qq_user', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | client_id: { 29 | // 其实就是appid 30 | type: DataTypes.INTEGER, 31 | }, 32 | openid: { 33 | type: DataTypes.STRING(500), 34 | }, 35 | unionid: { 36 | type: DataTypes.STRING(500), 37 | }, 38 | // https://wiki.connect.qq.com/%E4%BD%BF%E7%94%A8authorization_code%E8%8E%B7%E5%8F%96access_token 39 | // access_token: { 40 | // type: DataTypes.TEXT('long'), 41 | // }, 42 | // expires_in: { 43 | // // 该access token的有效期,单位为秒。 44 | // type: DataTypes.STRING(500), 45 | // }, 46 | // refresh_token: { 47 | // type: DataTypes.TEXT('long'), 48 | // }, 49 | nickname: { 50 | type: DataTypes.STRING(500), 51 | }, 52 | figureurl: { 53 | // 大小为30×30像素的QQ空间头像URL。 54 | type: DataTypes.STRING(500), 55 | }, 56 | figureurl_1: { 57 | // 大小为50×50像素的QQ空间头像URL。 58 | type: DataTypes.STRING(500), 59 | }, 60 | figureurl_2: { 61 | // 大小为100×100像素的QQ空间头像URL。 62 | type: DataTypes.STRING(500), 63 | }, 64 | figureurl_qq_1: { 65 | // 大小为40×40像素的QQ头像URL。 66 | type: DataTypes.STRING(500), 67 | }, 68 | figureurl_qq_2: { 69 | // 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100x100的头像,但40x40像素则是一定会有 70 | type: DataTypes.STRING(500), 71 | }, 72 | constellation: { 73 | // 星座 74 | type: DataTypes.STRING(500), 75 | }, 76 | gender: { 77 | type: DataTypes.STRING(500), 78 | }, 79 | city: { 80 | type: DataTypes.STRING(500), 81 | }, 82 | province: { 83 | type: DataTypes.STRING(500), 84 | }, 85 | year: { 86 | type: DataTypes.STRING(500), 87 | }, 88 | }, 89 | { 90 | indexes: [ 91 | { 92 | name: 'client_id', 93 | fields: ['client_id'], 94 | }, 95 | { 96 | name: 'openid', 97 | fields: ['openid'], 98 | }, 99 | { 100 | name: 'unionid', 101 | fields: ['unionid'], 102 | }, 103 | ], 104 | paranoid: true, 105 | freezeTableName: true, 106 | createdAt: 'created_at', 107 | updatedAt: 'updated_at', 108 | deletedAt: 'deleted_at', 109 | } 110 | ); 111 | 112 | initTable({ model, sequelize }); 113 | export default model; 114 | -------------------------------------------------------------------------------- /src/model/role.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IRole } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface RoleModel 13 | extends Model, InferCreationAttributes>, 14 | IRole {} 15 | 16 | const model = sequelize.define( 17 | 'role', 18 | { 19 | id: { 20 | type: DataTypes.INTEGER, 21 | primaryKey: true, 22 | allowNull: false, 23 | autoIncrement: true, 24 | }, 25 | role_name: { 26 | type: DataTypes.STRING(300), 27 | }, 28 | role_value: { 29 | type: DataTypes.STRING(300), 30 | }, 31 | type: { 32 | type: DataTypes.INTEGER, // 1:默认,2:自定义 33 | }, 34 | priority: { 35 | type: DataTypes.INTEGER, 36 | }, 37 | p_id: { 38 | type: DataTypes.INTEGER, 39 | defaultValue: 0, // 0:最外层的父级 40 | }, 41 | }, 42 | { 43 | indexes: [ 44 | { 45 | name: 'p_id', 46 | fields: ['p_id'], 47 | }, 48 | ], 49 | paranoid: true, 50 | freezeTableName: true, 51 | createdAt: 'created_at', 52 | updatedAt: 'updated_at', 53 | deletedAt: 'deleted_at', 54 | } 55 | ); 56 | 57 | initTable({ model, sequelize }); 58 | export default model; 59 | -------------------------------------------------------------------------------- /src/model/roleAuth.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IRoleAuth } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface RoleAuthModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IRoleAuth {} 18 | 19 | const model = sequelize.define( 20 | 'role_auth', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | role_id: { 29 | type: DataTypes.INTEGER, 30 | }, 31 | auth_id: { 32 | type: DataTypes.INTEGER, 33 | }, 34 | }, 35 | { 36 | indexes: [ 37 | { 38 | name: 'role_id', 39 | fields: ['role_id'], 40 | }, 41 | { 42 | name: 'auth_id', 43 | fields: ['auth_id'], 44 | }, 45 | ], 46 | paranoid: true, 47 | freezeTableName: true, 48 | createdAt: 'created_at', 49 | updatedAt: 'updated_at', 50 | deletedAt: 'deleted_at', 51 | } 52 | ); 53 | 54 | initTable({ model, sequelize }); 55 | export default model; 56 | -------------------------------------------------------------------------------- /src/model/star.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IStar } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface StarModel 13 | extends Model, InferCreationAttributes>, 14 | IStar {} 15 | 16 | const model = sequelize.define( 17 | 'star', 18 | { 19 | id: { 20 | type: DataTypes.INTEGER, 21 | primaryKey: true, 22 | allowNull: false, 23 | autoIncrement: true, 24 | }, 25 | article_id: { 26 | type: DataTypes.INTEGER, 27 | defaultValue: -1, // -1:给用户的star 非-1:给这篇文章的star 28 | }, 29 | comment_id: { 30 | type: DataTypes.INTEGER, 31 | defaultValue: -1, // -1:给文章的star 非-1:给这条评论的star 32 | }, 33 | from_user_id: { 34 | type: DataTypes.INTEGER, 35 | }, 36 | to_user_id: { 37 | type: DataTypes.INTEGER, 38 | defaultValue: -1, // -1:给文章的star 非-1:给这个用户star 39 | }, 40 | }, 41 | { 42 | indexes: [ 43 | { 44 | name: 'article_id', 45 | fields: ['article_id'], 46 | }, 47 | { 48 | name: 'comment_id', 49 | fields: ['comment_id'], 50 | }, 51 | { 52 | name: 'from_user_id', 53 | fields: ['from_user_id'], 54 | }, 55 | { 56 | name: 'to_user_id', 57 | fields: ['to_user_id'], 58 | }, 59 | ], 60 | paranoid: true, 61 | freezeTableName: true, 62 | createdAt: 'created_at', 63 | updatedAt: 'updated_at', 64 | deletedAt: 'deleted_at', 65 | } 66 | ); 67 | 68 | initTable({ model, sequelize }); 69 | export default model; 70 | -------------------------------------------------------------------------------- /src/model/tag.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { ITag } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface TagModel 13 | extends Model, InferCreationAttributes>, 14 | ITag {} 15 | 16 | const model = sequelize.define( 17 | 'tag', 18 | { 19 | id: { 20 | type: DataTypes.INTEGER, 21 | primaryKey: true, 22 | allowNull: false, 23 | autoIncrement: true, 24 | }, 25 | name: { 26 | type: DataTypes.STRING(100), 27 | }, 28 | color: { 29 | type: DataTypes.STRING(100), 30 | }, 31 | priority: { 32 | type: DataTypes.INTEGER, // 权重 33 | }, 34 | }, 35 | { 36 | paranoid: true, 37 | freezeTableName: true, 38 | createdAt: 'created_at', 39 | updatedAt: 'updated_at', 40 | deletedAt: 'deleted_at', 41 | } 42 | ); 43 | 44 | initTable({ model, sequelize }); 45 | export default model; 46 | -------------------------------------------------------------------------------- /src/model/theme.model.ts: -------------------------------------------------------------------------------- 1 | // import Sequelize from 'sequelize';//这种写法没有提示。 2 | // import * as Sequelize from 'sequelize'; // 这种写法有提示。 3 | // import Sequelize = require('sequelize'); // 这种写法有提示。 4 | import { 5 | DataTypes, 6 | InferAttributes, 7 | InferCreationAttributes, 8 | Model, 9 | } from 'sequelize'; 10 | 11 | import sequelize from '@/config/mysql'; 12 | import { ITheme } from '@/interface'; 13 | import { initTable } from '@/utils'; 14 | 15 | // const Sequelize = require('sequelize'); 16 | interface ThemeModel 17 | extends Model< 18 | InferAttributes, 19 | InferCreationAttributes 20 | >, 21 | ITheme {} 22 | 23 | const model = sequelize.define( 24 | // 这将控制自动生成的foreignKey和关联命名的名称 25 | 'theme', 26 | { 27 | id: { 28 | type: DataTypes.INTEGER, 29 | primaryKey: true, 30 | allowNull: false, 31 | autoIncrement: true, 32 | }, 33 | model: { 34 | type: DataTypes.STRING(100), 35 | comment: '模块名', 36 | // unique: true, // 唯一约束,如果尝试插入已存在的model,将抛出 SequelizeUniqueConstraintError. 37 | }, 38 | key: { 39 | type: DataTypes.STRING(100), 40 | allowNull: false, 41 | comment: '变量key', 42 | }, 43 | value: { 44 | type: DataTypes.STRING(100), 45 | allowNull: false, 46 | comment: '变量value', 47 | // validate: { 48 | // len: [3, 100], 49 | // }, 50 | }, 51 | lang: { 52 | type: DataTypes.STRING(100), 53 | comment: '语言', 54 | // validate: { 55 | // max: 50, 56 | // }, 57 | }, 58 | desc: { 59 | type: DataTypes.STRING(100), 60 | comment: '简介', 61 | // validate: { 62 | // max: 50, 63 | // }, 64 | }, 65 | }, 66 | { 67 | paranoid: true, 68 | freezeTableName: true, 69 | createdAt: 'created_at', 70 | updatedAt: 'updated_at', 71 | deletedAt: 'deleted_at', 72 | } 73 | ); 74 | 75 | initTable({ model, sequelize }); 76 | export default model; 77 | -------------------------------------------------------------------------------- /src/model/thirdUser.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { THIRD_PLATFORM } from '@/constant'; 10 | import { IThirdUser } from '@/interface'; 11 | import { initTable } from '@/utils'; 12 | 13 | interface ThirdUserModel 14 | extends Model< 15 | InferAttributes, 16 | InferCreationAttributes 17 | >, 18 | IThirdUser {} 19 | 20 | const model = sequelize.define( 21 | 'third_user', 22 | { 23 | id: { 24 | type: DataTypes.INTEGER, 25 | primaryKey: true, 26 | allowNull: false, 27 | autoIncrement: true, 28 | }, 29 | user_id: { 30 | type: DataTypes.INTEGER, 31 | allowNull: false, 32 | }, 33 | third_user_id: { 34 | type: DataTypes.INTEGER, 35 | allowNull: false, 36 | }, 37 | third_platform: { 38 | type: DataTypes.INTEGER, 39 | allowNull: false, 40 | defaultValue: THIRD_PLATFORM.website, 41 | }, 42 | }, 43 | { 44 | indexes: [ 45 | { 46 | name: 'user_id', 47 | fields: ['user_id'], 48 | }, 49 | { 50 | name: 'third_user_id', 51 | fields: ['third_user_id'], 52 | }, 53 | ], 54 | paranoid: true, 55 | freezeTableName: true, 56 | createdAt: 'created_at', 57 | updatedAt: 'updated_at', 58 | deletedAt: 'deleted_at', 59 | } 60 | ); 61 | 62 | initTable({ model, sequelize }); 63 | export default model; 64 | -------------------------------------------------------------------------------- /src/model/type.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IType } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface TypeModel 13 | extends Model, InferCreationAttributes>, 14 | IType {} 15 | 16 | const model = sequelize.define( 17 | 'type', 18 | { 19 | id: { 20 | type: DataTypes.INTEGER, 21 | primaryKey: true, 22 | allowNull: false, 23 | autoIncrement: true, 24 | }, 25 | name: { 26 | type: DataTypes.STRING(100), 27 | }, 28 | priority: { 29 | type: DataTypes.INTEGER, 30 | }, 31 | }, 32 | { 33 | paranoid: true, 34 | freezeTableName: true, 35 | createdAt: 'created_at', 36 | updatedAt: 'updated_at', 37 | deletedAt: 'deleted_at', 38 | } 39 | ); 40 | 41 | initTable({ model, sequelize }); 42 | export default model; 43 | -------------------------------------------------------------------------------- /src/model/userArticle.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IUserArticle } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface UserArticleModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IUserArticle {} 18 | 19 | const model = sequelize.define( 20 | 'user_article', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | user_id: { 29 | type: DataTypes.INTEGER, 30 | allowNull: false, 31 | // references: { 32 | // model: userModel, 33 | // key: 'id', 34 | // }, 35 | }, 36 | article_id: { 37 | type: DataTypes.INTEGER, 38 | allowNull: false, 39 | // references: { 40 | // model: articleModel, 41 | // key: 'id', 42 | // }, 43 | }, 44 | }, 45 | { 46 | indexes: [ 47 | { 48 | name: 'user_id', 49 | fields: ['user_id'], 50 | }, 51 | { 52 | name: 'article_id', 53 | fields: ['article_id'], 54 | }, 55 | ], 56 | paranoid: true, 57 | freezeTableName: true, 58 | createdAt: 'created_at', 59 | updatedAt: 'updated_at', 60 | deletedAt: 'deleted_at', 61 | } 62 | ); 63 | 64 | initTable({ model, sequelize }); 65 | export default model; 66 | -------------------------------------------------------------------------------- /src/model/userRole.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IUserRole } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface UserRoleModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IUserRole {} 18 | 19 | const model = sequelize.define( 20 | 'user_role', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | user_id: { 29 | type: DataTypes.INTEGER, 30 | }, 31 | role_id: { 32 | type: DataTypes.INTEGER, 33 | }, 34 | }, 35 | { 36 | indexes: [ 37 | { 38 | name: 'user_id', 39 | fields: ['user_id'], 40 | }, 41 | { 42 | name: 'role_id', 43 | fields: ['role_id'], 44 | }, 45 | ], 46 | paranoid: true, 47 | freezeTableName: true, 48 | createdAt: 'created_at', 49 | updatedAt: 'updated_at', 50 | deletedAt: 'deleted_at', 51 | } 52 | ); 53 | 54 | initTable({ model, sequelize }); 55 | export default model; 56 | -------------------------------------------------------------------------------- /src/model/visitorLog.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IVisitorLog } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface VisitorLogModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IVisitorLog {} 18 | 19 | const model = sequelize.define( 20 | 'visitor_log', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | user_id: { 29 | type: DataTypes.INTEGER, 30 | defaultValue: -1, // -1:游客 非-1:用户 31 | }, 32 | ip: { 33 | type: DataTypes.STRING(500), 34 | }, 35 | ip_data: { 36 | type: DataTypes.STRING(500), 37 | }, 38 | page_url: { 39 | type: DataTypes.STRING(500), 40 | }, 41 | user_agent: { 42 | // qq浏览器的user_agent能达到四百多字符 43 | type: DataTypes.STRING(500), 44 | }, 45 | }, 46 | { 47 | indexes: [ 48 | { 49 | name: 'user_id', 50 | fields: ['user_id'], 51 | }, 52 | ], 53 | paranoid: true, 54 | freezeTableName: true, 55 | createdAt: 'created_at', 56 | updatedAt: 'updated_at', 57 | deletedAt: 'deleted_at', 58 | } 59 | ); 60 | 61 | initTable({ model, sequelize }); 62 | export default model; 63 | -------------------------------------------------------------------------------- /src/model/works.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataTypes, 3 | InferAttributes, 4 | InferCreationAttributes, 5 | Model, 6 | } from 'sequelize'; 7 | 8 | import sequelize from '@/config/mysql'; 9 | import { IWorks } from '@/interface'; 10 | import { initTable } from '@/utils'; 11 | 12 | interface WorksModel 13 | extends Model< 14 | InferAttributes, 15 | InferCreationAttributes 16 | >, 17 | IWorks {} 18 | 19 | const model = sequelize.define( 20 | 'works', 21 | { 22 | id: { 23 | type: DataTypes.INTEGER, 24 | primaryKey: true, 25 | allowNull: false, 26 | autoIncrement: true, 27 | }, 28 | name: { 29 | type: DataTypes.STRING(100), 30 | }, 31 | desc: { 32 | type: DataTypes.STRING(100), 33 | }, 34 | bg_url: { 35 | type: DataTypes.STRING(300), 36 | }, 37 | url: { 38 | type: DataTypes.STRING(300), 39 | }, 40 | priority: { 41 | type: DataTypes.INTEGER, 42 | }, 43 | status: { 44 | type: DataTypes.INTEGER, 45 | defaultValue: 1, // 1:已审核 2:未审核 46 | }, 47 | }, 48 | { 49 | paranoid: true, 50 | freezeTableName: true, 51 | createdAt: 'created_at', 52 | updatedAt: 'updated_at', 53 | deletedAt: 'deleted_at', 54 | } 55 | ); 56 | 57 | initTable({ model, sequelize }); 58 | export default model; 59 | -------------------------------------------------------------------------------- /src/public/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | hello world! 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/router/article.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import articleController from '@/controller/article.controller'; 4 | import { verifyProp } from '@/middleware/article.middleware'; 5 | 6 | const articleRouter = new Router({ prefix: '/article' }); 7 | 8 | // 压力测试 9 | // articleRouter.get('/test', articleController.test); 10 | 11 | // 点击了文章 12 | articleRouter.post('/click', articleController.click); 13 | 14 | // 访问了文章 15 | articleRouter.post('/visit', articleController.visit); 16 | 17 | // 文章列表 18 | articleRouter.get('/list', articleController.getList); 19 | 20 | // 搜索文章列表 21 | articleRouter.get('/keyword_list', articleController.getKeyWordList); 22 | 23 | // 查找文章 24 | articleRouter.get('/find/:id', articleController.find); 25 | 26 | // 新增文章 27 | articleRouter.post('/create', verifyProp, articleController.create); 28 | 29 | // 更新文章 30 | articleRouter.put('/update/:id', verifyProp, articleController.update); 31 | 32 | // 删除文章 33 | articleRouter.delete('/delete/:id', verifyProp, articleController.delete); 34 | 35 | export default articleRouter; 36 | -------------------------------------------------------------------------------- /src/router/articleTag.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import articleTagController from '@/controller/articleTag.controller'; 4 | import { verifyProp } from '@/middleware/articleTag.middleware'; 5 | 6 | const articleRouter = new Router({ prefix: '/article_tag' }); 7 | 8 | // 文章标签列表 9 | articleRouter.get('/list', articleTagController.getList); 10 | 11 | // 创建文章标签 12 | articleRouter.post('/create', verifyProp, articleTagController.create); 13 | 14 | export default articleRouter; 15 | -------------------------------------------------------------------------------- /src/router/auth.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import authController from '@/controller/auth.controller'; 4 | import { verifyProp } from '@/middleware/auth.middleware'; 5 | 6 | const authRouter = new Router({ prefix: '/auth' }); 7 | 8 | // DONE 权限列表(分页) 9 | authRouter.get('/list', authController.getList); 10 | 11 | // DONE 权限列表(不分页) 12 | authRouter.get('/all_list', authController.getAllList); 13 | 14 | // 获取所有权限(树型) 15 | authRouter.get('/get_tree_auth', authController.getTreeAuth); 16 | 17 | // 获取除了父级以外的所有权限(树型) 18 | authRouter.get('/get_tree_child_auth', authController.getTreeChildAuth); 19 | 20 | // 查找权限 21 | authRouter.get('/find/:id', authController.find); 22 | 23 | // 获取该权限的子权限(只找一层) 24 | authRouter.get('/get_child_auth/:id', authController.getChildAuth); 25 | 26 | // 获取该权限的子权限(递归查找所有) 27 | authRouter.get('/get_all_child_auth/:id', authController.getAllChildAuth); 28 | 29 | // 创建权限 30 | authRouter.post('/create', verifyProp, authController.create); 31 | 32 | // 更新权限 33 | authRouter.put('/update/:id', verifyProp, authController.update); 34 | 35 | // 删除权限 36 | authRouter.delete('/delete/:id', authController.delete); 37 | 38 | // 批量新增子权限 39 | authRouter.put( 40 | '/batch_add_child_auths', 41 | verifyProp, 42 | authController.batchAddChildAuths 43 | ); 44 | 45 | // 批量删除子权限 46 | authRouter.delete( 47 | '/batch_delete_child_auths', 48 | verifyProp, 49 | authController.batchDeleteChildAuths 50 | ); 51 | 52 | export default authRouter; 53 | -------------------------------------------------------------------------------- /src/router/backend.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import { betaError } from '@/app/auth/verifyEnv'; 4 | import backendController from '@/controller/backend.controller'; 5 | import { verifyIdOne } from '@/middleware/utils.middleware'; 6 | 7 | const backendRouter = new Router({ prefix: '/backend' }); 8 | 9 | backendRouter.get('/list', betaError, verifyIdOne, backendController.getList); 10 | 11 | backendRouter.get( 12 | '/detail', 13 | betaError, 14 | verifyIdOne, 15 | backendController.getDetail 16 | ); 17 | 18 | backendRouter.get('/find/:id', betaError, verifyIdOne, backendController.find); 19 | 20 | backendRouter.post('/create', betaError, verifyIdOne, backendController.create); 21 | 22 | backendRouter.post( 23 | '/exec_cmd', 24 | betaError, 25 | verifyIdOne, 26 | backendController.execCmd 27 | ); 28 | 29 | backendRouter.put( 30 | '/update/:id', 31 | betaError, 32 | verifyIdOne, 33 | backendController.update 34 | ); 35 | 36 | backendRouter.delete( 37 | '/delete/:id', 38 | betaError, 39 | verifyIdOne, 40 | backendController.delete 41 | ); 42 | 43 | export default backendRouter; 44 | -------------------------------------------------------------------------------- /src/router/blacklist.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import blacklistController from '@/controller/blacklist.controller'; 4 | 5 | const blacklistRouter = new Router({ prefix: '/blacklist' }); 6 | 7 | // 黑名单列表 8 | blacklistRouter.get('/list', blacklistController.getList); 9 | 10 | // 创建黑名单 11 | blacklistRouter.post('/create', blacklistController.create); 12 | 13 | // 查找黑名单 14 | blacklistRouter.get('/find/:id', blacklistController.find); 15 | 16 | // 更新黑名单 17 | blacklistRouter.put('/update/:id', blacklistController.update); 18 | 19 | // 删除黑名单 20 | blacklistRouter.delete('/delete/:id', blacklistController.delete); 21 | 22 | export default blacklistRouter; 23 | -------------------------------------------------------------------------------- /src/router/buryingPoint.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import buryingPointController from '@/controller/buryingPoint.controller'; 4 | 5 | const buryingPointRouter = new Router({ prefix: '/burying_point' }); 6 | 7 | buryingPointRouter.get('/list', buryingPointController.getList); 8 | 9 | buryingPointRouter.post('/create', buryingPointController.create); 10 | 11 | export default buryingPointRouter; 12 | -------------------------------------------------------------------------------- /src/router/comment.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import commentController from '@/controller/comment.controller'; 4 | import { verifyProp } from '@/middleware/comment.middleware'; 5 | 6 | const commentRouter = new Router({ prefix: '/comment' }); 7 | 8 | // 评论列表 9 | commentRouter.get('/list', commentController.getList); 10 | 11 | // 文章评论列表 12 | commentRouter.get( 13 | '/article/:article_id', 14 | commentController.getArticleCommentList 15 | ); 16 | 17 | // 留言板评论列表 18 | commentRouter.get('/comment', commentController.getCommentList); 19 | 20 | // 子评论列表 21 | commentRouter.get('/child_comment', commentController.getChildrenCommentList); 22 | 23 | // 父评论的所有子评论(不分页) 24 | commentRouter.get( 25 | '/child_comment/:parent_comment_id', 26 | commentController.getAllChildrenComment 27 | ); 28 | 29 | // 创建评论 30 | commentRouter.post('/create', verifyProp, commentController.create); 31 | 32 | // 查找评论 33 | commentRouter.get('/find/:id', commentController.find); 34 | 35 | // 更新评论 36 | commentRouter.put('/update/:id', commentController.update); 37 | 38 | // 删除评论 39 | commentRouter.delete('/delete/:id', commentController.delete); 40 | 41 | export default commentRouter; 42 | -------------------------------------------------------------------------------- /src/router/csrf.router.ts: -------------------------------------------------------------------------------- 1 | import { ParameterizedContext } from 'koa'; 2 | import Router from 'koa-router'; 3 | 4 | import successHandler from '@/app/handler/success-handle'; 5 | 6 | const csrfRouter = new Router({ prefix: '/csrf' }); 7 | 8 | csrfRouter.get('/get', (ctx: ParameterizedContext, next) => { 9 | successHandler({ ctx, data: 'ok' }); 10 | return next(); 11 | }); 12 | 13 | export default csrfRouter; 14 | -------------------------------------------------------------------------------- /src/router/emailUser.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import emailController from '@/controller/emailUser.controller'; 4 | import { verifyProp } from '@/middleware/emailUser.middleware'; 5 | 6 | const emailRouter = new Router({ prefix: '/email_user' }); 7 | 8 | // 查找邮箱 9 | emailRouter.get('/find', emailController.find); // verifyProp其实只会对body参数做校验,这里是get请求,因此就不判断了 10 | 11 | // 发送邮箱登录验证码 12 | emailRouter.post('/send_login_code', verifyProp, emailController.sendLoginCode); 13 | 14 | // 邮箱登录 15 | emailRouter.post('/login', verifyProp, emailController.login); 16 | 17 | // 发送邮箱注册验证码 18 | emailRouter.post( 19 | '/send_register_code', 20 | verifyProp, 21 | emailController.sendRegisterCode 22 | ); 23 | 24 | // 邮箱注册 25 | emailRouter.post('/register', verifyProp, emailController.register); 26 | 27 | // 发送绑定邮箱验证码 28 | emailRouter.post( 29 | '/send_bind_code', 30 | verifyProp, 31 | emailController.sendBindEmailCode 32 | ); 33 | 34 | // 发送取消绑定邮箱验证码 35 | emailRouter.post( 36 | '/send_cancel_bind_code', 37 | emailController.sendCancelBindEmailCode 38 | ); 39 | 40 | // 邮箱列表 41 | emailRouter.get('/list', emailController.getList); 42 | 43 | // 用户绑定邮箱 44 | emailRouter.post('/bind_email', verifyProp, emailController.bindEmail); 45 | 46 | // 用户解绑邮箱 47 | emailRouter.post( 48 | '/cancel_bind_email', 49 | verifyProp, 50 | emailController.cancelBindEmail 51 | ); 52 | 53 | export default emailRouter; 54 | -------------------------------------------------------------------------------- /src/router/frontend.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import frontendController from '@/controller/frontend.controller'; 4 | 5 | const frontendRouter = new Router({ prefix: '/frontend' }); 6 | 7 | frontendRouter.get('/list', frontendController.getList); 8 | 9 | frontendRouter.get('/detail', frontendController.getDetail); 10 | 11 | frontendRouter.get('/find/:id', frontendController.find); 12 | 13 | frontendRouter.post('/create', frontendController.create); 14 | 15 | frontendRouter.put('/update/:id', frontendController.update); 16 | 17 | frontendRouter.delete('/delete/:id', frontendController.delete); 18 | 19 | export default frontendRouter; 20 | -------------------------------------------------------------------------------- /src/router/githubUser.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import githubUserController from '@/controller/githubUser.controller'; 4 | 5 | const githubUserRouter = new Router({ prefix: '/github_user' }); 6 | 7 | // 用户列表 8 | githubUserRouter.get('/list', githubUserController.list); 9 | 10 | // 用户列表 11 | githubUserRouter.get('/stargazers', githubUserController.getRepoStargazers); 12 | 13 | // 用户github登录 14 | githubUserRouter.post('/login', githubUserController.login); 15 | 16 | // 绑定github 17 | githubUserRouter.post('/bind_github', githubUserController.bindGithub); 18 | 19 | // 取消绑定github 20 | githubUserRouter.post( 21 | '/cancel_bind_github', 22 | githubUserController.cancelBindGithub 23 | ); 24 | 25 | // 查找用户 26 | githubUserRouter.get('/find/:id', githubUserController.find); 27 | 28 | // 更新用户 29 | githubUserRouter.put('/update/:id', githubUserController.update); 30 | 31 | // 删除用户 32 | githubUserRouter.delete('/delete/:id', githubUserController.delete); 33 | 34 | export default githubUserRouter; 35 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import Router from 'koa-router'; 4 | 5 | import { PROJECT_ENV, PROJECT_NAME, PROJECT_NODE_ENV } from '@/constant'; 6 | import { chalkERROR, chalkINFO, chalkSUCCESS } from '@/utils/chalkTip'; 7 | 8 | const router = new Router(); 9 | 10 | export function loadAllRoutes(app) { 11 | router.get('/', async (ctx, next) => { 12 | ctx.body = { 13 | msg: `欢迎访问${PROJECT_NAME},当前环境:${PROJECT_ENV},当前时间:${new Date().toLocaleString()}`, 14 | }; 15 | await next(); 16 | }); 17 | 18 | app.use(router.routes()).use(router.allowedMethods()); // 每一个router都要配置routes()和allowedMethods() 19 | 20 | fs.readdirSync(__dirname).forEach((file) => { 21 | try { 22 | if (PROJECT_NODE_ENV === 'development') { 23 | if (file === 'index.ts') return; 24 | } else if (file === 'index.js') return; 25 | 26 | const allRouter = require(`./${file}`).default; 27 | app.use(allRouter.routes()).use(allRouter.allowedMethods()); // allRouter也要配置routes()和allowedMethods() 28 | console.log(chalkINFO(`加载路由: ${file}`)); 29 | } catch (error) { 30 | console.log(chalkERROR(`加载${file}路由出错:`)); 31 | console.log(error); 32 | } 33 | }); 34 | console.log(chalkSUCCESS('加载所有路由成功!')); 35 | } 36 | -------------------------------------------------------------------------------- /src/router/init.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import initController from '@/controller/init.controller'; 4 | 5 | const initRouter = new Router({ prefix: '/init' }); 6 | 7 | // 初始化数据库 8 | initRouter.post('/database', initController.initDatabase); 9 | 10 | // 初始化角色 11 | initRouter.post('/role', initController.initRole); 12 | 13 | // 初始化权限 14 | initRouter.post('/auth', initController.initAuth); 15 | 16 | // 初始化角色权限 17 | initRouter.post('/role_auth', initController.initRoleAuth); 18 | 19 | // 初始化时间表 20 | initRouter.post('/day_data', initController.initDayData); 21 | 22 | // 初始化管理员 23 | initRouter.post('/admin_user', initController.initAdminUser); 24 | 25 | // 初始化前台设置 26 | initRouter.post('/frontend', initController.initFrontend); 27 | 28 | // 初始化互动统计 29 | initRouter.post('/interaction_statis', initController.initInteractionStatis); 30 | 31 | export default initRouter; 32 | -------------------------------------------------------------------------------- /src/router/interaction.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import interactionController from '@/controller/interaction.controller'; 4 | 5 | const interactionRouter = new Router({ prefix: '/interaction' }); 6 | 7 | // 互动列表 8 | interactionRouter.get('/list', interactionController.getList); 9 | 10 | // // 创建互动 11 | // linkRouter.post('/create', verifyProp, linkController.create); 12 | 13 | // // 查找互动 14 | // linkRouter.get('/find/:id', linkController.find); 15 | 16 | // // 更新互动 17 | // linkRouter.put('/update/:id', verifyProp, linkController.update); 18 | 19 | // // 删除互动 20 | // linkRouter.delete('/delete/:id', linkController.delete); 21 | 22 | export default interactionRouter; 23 | -------------------------------------------------------------------------------- /src/router/interactionStatis.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import interactionController from '@/controller/interaction.controller'; 4 | 5 | const interactionRouter = new Router({ prefix: '/interaction_statis' }); 6 | 7 | // 互动统计列表 8 | interactionRouter.get('/list', interactionController.getList); 9 | 10 | // // 创建互动统计 11 | // linkRouter.post('/create', verifyProp, linkController.create); 12 | 13 | // // 查找互动统计 14 | // linkRouter.get('/find/:id', linkController.find); 15 | 16 | // // 更新互动统计 17 | // linkRouter.put('/update/:id', verifyProp, linkController.update); 18 | 19 | // // 删除互动统计 20 | // linkRouter.delete('/delete/:id', linkController.delete); 21 | 22 | export default interactionRouter; 23 | -------------------------------------------------------------------------------- /src/router/link.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import linkController from '@/controller/link.controller'; 4 | import { verifyProp } from '@/middleware/link.middleware'; 5 | 6 | const linkRouter = new Router({ prefix: '/link' }); 7 | 8 | // 友链列表 9 | linkRouter.get('/list', linkController.getList); 10 | 11 | // 创建友链 12 | linkRouter.post('/create', verifyProp, linkController.create); 13 | 14 | // 查找友链 15 | linkRouter.get('/find/:id', linkController.find); 16 | 17 | // 更新友链 18 | linkRouter.put('/update/:id', verifyProp, linkController.update); 19 | 20 | // 删除友链 21 | linkRouter.delete('/delete/:id', linkController.delete); 22 | 23 | export default linkRouter; 24 | -------------------------------------------------------------------------------- /src/router/log.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import logController from '@/controller/log.controller'; 4 | 5 | const logRouter = new Router({ prefix: '/log' }); 6 | 7 | // 标签列表 8 | logRouter.get('/list', logController.getList); 9 | 10 | export default logRouter; 11 | -------------------------------------------------------------------------------- /src/router/monit.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import monitController from '@/controller/monit.controller'; 4 | 5 | const monitRouter = new Router({ prefix: '/monit' }); 6 | 7 | // 监控列表 8 | monitRouter.get('/list', monitController.getList); 9 | 10 | export default monitRouter; 11 | -------------------------------------------------------------------------------- /src/router/music.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import musicController from '@/controller/music.controller'; 4 | import { verifyProp } from '@/middleware/music.middleware'; 5 | 6 | const musicRouter = new Router({ prefix: '/music' }); 7 | 8 | // 音乐列表 9 | musicRouter.get('/list', musicController.getList); 10 | 11 | // 创建音乐 12 | musicRouter.post('/create', verifyProp, musicController.create); 13 | 14 | // 更新音乐 15 | musicRouter.put('/update/:id', verifyProp, musicController.update); 16 | 17 | // 查找音乐 18 | musicRouter.get('/find/:id', musicController.find); 19 | 20 | // 删除音乐 21 | musicRouter.delete('/delete/:id', musicController.delete); 22 | 23 | export default musicRouter; 24 | -------------------------------------------------------------------------------- /src/router/other.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import otherController from '@/controller/other.controller'; 4 | 5 | const otherRouter = new Router({ prefix: '/other' }); 6 | 7 | // 发送验证码 8 | otherRouter.post('/send_email', otherController.sendCode); 9 | 10 | // 获取运行信息 11 | otherRouter.get('/server_info', otherController.getServerInfo); 12 | 13 | export default otherRouter; 14 | -------------------------------------------------------------------------------- /src/router/position.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import positionController from '@/controller/position.controller'; 4 | 5 | const positionRouter = new Router({ prefix: '/position' }); 6 | 7 | positionRouter.get('/get', positionController.get); 8 | 9 | export default positionRouter; 10 | -------------------------------------------------------------------------------- /src/router/qiniuData.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import { betaError } from '@/app/auth/verifyEnv'; 4 | import qiniuController from '@/controller/qiniuData.controller'; 5 | 6 | const qiniuRouter = new Router({ prefix: '/qiniu_data' }); 7 | 8 | // TIP 路由==》控制器 9 | // TIP 控制器(比如删除图片)不需要关心用户权限,因为用户只能调用接口,只要在接口加上中间件即可限制用户不进入控制器即可 10 | // WARN 需要注意的是,业务上能解耦的尽量解耦,比如新增/删除文章,这两个操作都会涉及到封面图, 11 | // WARN 不要在新增/删除文章的控制器里面直接调用七牛云控制器,操作文章就仅仅操作文章可以了,别涉及到七牛云控制器的操作,如果确实需要的,就让前端在操作文章后,主动调七牛云的接口操作 12 | 13 | // 获取token 14 | // qiniuRouter.get('/get_token', qiniuController.getToken); 15 | 16 | qiniuRouter.get('/list', qiniuController.getList); 17 | 18 | qiniuRouter.post('/prefetch', qiniuController.prefetch); 19 | 20 | // 对比差异 21 | qiniuRouter.get('/diff', qiniuController.getDiff); 22 | 23 | // 上传文件,只支持一次性上传一个文件 24 | qiniuRouter.post('/upload', betaError, qiniuController.upload); 25 | 26 | // 上传chunk 27 | qiniuRouter.post('/upload_chunk', betaError, qiniuController.uploadChunk); 28 | 29 | // 合并chunk 30 | qiniuRouter.post('/merge_chunk', betaError, qiniuController.mergeChunk); 31 | 32 | // 上传文件,只支持一次性上传多个文件 33 | qiniuRouter.post('/mulit_upload', betaError, qiniuController.upload); 34 | 35 | // 文件进度 36 | qiniuRouter.get('/progress', betaError, qiniuController.getProgress); 37 | 38 | qiniuRouter.get('/batch_list', betaError, qiniuController.getBatchFileInfo); 39 | 40 | qiniuRouter.delete('/delete/:id', betaError, qiniuController.delete); 41 | 42 | qiniuRouter.delete( 43 | '/delete_by_qiniukey', 44 | betaError, 45 | qiniuController.deleteByQiniuKey 46 | ); 47 | 48 | qiniuRouter.put('/update/:id', betaError, qiniuController.update); 49 | 50 | qiniuRouter.post('/sync_qiniu_data', betaError, qiniuController.syncQiniuData); 51 | 52 | export default qiniuRouter; 53 | -------------------------------------------------------------------------------- /src/router/qqUser.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import qqUserController from '@/controller/qqUser.controller'; 4 | import { verifyProp } from '@/middleware/qqUser.middleware'; 5 | 6 | const qqUserRouter = new Router({ prefix: '/qq_user' }); 7 | 8 | /** 9 | * WARN:中间件接收两个参数,ctx和next,如果这个中间件是异步的(即加了async) 10 | * 则这个中间件必须调用next时必须加上await,如果是直接next,就会直接返回404给前端!不会继续触发下一个中间件!!! 11 | */ 12 | 13 | // 用户列表 14 | qqUserRouter.get('/list', qqUserController.list); 15 | 16 | // 用户qq登录 17 | qqUserRouter.post('/login', verifyProp, qqUserController.login); 18 | 19 | // 绑定qq 20 | qqUserRouter.post('/bind_qq', verifyProp, qqUserController.bindQQ); 21 | 22 | // 取消绑定qq 23 | qqUserRouter.post('/cancel_bind_qq', verifyProp, qqUserController.cancelBindQQ); 24 | 25 | // 查找用户 26 | qqUserRouter.get('/find/:id', qqUserController.find); 27 | 28 | // 更新用户 29 | qqUserRouter.put('/update/:id', qqUserController.update); 30 | 31 | // 删除用户 32 | qqUserRouter.delete('/delete/:id', qqUserController.delete); 33 | 34 | export default qqUserRouter; 35 | -------------------------------------------------------------------------------- /src/router/role.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import roleController from '@/controller/role.controller'; 4 | import { verifyProp } from '@/middleware/role.middleware'; 5 | 6 | const roleRouter = new Router({ prefix: '/role' }); 7 | 8 | // DONE 角色列表(分页) 9 | roleRouter.get('/list', roleController.getList); 10 | 11 | // DONE 所有角色列表(不分页) 12 | roleRouter.get('/all_list', roleController.getAllList); 13 | 14 | // DONE 获取所有角色(树型) 15 | roleRouter.get('/get_tree_role', roleController.getTreeRole); 16 | 17 | // DONE 获取除了父级以外的所有角色(树型) 18 | roleRouter.get('/get_tree_child_role', roleController.getTreeChildRole); 19 | 20 | // DONE 批量删除子角色 21 | roleRouter.post( 22 | '/batch_delete_child_roles', 23 | verifyProp, 24 | roleController.batchDeleteChildRoles 25 | ); 26 | 27 | // DONE 批量新增子角色 28 | roleRouter.put( 29 | '/batch_add_child_roles', 30 | verifyProp, 31 | roleController.batchAddChildRoles 32 | ); 33 | 34 | // DONE 创建角色 35 | roleRouter.post('/create', verifyProp, roleController.create); 36 | 37 | // DONE 更新角色 38 | roleRouter.put('/update/:id', verifyProp, roleController.update); 39 | 40 | // DONE 查找角色 41 | roleRouter.get('/find/:id', roleController.find); 42 | 43 | // DONE 删除角色(会删除底下关联的所有子角色) 44 | roleRouter.delete('/delete/:id', roleController.delete); 45 | 46 | // DONE 获取该角色的子角色(只找一层) 47 | roleRouter.get('/get_child_role/:id', roleController.getChildRole); 48 | 49 | // DONE 获取该角色的子角色(递归查找所有) 50 | roleRouter.get('/get_all_child_role/:id', roleController.getAllChildRole); 51 | 52 | // DONE 获取某个用户的角色 53 | roleRouter.get('/get_user_role/:user_id', roleController.getUserRole); 54 | 55 | // DONE 获取我的角色 56 | roleRouter.get('/get_my_role', roleController.getMyRole); 57 | 58 | // DONE 获取某个角色的权限 59 | roleRouter.get('/get_role_auth/:id', roleController.getRoleAuth); 60 | 61 | // 修改某个角色的权限 62 | roleRouter.put('/update_role_auth/:id', roleController.updateRoleAuth); 63 | 64 | export default roleRouter; 65 | -------------------------------------------------------------------------------- /src/router/roleAuth.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import roleAuthController from '@/controller/roleAuth.controller'; 4 | 5 | const roleAuthRouter = new Router({ prefix: '/role_auth' }); 6 | 7 | roleAuthRouter.get('/list', roleAuthController.getList); 8 | 9 | export default roleAuthRouter; 10 | -------------------------------------------------------------------------------- /src/router/schedule.route.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import { betaError } from '@/app/auth/verifyEnv'; 4 | import scheduleController from '@/controller/schedule.controller'; 5 | import { verifyIdOne } from '@/middleware/utils.middleware'; 6 | 7 | const scheduleRouter = new Router({ prefix: '/schedule' }); 8 | 9 | // 查看备份任务 10 | scheduleRouter.get( 11 | '/db_job', 12 | betaError, 13 | verifyIdOne, 14 | scheduleController.getDbJob 15 | ); 16 | 17 | // 查看内存 18 | scheduleRouter.get( 19 | '/invoke_showMemory_job', 20 | betaError, 21 | verifyIdOne, 22 | scheduleController.invokeShowMemoryJob 23 | ); 24 | 25 | // 执行备份任务 26 | scheduleRouter.post( 27 | '/invoke_db_job', 28 | betaError, 29 | verifyIdOne, 30 | scheduleController.invokeDbJob 31 | ); 32 | 33 | // 执行清除buff/cache任务 34 | scheduleRouter.post( 35 | '/invoke_clearCache_job', 36 | betaError, 37 | verifyIdOne, 38 | scheduleController.invokeClearCacheJob 39 | ); 40 | 41 | // 执行重启pm2 42 | scheduleRouter.post( 43 | '/restart_pm2', 44 | betaError, 45 | verifyIdOne, 46 | scheduleController.restartPm2 47 | ); 48 | 49 | export default scheduleRouter; 50 | -------------------------------------------------------------------------------- /src/router/star.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import starController from '@/controller/star.controller'; 4 | import { verifyProp } from '@/middleware/star.middleware'; 5 | 6 | const starRouter = new Router({ prefix: '/star' }); 7 | 8 | // star列表 9 | starRouter.get('/list', starController.getList); 10 | 11 | // 创建star 12 | starRouter.post('/create', verifyProp, starController.create); 13 | 14 | // 更新star 15 | starRouter.put('/update/:id', verifyProp, starController.update); 16 | 17 | // 查找star 18 | starRouter.get('/find/:id', starController.find); 19 | 20 | // 删除star 21 | starRouter.delete('/delete/:id', starController.delete); 22 | 23 | export default starRouter; 24 | -------------------------------------------------------------------------------- /src/router/statis.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import frontendController from '@/controller/frontend.controller'; 4 | 5 | const statisRouter = new Router({ prefix: '/statis' }); 6 | 7 | statisRouter.get('/detail', frontendController.getStatis); 8 | 9 | export default statisRouter; 10 | -------------------------------------------------------------------------------- /src/router/tag.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import tagController from '@/controller/tag.controller'; 4 | import { verifyProp } from '@/middleware/tag.middleware'; 5 | 6 | const tagRouter = new Router({ prefix: '/tag' }); 7 | 8 | // 标签列表 9 | tagRouter.get('/list', tagController.getList); 10 | 11 | // 标签文章列表 12 | tagRouter.get('/article_list/:tag_id', tagController.getArticleList); 13 | 14 | // 查找标签 15 | tagRouter.get('/find/:id', tagController.find); 16 | 17 | // 删除标签 18 | tagRouter.delete('/delete/:id', tagController.delete); 19 | 20 | // 创建标签 21 | tagRouter.post('/create', verifyProp, tagController.create); 22 | 23 | // 更新标签 24 | tagRouter.put('/update/:id', verifyProp, tagController.update); 25 | 26 | export default tagRouter; 27 | -------------------------------------------------------------------------------- /src/router/theme.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import themeController from '@/controller/theme.controller'; 4 | 5 | const themeRouter = new Router({ prefix: '/theme' }); 6 | 7 | // 主题列表 8 | themeRouter.get('/list', themeController.getList); 9 | 10 | // 创建主题 11 | themeRouter.post('/create', themeController.create); 12 | 13 | // 查找主题 14 | themeRouter.get('/find/:id', themeController.find); 15 | 16 | // 更新主题 17 | themeRouter.put('/update/:id', themeController.update); 18 | 19 | // 删除主题 20 | themeRouter.delete('/delete/:id', themeController.delete); 21 | 22 | export default themeRouter; 23 | -------------------------------------------------------------------------------- /src/router/type.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import typeController from '@/controller/type.controller'; 4 | import { verifyProp } from '@/middleware/type.middleware'; 5 | 6 | const typeRouter = new Router({ 7 | prefix: '/type', 8 | }); 9 | 10 | // 分类列表 11 | typeRouter.get('/list', typeController.getList); 12 | 13 | // 创建分类 14 | typeRouter.post('/create', verifyProp, typeController.create); 15 | 16 | // 查找分类 17 | typeRouter.get('/find/:id', typeController.find); 18 | 19 | // 更新分类 20 | typeRouter.put('/update/:id', verifyProp, typeController.update); 21 | 22 | // 删除分类 23 | typeRouter.delete('/delete/:id', typeController.delete); 24 | 25 | export default typeRouter; 26 | -------------------------------------------------------------------------------- /src/router/user.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import userController from '@/controller/user.controller'; 4 | import { verifyProp } from '@/middleware/user.middleware'; 5 | 6 | const userRouter = new Router({ prefix: '/user' }); 7 | 8 | /** 9 | * WARN:中间件接收两个参数,ctx和next,如果这个中间件是异步的(即加了async) 10 | * 则这个中间件必须调用next时必须加上await,如果是直接next,就会直接返回404给前端!不会继续触发下一个中间件!!! 11 | */ 12 | 13 | // 用户列表 14 | userRouter.get('/list', userController.list); 15 | 16 | // 账号密码登录 17 | userRouter.post('/login', verifyProp, userController.login); 18 | 19 | // 获取用户信息 20 | userRouter.get('/get_user_info', userController.getUserInfo); 21 | 22 | // 用户注册 23 | userRouter.post('/register', verifyProp, userController.register); 24 | 25 | // 创建用户(废弃) 26 | // userRouter.post('/create', userController.create); 27 | 28 | // 查找用户 29 | userRouter.get('/find/:id', userController.find); 30 | 31 | // 获取密码 32 | userRouter.get('/get_pwd', userController.getPwd); 33 | 34 | // 更新用户 35 | userRouter.put('/update/:id', verifyProp, userController.update); 36 | 37 | // 修改密码 38 | userRouter.put('/update_pwd', userController.updatePwd); 39 | 40 | // 更新用户角色 41 | userRouter.put( 42 | '/update_user_role/:id', 43 | verifyProp, 44 | userController.updateUserRole 45 | ); 46 | 47 | // 删除用户 48 | userRouter.delete('/delete/:id', userController.delete); 49 | 50 | export default userRouter; 51 | -------------------------------------------------------------------------------- /src/router/visitorLog.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import visitorLogController from '@/controller/visitorLog.controller'; 4 | import { verifyProp } from '@/middleware/visitorLog.middleware'; 5 | 6 | const visitorLogRouter = new Router({ prefix: '/visitor_log' }); 7 | 8 | // 获取历史访问数据 9 | visitorLogRouter.get('/history', visitorLogController.getHistoryVisitTotal); 10 | 11 | // 获取当天访问数据 12 | visitorLogRouter.get('/day', visitorLogController.getDayVisitTotal); 13 | 14 | // 获取ip访问总数 15 | visitorLogRouter.get('/ip_total', visitorLogController.getIpVisitTotal); 16 | 17 | // 访客日志列表(只带用户id,不带用户信息) 18 | visitorLogRouter.get('/list', visitorLogController.getList); 19 | 20 | // 访客日志列表(带用户信息) 21 | visitorLogRouter.get('/list2', visitorLogController.getList2); 22 | 23 | // 创建访客日志 24 | visitorLogRouter.post('/create', verifyProp, visitorLogController.create); 25 | 26 | // 删除访客日志 27 | visitorLogRouter.delete('/delete/:id', visitorLogController.delete); 28 | 29 | export default visitorLogRouter; 30 | -------------------------------------------------------------------------------- /src/router/works.router.ts: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router'; 2 | 3 | import worksController from '@/controller/works.controller'; 4 | import { verifyProp } from '@/middleware/works.middleware'; 5 | 6 | const worksRouter = new Router({ prefix: '/works' }); 7 | 8 | // 标签列表 9 | worksRouter.get('/list', worksController.getList); 10 | 11 | // 创建标签 12 | worksRouter.post('/create', verifyProp, worksController.create); 13 | 14 | // 查找标签 15 | worksRouter.get('/find/:id', worksController.find); 16 | 17 | // 更新标签 18 | worksRouter.put('/update/:id', verifyProp, worksController.update); 19 | 20 | // 删除标签 21 | worksRouter.delete('/delete/:id', worksController.delete); 22 | 23 | export default worksRouter; 24 | -------------------------------------------------------------------------------- /src/secret/secretTemp.ts: -------------------------------------------------------------------------------- 1 | import { PROJECT_ENV, PROJECT_ENV_ENUM } from '@/constant'; 2 | 3 | export const JWT_SECRET = '**********'; // jwt秘钥 4 | 5 | export const QINIU_ACCESSKEY = '**********'; // 七牛云秘钥 6 | export const QINIU_SECRETKEY = '**********'; // 七牛云秘钥 7 | export const QINIU_CALLBACK_URL = '**********'; // 七牛云回调 8 | 9 | export const WWW_QQ_CLIENT_ID = '**********'; // qq登录APP ID 10 | export const WWW_QQ_CLIENT_SECRET = '**********'; // qq登录APP Key 11 | export const WWW_QQ_REDIRECT_URI = '**********'; // qq登录回调地址 12 | export const ADMIN_QQ_CLIENT_ID = '**********'; // qq登录APP ID 13 | export const ADMIN_QQ_CLIENT_SECRET = '**********'; // qq登录APP Key 14 | export const ADMIN_QQ_REDIRECT_URI = '**********'; // qq登录回调地址 15 | 16 | export const GITHUB_CLIENT_ID = '**********'; // github登录APP ID 17 | export const GITHUB_CLIENT_SECRET = '**********'; // github登录APP Key 18 | export const GITHUB_REDIRECT_URI = 19 | '**************************************************'; // github登录回调地址 20 | 21 | export const GAODE_WEB_IP_URL = '**********'; // 高德地图url 22 | export const GAODE_WEB_IP_KEY = '**********'; // 高德地图key 23 | 24 | export const QQ_EMAIL_USER = '**********'; // qq邮箱auth的用户 25 | export const QQ_EMAIL_PASS = '**********'; // qq邮箱auth的秘钥 26 | 27 | export const IP_WHITE_LIST = ['127.0.0.1']; // ip白名单 28 | 29 | export const MYSQL_CONFIG = { 30 | docker: { 31 | container: '**********', 32 | image: '**********', 33 | port: { 3306: 3306 }, 34 | MYSQL_ROOT_PASSWORD: '**********', 35 | volume: '**********', 36 | }, 37 | database: 38 | PROJECT_ENV !== PROJECT_ENV_ENUM.prod ? `************` : '************', 39 | username: '**********', 40 | password: '**********', 41 | host: '**********', 42 | port: 666, 43 | }; // mysql配置 44 | 45 | export const SSH_CONFIG = { 46 | username: '**********', 47 | password: '**********', 48 | host: '**********', 49 | port: 666, 50 | }; // ssh配置 51 | 52 | export enum REDIS_DATABASE { 53 | blog, 54 | live, 55 | } 56 | 57 | export const REDIS_CONFIG = { 58 | database: REDIS_DATABASE.blog, 59 | socket: { 60 | port: 666, 61 | host: '**********', 62 | }, 63 | username: '**********', 64 | password: '**********', 65 | }; // redis配置 66 | -------------------------------------------------------------------------------- /src/service/articleTag.service.ts: -------------------------------------------------------------------------------- 1 | import { Op, col, fn } from 'sequelize'; 2 | 3 | import articleTagModel from '@/model/articleTag.model'; 4 | 5 | class ArticleTagService { 6 | async create(props) { 7 | const res = await articleTagModel.create(props); 8 | return res; 9 | } 10 | 11 | async getList() { 12 | const res = await articleTagModel.findAndCountAll(); 13 | return res; 14 | } 15 | 16 | async findArticleTagCount(article_ids: number[]) { 17 | const res = await articleTagModel.findAndCountAll({ 18 | attributes: ['article_id', [fn('COUNT', col('*')), 'count']], 19 | where: { 20 | article_id: { 21 | [Op.in]: article_ids, 22 | }, 23 | }, 24 | group: ['article_id'], 25 | }); 26 | return res; 27 | } 28 | 29 | async findArticleTag(article_ids: number[]) { 30 | const res = await articleTagModel.findAll({ 31 | attributes: [ 32 | 'article_id', 33 | 'tag_id', 34 | // [fn('GROUP_CONCAT', col('tag.id')), 'tag_id'], 35 | // [fn('GROUP_CONCAT', col('tag.name')), 'tag_name'], 36 | // [fn('GROUP_CONCAT', col('tag.color')), 'tag_color'], 37 | ], 38 | where: { 39 | article_id: { 40 | [Op.in]: article_ids, 41 | }, 42 | }, 43 | // group: ['article_id'], 44 | }); 45 | return res; 46 | } 47 | } 48 | 49 | export default new ArticleTagService(); 50 | -------------------------------------------------------------------------------- /src/service/articleType.service.ts: -------------------------------------------------------------------------------- 1 | import articleTypeModel from '@/model/dayData.model'; 2 | 3 | class ArticleTypeService { 4 | async create(props) { 5 | const res = await articleTypeModel.create(props); 6 | return res; 7 | } 8 | 9 | async getList() { 10 | const res = await articleTypeModel.findAndCountAll(); 11 | return res; 12 | } 13 | } 14 | 15 | export default new ArticleTypeService(); 16 | -------------------------------------------------------------------------------- /src/service/blacklist.service.ts: -------------------------------------------------------------------------------- 1 | import { deleteUseLessObjectKey, filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IBlacklist, IList } from '@/interface'; 5 | import blacklistModel from '@/model/blacklist.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class LinkService { 11 | /** 黑名单是否存在 */ 12 | async isExist(ids: number[]) { 13 | const res = await blacklistModel.count({ 14 | where: { 15 | id: { 16 | [Op.in]: ids, 17 | }, 18 | }, 19 | }); 20 | return res === ids.length; 21 | } 22 | 23 | /** 获取黑名单列表 */ 24 | async getList({ 25 | id, 26 | user_id, 27 | orderBy, 28 | orderName, 29 | nowPage, 30 | pageSize, 31 | keyWord, 32 | rangTimeType, 33 | rangTimeStart, 34 | rangTimeEnd, 35 | }: IList) { 36 | let offset; 37 | let limit; 38 | if (nowPage && pageSize) { 39 | offset = (+nowPage - 1) * +pageSize; 40 | limit = +pageSize; 41 | } 42 | const allWhere = deleteUseLessObjectKey({ id, user_id }); 43 | if (keyWord) { 44 | const keyWordWhere = [ 45 | { 46 | ip: { 47 | [Op.like]: `%${keyWord}%`, 48 | }, 49 | }, 50 | { 51 | msg: { 52 | [Op.like]: `%${keyWord}%`, 53 | }, 54 | }, 55 | ]; 56 | allWhere[Op.or] = keyWordWhere; 57 | } 58 | if (rangTimeType) { 59 | allWhere[rangTimeType] = { 60 | [Op.gt]: new Date(+rangTimeStart!), 61 | [Op.lt]: new Date(+rangTimeEnd!), 62 | }; 63 | } 64 | // @ts-ignore 65 | const result = await blacklistModel.findAndCountAll({ 66 | order: [[orderName, orderBy]], 67 | limit, 68 | offset, 69 | where: { 70 | ...allWhere, 71 | }, 72 | }); 73 | return handlePaging(result, nowPage, pageSize); 74 | } 75 | 76 | /** 查找黑名单 */ 77 | async find(id: number) { 78 | const result = await blacklistModel.findOne({ where: { id } }); 79 | return result; 80 | } 81 | 82 | /** 根据ip查找黑名单 */ 83 | async findByIp(ip: string) { 84 | const result = await blacklistModel.findOne({ where: { ip } }); 85 | return result; 86 | } 87 | 88 | /** 创建黑名单 */ 89 | async create(data: IBlacklist) { 90 | const result = await blacklistModel.create(data); 91 | return result; 92 | } 93 | 94 | /** 修改黑名单 */ 95 | async update(data: IBlacklist) { 96 | const { id } = data; 97 | const data2 = filterObj(data, ['id']); 98 | const result = await blacklistModel.update(data2, { 99 | where: { id }, 100 | limit: 1, 101 | }); 102 | return result; 103 | } 104 | 105 | /** 删除黑名单 */ 106 | async delete(id: number) { 107 | const result = await blacklistModel.destroy({ 108 | where: { id }, 109 | limit: 1, 110 | individualHooks: true, 111 | }); 112 | return result; 113 | } 114 | } 115 | 116 | export default new LinkService(); 117 | -------------------------------------------------------------------------------- /src/service/buryingPoint.service.ts: -------------------------------------------------------------------------------- 1 | import { deleteUseLessObjectKey } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IBuryingPoint, IList } from '@/interface'; 5 | import buryingPointModel from '@/model/buryingPoint.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class BuryingPointService { 11 | async isExist(ids: number[]) { 12 | const res = await buryingPointModel.count({ 13 | where: { 14 | id: { 15 | [Op.in]: ids, 16 | }, 17 | }, 18 | }); 19 | return res === ids.length; 20 | } 21 | 22 | async getList({ 23 | id, 24 | user_id, 25 | article_id, 26 | field_a, 27 | field_b, 28 | field_c, 29 | field_d, 30 | field_e, 31 | field_f, 32 | field_g, 33 | orderBy, 34 | orderName, 35 | nowPage, 36 | pageSize, 37 | keyWord, 38 | rangTimeType, 39 | rangTimeStart, 40 | rangTimeEnd, 41 | }: IList) { 42 | let offset; 43 | let limit; 44 | if (nowPage && pageSize) { 45 | offset = (+nowPage - 1) * +pageSize; 46 | limit = +pageSize; 47 | } 48 | const allWhere: any = deleteUseLessObjectKey({ 49 | id, 50 | user_id, 51 | article_id, 52 | field_a, 53 | field_b, 54 | field_c, 55 | field_d, 56 | field_e, 57 | field_f, 58 | field_g, 59 | }); 60 | if (keyWord) { 61 | const keyWordWhere = [ 62 | { 63 | ip: { 64 | [Op.like]: `%${keyWord}%`, 65 | }, 66 | }, 67 | { 68 | user_agent: { 69 | [Op.like]: `%${keyWord}%`, 70 | }, 71 | }, 72 | { 73 | remark: { 74 | [Op.like]: `%${keyWord}%`, 75 | }, 76 | }, 77 | ]; 78 | allWhere[Op.or] = keyWordWhere; 79 | } 80 | if (rangTimeType) { 81 | allWhere[rangTimeType] = { 82 | [Op.gt]: new Date(+rangTimeStart!), 83 | [Op.lt]: new Date(+rangTimeEnd!), 84 | }; 85 | } 86 | // @ts-ignore 87 | const result = await buryingPointModel.findAndCountAll({ 88 | order: [[orderName, orderBy]], 89 | limit, 90 | offset, 91 | where: { 92 | ...allWhere, 93 | }, 94 | }); 95 | return handlePaging(result, nowPage, pageSize); 96 | } 97 | 98 | async create(data: IBuryingPoint) { 99 | const result = await buryingPointModel.create(data); 100 | return result; 101 | } 102 | } 103 | 104 | export default new BuryingPointService(); 105 | -------------------------------------------------------------------------------- /src/service/interaction.service.ts: -------------------------------------------------------------------------------- 1 | import { filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IInteraction, IList } from '@/interface'; 5 | import interactionModel from '@/model/interaction.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class InteractionService { 11 | async isExist(ids: number[]) { 12 | const res = await interactionModel.count({ 13 | where: { 14 | id: { 15 | [Op.in]: ids, 16 | }, 17 | }, 18 | }); 19 | return res === ids.length; 20 | } 21 | 22 | async getList({ 23 | id, 24 | orderBy, 25 | orderName, 26 | nowPage, 27 | pageSize, 28 | keyWord, 29 | rangTimeType, 30 | rangTimeStart, 31 | rangTimeEnd, 32 | }: IList) { 33 | let offset; 34 | let limit; 35 | if (nowPage && pageSize) { 36 | offset = (+nowPage - 1) * +pageSize; 37 | limit = +pageSize; 38 | } 39 | const allWhere: any = {}; 40 | if (id) { 41 | allWhere.id = +id; 42 | } 43 | if (keyWord) { 44 | const keyWordWhere = [ 45 | { 46 | name: { 47 | [Op.like]: `%${keyWord}%`, 48 | }, 49 | }, 50 | { 51 | author: { 52 | [Op.like]: `%${keyWord}%`, 53 | }, 54 | }, 55 | ]; 56 | allWhere[Op.or] = keyWordWhere; 57 | } 58 | if (rangTimeType) { 59 | allWhere[rangTimeType] = { 60 | [Op.gt]: new Date(+rangTimeStart!), 61 | [Op.lt]: new Date(+rangTimeEnd!), 62 | }; 63 | } 64 | // @ts-ignore 65 | const result = await interactionModel.findAndCountAll({ 66 | order: [[orderName, orderBy]], 67 | limit, 68 | offset, 69 | where: { 70 | ...allWhere, 71 | }, 72 | }); 73 | return handlePaging(result, nowPage, pageSize); 74 | } 75 | 76 | async find(id: number) { 77 | const result = await interactionModel.findOne({ where: { id } }); 78 | return result; 79 | } 80 | 81 | async create(data: IInteraction) { 82 | const result = await interactionModel.create(data); 83 | return result; 84 | } 85 | 86 | async update(data: IInteraction) { 87 | const { id } = data; 88 | const data2 = filterObj(data, ['id']); 89 | const result = await interactionModel.update(data2, { 90 | where: { id }, 91 | limit: 1, 92 | }); 93 | return result; 94 | } 95 | 96 | async delete(id: number) { 97 | const result = await interactionModel.destroy({ 98 | where: { id }, 99 | limit: 1, 100 | individualHooks: true, 101 | }); 102 | return result; 103 | } 104 | } 105 | 106 | export default new InteractionService(); 107 | -------------------------------------------------------------------------------- /src/service/interactionStatis.service.ts: -------------------------------------------------------------------------------- 1 | import { filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IInteractionStatis, IList } from '@/interface'; 5 | import interactionStatisModel from '@/model/interactionStatis.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class InteractionStatisService { 11 | async isExist(ids: number[]) { 12 | const res = await interactionStatisModel.count({ 13 | where: { 14 | id: { 15 | [Op.in]: ids, 16 | }, 17 | }, 18 | }); 19 | return res === ids.length; 20 | } 21 | 22 | async getList({ 23 | id, 24 | orderBy, 25 | orderName, 26 | nowPage, 27 | pageSize, 28 | keyWord, 29 | type, 30 | rangTimeType, 31 | rangTimeStart, 32 | rangTimeEnd, 33 | }: IList) { 34 | let offset; 35 | let limit; 36 | if (nowPage && pageSize) { 37 | offset = (+nowPage - 1) * +pageSize; 38 | limit = +pageSize; 39 | } 40 | const allWhere: any = {}; 41 | if (id) { 42 | allWhere.id = +id; 43 | } 44 | if (type) { 45 | allWhere.type = type; 46 | } 47 | if (rangTimeType) { 48 | allWhere[rangTimeType] = { 49 | [Op.gt]: new Date(rangTimeStart!), 50 | [Op.lt]: new Date(rangTimeEnd!), 51 | }; 52 | } 53 | if (keyWord) { 54 | const keyWordWhere = [ 55 | { 56 | name: { 57 | [Op.like]: `%${keyWord}%`, 58 | }, 59 | }, 60 | { 61 | author: { 62 | [Op.like]: `%${keyWord}%`, 63 | }, 64 | }, 65 | ]; 66 | allWhere[Op.or] = keyWordWhere; 67 | } 68 | 69 | // @ts-ignore 70 | const result = await interactionStatisModel.findAndCountAll({ 71 | order: [[orderName, orderBy]], 72 | limit, 73 | offset, 74 | where: { 75 | ...allWhere, 76 | }, 77 | }); 78 | return handlePaging(result, nowPage, pageSize); 79 | } 80 | 81 | async find(id: number) { 82 | const result = await interactionStatisModel.findOne({ where: { id } }); 83 | return result; 84 | } 85 | 86 | async create(data: IInteractionStatis) { 87 | const result = await interactionStatisModel.create(data); 88 | return result; 89 | } 90 | 91 | async update(data: IInteractionStatis) { 92 | const { id } = data; 93 | const data2 = filterObj(data, ['id']); 94 | const result = await interactionStatisModel.update(data2, { 95 | where: { id }, 96 | limit: 1, 97 | }); 98 | return result; 99 | } 100 | 101 | async delete(id: number) { 102 | const result = await interactionStatisModel.destroy({ 103 | where: { id }, 104 | limit: 1, 105 | individualHooks: true, 106 | }); 107 | return result; 108 | } 109 | } 110 | 111 | export default new InteractionStatisService(); 112 | -------------------------------------------------------------------------------- /src/service/link.service.ts: -------------------------------------------------------------------------------- 1 | import { deleteUseLessObjectKey, filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { ILink, IList } from '@/interface'; 5 | import linkModel from '@/model/link.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class LinkService { 11 | /** 友链是否存在 */ 12 | async isExist(ids: number[]) { 13 | const res = await linkModel.count({ 14 | where: { 15 | id: { 16 | [Op.in]: ids, 17 | }, 18 | }, 19 | }); 20 | return res === ids.length; 21 | } 22 | 23 | /** 获取友链列表 */ 24 | async getList({ 25 | id, 26 | orderBy, 27 | orderName, 28 | nowPage, 29 | pageSize, 30 | keyWord, 31 | status, 32 | rangTimeType, 33 | rangTimeStart, 34 | rangTimeEnd, 35 | }: IList) { 36 | let offset; 37 | let limit; 38 | if (nowPage && pageSize) { 39 | offset = (+nowPage - 1) * +pageSize; 40 | limit = +pageSize; 41 | } 42 | const allWhere = deleteUseLessObjectKey({ id, status }); 43 | if (keyWord) { 44 | const keyWordWhere = [ 45 | { 46 | name: { 47 | [Op.like]: `%${keyWord}%`, 48 | }, 49 | }, 50 | { 51 | url: { 52 | [Op.like]: `%${keyWord}%`, 53 | }, 54 | }, 55 | { 56 | desc: { 57 | [Op.like]: `%${keyWord}%`, 58 | }, 59 | }, 60 | ]; 61 | allWhere[Op.or] = keyWordWhere; 62 | } 63 | if (rangTimeType) { 64 | allWhere[rangTimeType] = { 65 | [Op.gt]: new Date(+rangTimeStart!), 66 | [Op.lt]: new Date(+rangTimeEnd!), 67 | }; 68 | } 69 | // @ts-ignore 70 | const result = await linkModel.findAndCountAll({ 71 | order: [[orderName, orderBy]], 72 | limit, 73 | offset, 74 | where: { 75 | ...allWhere, 76 | }, 77 | }); 78 | return handlePaging(result, nowPage, pageSize); 79 | } 80 | 81 | /** 查找友链 */ 82 | async find(id: number) { 83 | const result = await linkModel.findOne({ where: { id } }); 84 | return result; 85 | } 86 | 87 | /** 创建友链 */ 88 | async create(data: ILink) { 89 | const result = await linkModel.create(data); 90 | return result; 91 | } 92 | 93 | /** 修改友链 */ 94 | async update(data: ILink) { 95 | const { id } = data; 96 | const data2 = filterObj(data, ['id']); 97 | const result = await linkModel.update(data2, { 98 | where: { id }, 99 | limit: 1, 100 | }); 101 | return result; 102 | } 103 | 104 | /** 删除友链 */ 105 | async delete(id: number) { 106 | const result = await linkModel.destroy({ 107 | where: { id }, 108 | limit: 1, 109 | individualHooks: true, 110 | }); 111 | return result; 112 | } 113 | } 114 | 115 | export default new LinkService(); 116 | -------------------------------------------------------------------------------- /src/service/monit.service.ts: -------------------------------------------------------------------------------- 1 | import { filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IList, IMonit } from '@/interface'; 5 | import monitModel from '@/model/monit.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class MonitService { 11 | /** 监控是否存在 */ 12 | async isExist(ids: number[]) { 13 | const res = await monitModel.count({ 14 | where: { 15 | id: { 16 | [Op.in]: ids, 17 | }, 18 | }, 19 | }); 20 | return res === ids.length; 21 | } 22 | 23 | /** 获取监控列表 */ 24 | async getList({ 25 | id, 26 | type, 27 | orderBy, 28 | orderName, 29 | nowPage, 30 | pageSize, 31 | keyWord, 32 | rangTimeType, 33 | rangTimeStart, 34 | rangTimeEnd, 35 | }: IList) { 36 | let offset; 37 | let limit; 38 | if (nowPage && pageSize) { 39 | offset = (+nowPage - 1) * +pageSize; 40 | limit = +pageSize; 41 | } 42 | const allWhere: any = {}; 43 | if (id) { 44 | allWhere.id = +id; 45 | } 46 | if (type) { 47 | allWhere.type = +type; 48 | } 49 | if (keyWord) { 50 | const keyWordWhere = [ 51 | { 52 | info: { 53 | [Op.like]: `%${keyWord}%`, 54 | }, 55 | }, 56 | ]; 57 | allWhere[Op.or] = keyWordWhere; 58 | } 59 | if (rangTimeType) { 60 | allWhere[rangTimeType] = { 61 | [Op.gt]: new Date(+rangTimeStart!), 62 | [Op.lt]: new Date(+rangTimeEnd!), 63 | }; 64 | } 65 | // @ts-ignore 66 | const result = await monitModel.findAndCountAll({ 67 | order: [[orderName, orderBy]], 68 | limit, 69 | offset, 70 | where: { 71 | ...allWhere, 72 | }, 73 | }); 74 | return handlePaging(result, nowPage, pageSize); 75 | } 76 | 77 | /** 查找监控 */ 78 | async find(id: number) { 79 | const result = await monitModel.findOne({ where: { id } }); 80 | return result; 81 | } 82 | 83 | /** 创建监控 */ 84 | async create(data: IMonit) { 85 | const result = await monitModel.create(data); 86 | return result; 87 | } 88 | 89 | /** 修改监控 */ 90 | async update(data: IMonit) { 91 | const { id } = data; 92 | const data2 = filterObj(data, ['id']); 93 | const result = await monitModel.update(data2, { 94 | where: { id }, 95 | limit: 1, 96 | }); 97 | return result; 98 | } 99 | 100 | /** 删除监控 */ 101 | async delete(id: number) { 102 | const result = await monitModel.destroy({ 103 | where: { id }, 104 | limit: 1, 105 | individualHooks: true, 106 | }); 107 | return result; 108 | } 109 | } 110 | 111 | export default new MonitService(); 112 | -------------------------------------------------------------------------------- /src/service/music.service.ts: -------------------------------------------------------------------------------- 1 | import { deleteUseLessObjectKey, filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IList, IMusic } from '@/interface'; 5 | import musicModel from '@/model/music.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | class MusicService { 10 | /** 音乐是否存在 */ 11 | async isExist(ids: number[]) { 12 | const res = await musicModel.count({ 13 | where: { 14 | id: { 15 | [Op.in]: ids, 16 | }, 17 | }, 18 | }); 19 | return res === ids.length; 20 | } 21 | 22 | /** 获取音乐列表 */ 23 | async getList({ 24 | id, 25 | status, 26 | orderBy, 27 | orderName, 28 | nowPage, 29 | pageSize, 30 | keyWord, 31 | rangTimeType, 32 | rangTimeStart, 33 | rangTimeEnd, 34 | }: IList) { 35 | let offset; 36 | let limit; 37 | if (nowPage && pageSize) { 38 | offset = (+nowPage - 1) * +pageSize; 39 | limit = +pageSize; 40 | } 41 | const allWhere = deleteUseLessObjectKey({ id, status }); 42 | 43 | if (keyWord) { 44 | const keyWordWhere = [ 45 | { 46 | name: { 47 | [Op.like]: `%${keyWord}%`, 48 | }, 49 | }, 50 | { 51 | author: { 52 | [Op.like]: `%${keyWord}%`, 53 | }, 54 | }, 55 | ]; 56 | allWhere[Op.or] = keyWordWhere; 57 | } 58 | if (rangTimeType) { 59 | allWhere[rangTimeType] = { 60 | [Op.gt]: new Date(+rangTimeStart!), 61 | [Op.lt]: new Date(+rangTimeEnd!), 62 | }; 63 | } 64 | // @ts-ignore 65 | const result = await musicModel.findAndCountAll({ 66 | order: [[orderName, orderBy]], 67 | limit, 68 | offset, 69 | where: { 70 | ...allWhere, 71 | }, 72 | }); 73 | return handlePaging(result, nowPage, pageSize); 74 | } 75 | 76 | /** 查找音乐 */ 77 | async find(id: number) { 78 | const result = await musicModel.findOne({ where: { id } }); 79 | return result; 80 | } 81 | 82 | /** 创建音乐 */ 83 | async create(data: IMusic) { 84 | const result = await musicModel.create(data); 85 | return result; 86 | } 87 | 88 | /** 修改音乐 */ 89 | async update(data: IMusic) { 90 | const { id } = data; 91 | const data2 = filterObj(data, ['id']); 92 | const result = await musicModel.update(data2, { 93 | where: { id }, 94 | limit: 1, 95 | }); 96 | return result; 97 | } 98 | 99 | /** 删除音乐 */ 100 | async delete(id: number) { 101 | const result = await musicModel.destroy({ 102 | where: { id }, 103 | limit: 1, 104 | individualHooks: true, 105 | }); 106 | return result; 107 | } 108 | } 109 | 110 | export default new MusicService(); 111 | -------------------------------------------------------------------------------- /src/service/position.service.ts: -------------------------------------------------------------------------------- 1 | import { IIpdata } from '@/interface'; 2 | import { GAODE_WEB_IP_KEY, GAODE_WEB_IP_URL } from '@/secret/secret'; 3 | import axios from '@/utils/request'; 4 | 5 | class PositionService { 6 | /** 7 | * country 国家 国家(或地区),中文。高德v3版查不了这个了。 8 | * province 省份 省(二级),中文。 9 | * city 城市 市(三级),中文。 10 | * district 区县 区(四级),中文。高德v3版查不了这个了。 11 | * isp 运营商 如电信、联通、移动。高德v3版查不了这个了。 12 | * location 经纬度 如 116.480881,39.989410。高德v3版查不了这个了。 13 | * Ip IP地址 提交的 Ipv4/ Ipv6地址。高德v3版查不了这个了。 14 | * adcode 城市的adcode编码。 15 | * rectangle 所在城市范围的左下右上对标对。 16 | * info 返回状态说明,status为0时,info返回错误原因,否则返回“OK”。 17 | * infocode 返回状态说明,10000代表正确,详情参阅info状态表。 18 | * status 值为0或1,0表示失败;1表示成功。 19 | */ 20 | async get(ip?: string) { 21 | if (['127.0.0.1', 'localhost', '', undefined].includes(ip)) { 22 | const ipStr = String(ip); 23 | return { 24 | info: ipStr, 25 | infocode: ipStr, 26 | status: ipStr, 27 | province: ipStr, 28 | city: ipStr, 29 | adcode: ipStr, 30 | rectangle: ipStr, 31 | frontend_rec_ip: ipStr, 32 | }; 33 | } 34 | // https://lbs.amap.com/api/webservice/guide/api/ipconfig 35 | const data: IIpdata = await axios.get(GAODE_WEB_IP_URL, { 36 | headers: { Accept: 'application/json' }, 37 | params: { key: GAODE_WEB_IP_KEY, ip }, 38 | }); 39 | return { ...data, frontend_rec_ip: ip || '' }; 40 | } 41 | } 42 | 43 | export default new PositionService(); 44 | -------------------------------------------------------------------------------- /src/service/roleAuth.service.ts: -------------------------------------------------------------------------------- 1 | import authModel from '@/model/auth.model'; 2 | import roleAuthModel from '@/model/roleAuth.model'; 3 | 4 | class RoleAuthService { 5 | async getList() { 6 | const result = await roleAuthModel.findAndCountAll({ 7 | include: authModel, 8 | distinct: true, 9 | }); 10 | return result; 11 | } 12 | } 13 | 14 | export default new RoleAuthService(); 15 | -------------------------------------------------------------------------------- /src/service/theme.service.ts: -------------------------------------------------------------------------------- 1 | import { filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IList, ITheme } from '@/interface'; 5 | import themeModel from '@/model/theme.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class ThemeService { 11 | /** 主题是否存在 */ 12 | async isExist(ids: number[]) { 13 | const res = await themeModel.count({ 14 | where: { 15 | id: { 16 | [Op.in]: ids, 17 | }, 18 | }, 19 | }); 20 | return res === ids.length; 21 | } 22 | 23 | /** 获取主题列表 */ 24 | async getList({ 25 | id, 26 | orderBy, 27 | orderName, 28 | nowPage, 29 | pageSize, 30 | keyWord, 31 | rangTimeType, 32 | rangTimeStart, 33 | rangTimeEnd, 34 | }: IList) { 35 | let offset; 36 | let limit; 37 | if (nowPage && pageSize) { 38 | offset = (+nowPage - 1) * +pageSize; 39 | limit = +pageSize; 40 | } 41 | const allWhere: any = {}; 42 | if (id) { 43 | allWhere.id = +id; 44 | } 45 | if (keyWord) { 46 | const keyWordWhere = [ 47 | { 48 | key: { 49 | [Op.like]: `%${keyWord}%`, 50 | }, 51 | }, 52 | { 53 | value: { 54 | [Op.like]: `%${keyWord}%`, 55 | }, 56 | }, 57 | { 58 | model: { 59 | [Op.like]: `%${keyWord}%`, 60 | }, 61 | }, 62 | { 63 | desc: { 64 | [Op.like]: `%${keyWord}%`, 65 | }, 66 | }, 67 | ]; 68 | allWhere[Op.or] = keyWordWhere; 69 | } 70 | if (rangTimeType) { 71 | allWhere[rangTimeType] = { 72 | [Op.gt]: new Date(+rangTimeStart!), 73 | [Op.lt]: new Date(+rangTimeEnd!), 74 | }; 75 | } 76 | // @ts-ignore 77 | const result = await themeModel.findAndCountAll({ 78 | order: [[orderName, orderBy]], 79 | limit, 80 | offset, 81 | where: { 82 | ...allWhere, 83 | }, 84 | }); 85 | return handlePaging(result, nowPage, pageSize); 86 | } 87 | 88 | /** 查找主题 */ 89 | async find(id: number) { 90 | const result = await themeModel.findOne({ where: { id } }); 91 | return result; 92 | } 93 | 94 | /** 创建主题 */ 95 | async create(data: ITheme) { 96 | const result = await themeModel.create(data); 97 | return result; 98 | } 99 | 100 | /** 修改主题 */ 101 | async update(data: ITheme) { 102 | const { id } = data; 103 | const data2 = filterObj(data, ['id']); 104 | const result = await themeModel.update(data2, { 105 | where: { id }, 106 | limit: 1, 107 | }); 108 | return result; 109 | } 110 | 111 | /** 删除主题 */ 112 | async delete(id: number) { 113 | const result = await themeModel.destroy({ 114 | where: { id }, 115 | limit: 1, 116 | individualHooks: true, 117 | }); 118 | return result; 119 | } 120 | } 121 | 122 | export default new ThemeService(); 123 | -------------------------------------------------------------------------------- /src/service/thirdUser.service.ts: -------------------------------------------------------------------------------- 1 | import { filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IList, IThirdUser } from '@/interface'; 5 | import thirdUserModel from '@/model/thirdUser.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class ThirdUserService { 11 | /** 第三方用户记录是否存在 */ 12 | async isExist(ids: number[]) { 13 | const res = await thirdUserModel.count({ 14 | where: { 15 | id: { 16 | [Op.in]: ids, 17 | }, 18 | }, 19 | }); 20 | return res === ids.length; 21 | } 22 | 23 | /** 获取第三方用户记录列表 */ 24 | async getList({ 25 | id, 26 | orderBy, 27 | orderName, 28 | nowPage, 29 | pageSize, 30 | keyWord, 31 | rangTimeType, 32 | rangTimeStart, 33 | rangTimeEnd, 34 | }: IList) { 35 | let offset; 36 | let limit; 37 | if (nowPage && pageSize) { 38 | offset = (+nowPage - 1) * +pageSize; 39 | limit = +pageSize; 40 | } 41 | const allWhere: any = {}; 42 | if (id) { 43 | allWhere.id = id; 44 | } 45 | if (keyWord) { 46 | const keyWordWhere = []; 47 | allWhere[Op.or] = keyWordWhere; 48 | } 49 | if (rangTimeType) { 50 | allWhere[rangTimeType] = { 51 | [Op.gt]: new Date(+rangTimeStart!), 52 | [Op.lt]: new Date(+rangTimeEnd!), 53 | }; 54 | } 55 | // @ts-ignore 56 | const result = await thirdUserModel.findAndCountAll({ 57 | order: [[orderName, orderBy]], 58 | limit, 59 | offset, 60 | where: { 61 | ...allWhere, 62 | }, 63 | }); 64 | return handlePaging(result, nowPage, pageSize); 65 | } 66 | 67 | /** 查找第三方用户记录 */ 68 | async find(id: number) { 69 | const result = await thirdUserModel.findOne({ where: { id } }); 70 | return result; 71 | } 72 | 73 | /** 根据third_platform和third_user_id查找第三方用户表里的记录 */ 74 | async findUser({ third_platform, third_user_id }) { 75 | const result = await thirdUserModel.findOne({ 76 | where: { third_platform, third_user_id }, 77 | }); 78 | return result; 79 | } 80 | 81 | /** 根据third_user_id查找第三方用户表里的记录 */ 82 | async findUserByThirdUserId(third_user_id) { 83 | const result = await thirdUserModel.findOne({ 84 | where: { third_user_id }, 85 | }); 86 | return result; 87 | } 88 | 89 | /** 根据user_id查找第三方用户表里的记录 */ 90 | async findByUserId(user_id: number) { 91 | const result = await thirdUserModel.findAll({ 92 | where: { user_id }, 93 | }); 94 | return result; 95 | } 96 | 97 | /** 创建第三方用户记录 */ 98 | async create(data: IThirdUser) { 99 | const result = await thirdUserModel.create(data); 100 | return result; 101 | } 102 | 103 | /** 修改第三方用户记录 */ 104 | async update(data: IThirdUser) { 105 | const { id } = data; 106 | const data2 = filterObj(data, ['id']); 107 | const result = await thirdUserModel.update(data2, { 108 | where: { id }, 109 | limit: 1, 110 | }); 111 | return result; 112 | } 113 | 114 | /** 删除第三方用户记录 */ 115 | async delete(id: number) { 116 | const result = await thirdUserModel.destroy({ 117 | where: { id }, 118 | limit: 1, 119 | individualHooks: true, 120 | }); 121 | return result; 122 | } 123 | } 124 | 125 | export default new ThirdUserService(); 126 | -------------------------------------------------------------------------------- /src/service/type.service.ts: -------------------------------------------------------------------------------- 1 | import { filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IList, IType } from '@/interface'; 5 | import typeModel from '@/model/type.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class TypeService { 11 | /** 分类是否存在 */ 12 | async isExist(ids: number[]) { 13 | const res = await typeModel.count({ 14 | where: { 15 | id: { 16 | [Op.in]: ids, 17 | }, 18 | }, 19 | }); 20 | return res === ids.length; 21 | } 22 | 23 | /** 获取分类列表 */ 24 | async getList({ 25 | id, 26 | orderBy, 27 | orderName, 28 | nowPage, 29 | pageSize, 30 | keyWord, 31 | rangTimeType, 32 | rangTimeStart, 33 | rangTimeEnd, 34 | }: IList) { 35 | let offset; 36 | let limit; 37 | if (nowPage && pageSize) { 38 | offset = (+nowPage - 1) * +pageSize; 39 | limit = +pageSize; 40 | } 41 | const allWhere: any = {}; 42 | if (id) { 43 | allWhere.id = +id; 44 | } 45 | if (keyWord) { 46 | const keyWordWhere = [ 47 | { 48 | name: { 49 | [Op.like]: `%${keyWord}%`, 50 | }, 51 | }, 52 | ]; 53 | allWhere[Op.or] = keyWordWhere; 54 | } 55 | if (rangTimeType) { 56 | allWhere[rangTimeType] = { 57 | [Op.gt]: new Date(+rangTimeStart!), 58 | [Op.lt]: new Date(+rangTimeEnd!), 59 | }; 60 | } 61 | // @ts-ignore 62 | const result = await typeModel.findAndCountAll({ 63 | order: [[orderName, orderBy]], 64 | limit, 65 | offset, 66 | where: { 67 | ...allWhere, 68 | }, 69 | }); 70 | return handlePaging(result, nowPage, pageSize); 71 | } 72 | 73 | /** 查找分类 */ 74 | async find(id: number) { 75 | const result = await typeModel.findOne({ where: { id } }); 76 | return result; 77 | } 78 | 79 | /** 创建分类 */ 80 | async create(data: IType) { 81 | const result = await typeModel.create(data); 82 | return result; 83 | } 84 | 85 | /** 修改分类 */ 86 | async update(data: IType) { 87 | const { id } = data; 88 | const data2 = filterObj(data, ['id']); 89 | const result = await typeModel.update(data2, { 90 | where: { id }, 91 | limit: 1, 92 | }); 93 | return result; 94 | } 95 | 96 | /** 删除分类 */ 97 | async delete(id: number) { 98 | const result = await typeModel.destroy({ 99 | where: { id }, 100 | limit: 1, 101 | individualHooks: true, 102 | }); 103 | return result; 104 | } 105 | } 106 | 107 | export default new TypeService(); 108 | -------------------------------------------------------------------------------- /src/service/works.service.ts: -------------------------------------------------------------------------------- 1 | import { deleteUseLessObjectKey, filterObj } from 'billd-utils'; 2 | import Sequelize from 'sequelize'; 3 | 4 | import { IList, IWorks } from '@/interface'; 5 | import worksModel from '@/model/works.model'; 6 | import { handlePaging } from '@/utils'; 7 | 8 | const { Op } = Sequelize; 9 | 10 | class WorksService { 11 | /** 作品是否存在 */ 12 | async isExist(ids: number[]) { 13 | const res = await worksModel.count({ 14 | where: { 15 | id: { 16 | [Op.in]: ids, 17 | }, 18 | }, 19 | }); 20 | return res === ids.length; 21 | } 22 | 23 | /** 获取作品列表 */ 24 | async getList({ 25 | id, 26 | status, 27 | orderBy, 28 | orderName, 29 | nowPage, 30 | pageSize, 31 | keyWord, 32 | rangTimeType, 33 | rangTimeStart, 34 | rangTimeEnd, 35 | }: IList) { 36 | let offset; 37 | let limit; 38 | if (nowPage && pageSize) { 39 | offset = (+nowPage - 1) * +pageSize; 40 | limit = +pageSize; 41 | } 42 | const allWhere = deleteUseLessObjectKey({ id, status }); 43 | if (keyWord) { 44 | const keyWordWhere = [ 45 | { 46 | name: { 47 | [Op.like]: `%${keyWord}%`, 48 | }, 49 | }, 50 | { 51 | desc: { 52 | [Op.like]: `%${keyWord}%`, 53 | }, 54 | }, 55 | { 56 | url: { 57 | [Op.like]: `%${keyWord}%`, 58 | }, 59 | }, 60 | ]; 61 | allWhere[Op.or] = keyWordWhere; 62 | } 63 | if (rangTimeType) { 64 | allWhere[rangTimeType] = { 65 | [Op.gt]: new Date(+rangTimeStart!), 66 | [Op.lt]: new Date(+rangTimeEnd!), 67 | }; 68 | } 69 | // @ts-ignore 70 | const result = await worksModel.findAndCountAll({ 71 | order: [[orderName, orderBy]], 72 | limit, 73 | offset, 74 | where: { 75 | ...allWhere, 76 | }, 77 | }); 78 | return handlePaging(result, nowPage, pageSize); 79 | } 80 | 81 | /** 查找作品 */ 82 | async find(id: IWorks['id']) { 83 | const result = await worksModel.findOne({ where: { id } }); 84 | return result; 85 | } 86 | 87 | /** 创建作品 */ 88 | async create(data: IWorks) { 89 | const result = await worksModel.create(data); 90 | return result; 91 | } 92 | 93 | /** 修改作品 */ 94 | async update(data: IWorks) { 95 | const { id } = data; 96 | const data2 = filterObj(data, ['id']); 97 | const result = await worksModel.update(data2, { where: { id }, limit: 1 }); 98 | return result; 99 | } 100 | 101 | /** 删除作品 */ 102 | async delete(id: IWorks['id']) { 103 | const result = await worksModel.destroy({ 104 | where: { id }, 105 | limit: 1, 106 | individualHooks: true, 107 | }); 108 | return result; 109 | } 110 | } 111 | 112 | export default new WorksService(); 113 | -------------------------------------------------------------------------------- /src/setup.ts: -------------------------------------------------------------------------------- 1 | import Koa from 'koa'; 2 | import koaBody from 'koa-body'; 3 | import conditional from 'koa-conditional-get'; 4 | import etag from 'koa-etag'; 5 | import staticService from 'koa-static'; 6 | 7 | import { catchErrorMiddle, corsMiddle } from '@/app/app.middleware'; 8 | import errorHandler from '@/app/handler/error-handle'; 9 | import { apiBeforeVerify } from '@/app/verify.middleware'; 10 | import { connectWebSocket } from '@/config/websocket'; 11 | import { 12 | PROJECT_ENV, 13 | PROJECT_ENV_ENUM, 14 | STATIC_DIR, 15 | UPLOAD_DIR, 16 | } from '@/constant'; 17 | import { initMonit } from '@/init/monit'; 18 | import { CustomError } from '@/model/customError.model'; 19 | import { loadAllRoutes } from '@/router'; 20 | 21 | export async function setupKoa({ port }) { 22 | const app = new Koa(); 23 | // app.proxyIpHeader 代理 ip 消息头, 默认为 X-Forwarded-For 24 | // app.proxyIpHeader = 'X-Real-IP'; 25 | app.proxy = true; 26 | 27 | app.use(catchErrorMiddle); // 全局错误处理 28 | 29 | app.use( 30 | koaBody({ 31 | multipart: true, 32 | formidable: { 33 | // 上传目录 34 | uploadDir: UPLOAD_DIR, // 默认os.tmpdir() 35 | // 保留文件扩展名 36 | keepExtensions: true, 37 | maxFileSize: 1024 * 1024 * 300, // 300m 38 | // onFileBegin(name, file) { 39 | // file.filepath = '可覆盖地址'; 40 | // }, 41 | }, 42 | onError(err) { 43 | console.log('koaBody错误', err); 44 | throw new CustomError(err.message, 500, 500); 45 | }, 46 | // parsedMethods: ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE'], // 声明将解析正文的 HTTP 方法,默认值['POST', 'PUT', 'PATCH']。替换strict选项。 47 | // strict: true, // 废弃了。如果启用,则不解析 GET、HEAD、DELETE 请求,默认true。即delete不会解析data数据 48 | }) 49 | ); // 解析参数 50 | 51 | app.use( 52 | staticService(STATIC_DIR, { 53 | maxage: 60 * 1000, // 缓存时间:1分钟 54 | }) 55 | ); // 静态文件目录 56 | 57 | app.use(conditional()); // 接口缓存 58 | app.use(etag()); // 接口缓存 59 | 60 | app.use(corsMiddle); // 设置允许跨域 61 | 62 | app.on('error', errorHandler); // 接收全局错误,位置必须得放在最开头? 63 | initMonit(); // 初始化监控 64 | // initDb('alert', sequelize); // 加载sequelize的relation表关联 65 | app.use(apiBeforeVerify); // 注意:需要在所有路由加载前使用这个中间件 66 | loadAllRoutes(app); // 加载所有路由 67 | await new Promise((resolve) => { 68 | // 语法糖, 等同于http.createServer(app.callback()).listen(3000); 69 | const httpServer = app.listen(port, () => { 70 | resolve('ok'); 71 | }); 72 | if (PROJECT_ENV !== PROJECT_ENV_ENUM.beta) { 73 | connectWebSocket(httpServer); // 初始化websocket 74 | } 75 | }); // http接口服务 76 | } 77 | -------------------------------------------------------------------------------- /src/utils/chalkTip.ts: -------------------------------------------------------------------------------- 1 | import nodeChalk from 'chalk'; 2 | import nodeEmoji from 'node-emoji'; 3 | 4 | import { PROJECT_ENV, PROJECT_ENV_ENUM } from '../constant'; 5 | 6 | export const emoji = nodeEmoji; 7 | export const chalk = nodeChalk; 8 | const disablePrettier = PROJECT_ENV === PROJECT_ENV_ENUM.prod; 9 | 10 | export const chalkINFO = (v: string) => { 11 | const time = new Date().toLocaleString('zh-CN'); 12 | const prefix = `[${time}] INFO `; 13 | if (disablePrettier) { 14 | return `${prefix} ${v}`; 15 | } 16 | return `${chalk.bgBlueBright.black(prefix)} ${chalk.blueBright(v)}`; 17 | }; 18 | export const chalkSUCCESS = (v: string) => { 19 | const time = new Date().toLocaleString('zh-CN'); 20 | const prefix = `[${time}] SUCCESS `; 21 | if (disablePrettier) { 22 | return `${prefix} ${v}`; 23 | } 24 | return `${chalk.bgGreenBright.black(prefix)} ${chalk.greenBright(v)}`; 25 | }; 26 | export const chalkERROR = (v: string) => { 27 | const time = new Date().toLocaleString('zh-CN'); 28 | const prefix = `[${time}] ERROR `; 29 | if (disablePrettier) { 30 | return `${prefix} ${v}`; 31 | } 32 | return `${chalk.bgRedBright.black(prefix)} ${chalk.redBright(v)}`; 33 | }; 34 | export const chalkWARN = (v: string) => { 35 | const time = new Date().toLocaleString('zh-CN'); 36 | const prefix = `[${time}] WARN `; 37 | if (disablePrettier) { 38 | return `${prefix} ${v}`; 39 | } 40 | return `${chalk.bgHex('#FFA500').black(`${prefix}`)} ${chalk.hex('#FFA500')( 41 | v 42 | )}`; 43 | }; 44 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const service = axios.create({ 4 | // baseURL: process.env.NODE_ENV === 'development' ? undefined : '/admin/', 5 | timeout: 5000, 6 | }); 7 | 8 | // 请求拦截 9 | service.interceptors.request.use( 10 | (config) => { 11 | // @ts-ignore 12 | return config; 13 | }, 14 | (error) => { 15 | console.log(error); 16 | return Promise.reject(error); 17 | } 18 | ); 19 | 20 | // 响应拦截 21 | service.interceptors.response.use( 22 | (response) => { 23 | return response.data; 24 | }, 25 | (error) => { 26 | return Promise.reject(error); 27 | } 28 | ); 29 | export default service; 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "CommonJS", // 指定生成什么模块代码。 5 | "lib": ["esnext"], 6 | "strict": true, // 启用所有严格的类型检查选项。默认:false 7 | "noImplicitAny": false, // 为具有隐含“any”类型的表达式和声明启用错误报告。 8 | "esModuleInterop": true, // esm和cjs混用 9 | "forceConsistentCasingInFileNames": true, // 在文件名中强制使用一致的大小写 10 | "moduleResolution": "node", // 指定模块解析策略 11 | "resolveJsonModule": true, // 解析json模块 12 | "skipLibCheck": true, // 跳过d.ts声明文件的类型检查。 13 | "baseUrl": "./", 14 | "paths": { 15 | "@/*": ["src/*"] 16 | }, 17 | "outDir": "./dist", 18 | "sourceMap": true, 19 | "noEmitOnError": false // 如果报告了任何错误,请不要发出编译器输出文件 20 | }, 21 | 22 | // ts-node的时候会读取这里的配置 23 | "ts-node": { 24 | "compilerOptions": { 25 | "module": "CommonJS" // 指定生成什么模块代码。 26 | }, 27 | "transpileOnly": true // 只编译,报警告或者错误一样运行 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "CommonJS", // 指定生成什么模块代码。 5 | "lib": ["esnext"], 6 | "strict": true, // 启用所有严格的类型检查选项。默认:false 7 | "noImplicitAny": false, // 为具有隐含“any”类型的表达式和声明启用错误报告。 8 | "esModuleInterop": true, // esm和cjs混用 9 | "forceConsistentCasingInFileNames": true, // 在文件名中强制使用一致的大小写 10 | "moduleResolution": "node", // 指定模块解析策略 11 | "resolveJsonModule": true, // 解析json模块 12 | "skipLibCheck": true, // 跳过d.ts声明文件的类型检查。 13 | "baseUrl": "./", 14 | "paths": { 15 | "@/*": ["src/*"] 16 | }, 17 | "outDir": "./dist", 18 | "noEmitOnError": false // 如果报告了任何错误,请不要发出编译器输出文件 19 | }, 20 | 21 | // ts-node的时候会读取这里的配置 22 | "ts-node": { 23 | "compilerOptions": { 24 | "module": "CommonJS" // 指定生成什么模块代码。 25 | }, 26 | "transpileOnly": true // 只编译,报警告或者错误一样运行 27 | } 28 | } 29 | --------------------------------------------------------------------------------