├── .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 |
--------------------------------------------------------------------------------