expressgy 4 months ago
commit 0e6ff7e292
  1. 89
      .eslintrc.js
  2. 56
      .gitignore
  3. 14
      .prettierrc.js
  4. 131
      README.md
  5. 110
      docs/RBAC/关于若依数据权限的一些问题.md
  6. 56
      docs/RBAC/对RBAC的理解.md
  7. BIN
      docs/RBAC/对RBAC的理解.png
  8. 241
      docs/drizzle条件查询.md
  9. 50
      docs/nestcli.md
  10. 6315
      docs/pacAuth_database_0.2.ndm2
  11. 337
      docs/swaggerApi.md
  12. 58
      docs/生命周期.md
  13. 36
      drizzle.config.js
  14. 8
      nest-cli.json
  15. 98
      package.json
  16. 7558
      pnpm-lock.yaml
  17. 7523
      pnpm-lock.yamlss
  18. 22
      src/application/app.controller.spec.ts
  19. 30
      src/application/app.controller.ts
  20. 62
      src/application/app.module.ts
  21. 15
      src/application/app.service.ts
  22. 74
      src/application/core-dict/core-dict.controller.ts
  23. 9
      src/application/core-dict/core-dict.module.ts
  24. 414
      src/application/core-dict/core-dict.service.ts
  25. 146
      src/application/core-dict/dto/create-core-dict.dto.ts
  26. 151
      src/application/core-dict/dto/get-core-dict.dto.ts
  27. 25
      src/application/core-dict/dto/update-core-dict.dto.ts
  28. 1
      src/application/core-dict/entities/core-dict.entity.ts
  29. 74
      src/application/core-env/core-env.controller.ts
  30. 9
      src/application/core-env/core-env.module.ts
  31. 438
      src/application/core-env/core-env.service.ts
  32. 133
      src/application/core-env/dto/create-core-env.dto.ts
  33. 142
      src/application/core-env/dto/get-core-env.dto.ts
  34. 25
      src/application/core-env/dto/update-core-env.dto.ts
  35. 1
      src/application/core-env/entities/core-env.entity.ts
  36. 20
      src/application/core-service/core-service.controller.spec.ts
  37. 65
      src/application/core-service/core-service.controller.ts
  38. 9
      src/application/core-service/core-service.module.ts
  39. 18
      src/application/core-service/core-service.service.spec.ts
  40. 234
      src/application/core-service/core-service.service.ts
  41. 62
      src/application/core-service/dto/create-core-service.dto.ts
  42. 33
      src/application/core-service/dto/get-core-service.dto.ts
  43. 36
      src/application/core-service/dto/serviceKey.dto.ts
  44. 5
      src/application/core-service/dto/update-core-service.dto.ts
  45. 1
      src/application/core-service/entities/core-service.entity.ts
  46. 26
      src/application/global.module.ts
  47. 1
      src/application/test/dto/create-test.dto.ts
  48. 4
      src/application/test/dto/update-test.dto.ts
  49. 1
      src/application/test/entities/test.entity.ts
  50. 20
      src/application/test/test.controller.spec.ts
  51. 42
      src/application/test/test.controller.ts
  52. 9
      src/application/test/test.module.ts
  53. 18
      src/application/test/test.service.spec.ts
  54. 26
      src/application/test/test.service.ts
  55. 109
      src/common/decorator/change-case/change-case.decorator.ts
  56. 15
      src/common/decorator/pac-info/pac-info.decorator.ts
  57. 13
      src/common/decorator/trim/trim.decorator.ts
  58. 7
      src/common/http-except/http-except.filter.spec.ts
  59. 48
      src/common/http-except/http-except.filter.ts
  60. 7
      src/common/interceptor/format-response/format-response.interceptor.spec.ts
  61. 15
      src/common/interceptor/format-response/format-response.interceptor.ts
  62. 7
      src/common/interceptor/log-request-info/log-request-info.interceptor.spec.ts
  63. 58
      src/common/interceptor/log-request-info/log-request-info.interceptor.ts
  64. 7
      src/common/interceptor/user-operation-log/user-operation-log.interceptor.spec.ts
  65. 54
      src/common/interceptor/user-operation-log/user-operation-log.interceptor.ts
  66. 18
      src/common/lifecycle/lifecycle.service.spec.ts
  67. 39
      src/common/lifecycle/lifecycle.service.ts
  68. 7
      src/common/middleware/log-request-info/log-request-info.middleware.spec.ts
  69. 24
      src/common/middleware/log-request-info/log-request-info.middleware.ts
  70. 7
      src/common/middleware/useragent/useragent.middleware.spec.ts
  71. 30
      src/common/middleware/useragent/useragent.middleware.ts
  72. 7
      src/common/pipe/trim/trim.pipe.spec.ts
  73. 14
      src/common/pipe/trim/trim.pipe.ts
  74. 18
      src/common/service/mysql/mysql.service.spec.ts
  75. 54
      src/common/service/mysql/mysql.service.ts
  76. 18
      src/common/service/redis/redis.service.spec.ts
  77. 89
      src/common/service/redis/redis.service.ts
  78. 48
      src/config/configuration.ts
  79. 89
      src/dto/get.dto.ts
  80. 180
      src/entities/0000_productive_zarda.sql
  81. 24
      src/entities/customType.ts
  82. 1127
      src/entities/meta/0000_snapshot.json
  83. 13
      src/entities/meta/_journal.json
  84. 3
      src/entities/relations.ts
  85. 342
      src/entities/schema.ts
  86. 87
      src/main.ts
  87. 18
      src/service/logger/logger.service.spec.ts
  88. 145
      src/service/logger/logger.service.ts
  89. 18
      src/service/snowflake/snowflake.service.spec.ts
  90. 74
      src/service/snowflake/snowflake.service.ts
  91. 40
      src/utils/boolean.enum.ts
  92. 32
      src/utils/customDrizzleRowWithRecursive.ts
  93. 25
      src/utils/extractBits.ts
  94. 35
      src/utils/formatBytes.ts
  95. 64
      src/utils/generateMachineId.ts
  96. 16
      src/utils/likeQuery.ts
  97. 69
      src/utils/mathodColor.ts
  98. 17
      src/utils/myType.d.ts
  99. 36
      src/utils/random.ts
  100. 20
      src/utils/sleep.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,89 @@
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
project: "tsconfig.json",
tsconfigRootDir: __dirname,
sourceType: "module"
},
plugins: ["@typescript-eslint/eslint-plugin", "prettier"],
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
root: true,
env: {
node: true,
jest: true
},
ignorePatterns: [".eslintrc.js"],
rules: {
"max-len": ["error", {
"code": 160, // 指定最大代码长度
"ignoreStrings": true, // 忽略字符串中的字符长度
"ignoreUrls": true, // 忽略URL的长度
"ignoreComments": false, // 注释中的字符会计入长度限制
"ignoreTemplateLiterals": true, // 忽略模板字符串的长度
"tabWidth": 4 // 设置制表符的宽度
}],
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"indent": [
"error",
4,
{
SwitchCase: 1
}
], // 1 表示在 switch 语句中,case 和 default 语句需要与 switch 的起始位置对齐,并且缩进一个级别(即 4 个空格)
// "curly": "error", // 所有的控制语句(if, for, while 等)使用大括号括起来,即使它们只包含单个语句。
"quotes": ["error", "single"], // 强制使用一致的引号风格
"no-unused-expressions": "error", // 禁止使用未使用的表达式
"no-console": "error", // 禁止使用console
"semi": ["error", "always"], // 启用 semi 规则,要求语句后总是使用分号,并将其设置为错误
// "new-cap": "error", //要求使用 new 关键字创建的实例的构造函数名必须大写。
"no-const-assign": "error", //禁止给 const 声明的变量赋值
"no-duplicate-case": "error", // 禁止 switch 语句中出现重复的 case
"no-extra-parens": "error", // 限制不必要的括号
"no-fallthrough": "error", //:禁止 switch 语句中的穿透行为
"no-multi-spaces": "error", //禁止使用多个空格。
"no-trailing-spaces": "error", //:禁止行尾有空格。
"no-undef": "error", //:禁止使用未声明的变量。
"no-undef-init": "error", //禁止初始化变量时使用 undefined。
"no-empty": 2, //块语句中的内容不能为空
"no-extra-semi": 2, //禁止多余的冒号
"no-func-assign": 2, //禁止重复的函数声明
"no-inline-comments": 2, //禁止行内备注
"space-before-function-paren": [
"error",
{
// "always" - 要求在函数名和参数列表之间总是有空白。
// "never" - 禁止在函数名和参数列表之间有空格。
anonymous: "always", // "anonymous" - 指定匿名函数表达式前的空格要求("always" 或 "never")。
named: "never", // "named" - 指定具名函数表达式前的空格要求("always" 或 "never")。
asyncArrow: "always" // "asyncArrow" - 指定异步箭头函数前的空格要求("always" 或 "never")
}
],
"no-multiple-empty-lines": [1, { max: 2 }], //空行最多不能超过2行
"no-nested-ternary": 0, //禁止使用嵌套的三目运算
"no-redeclare": 2, //禁止重复声明变量
"no-shadow": 2, //外部作用域中的变量不能与它所包含的作用域中的变量或参数同名
// 'no-unused-vars': [2, { vars: 'all', args: 'after-used' }], //不能有声明后未被使用的变量或参数
"no-use-before-define": 2, //未定义前不能使用
"no-mixed-spaces-and-tabs": "error", // 禁止在代码中混用空格和制表符
"lines-around-comment": [
"error",
{
beforeBlockComment: true, // beforeBlockComment: 在块级注释(以 /* 开头)之前需要有空行。
afterBlockComment: false, // afterBlockComment: 在块级注释之后需要有空行。
beforeLineComment: true, // beforeLineComment: 在行注释(以 // 开头)之前需要有空行。
afterLineComment: false, // afterLineComment: 在行注释之后需要有空行。
allowBlockStart: true, // allowBlockStart: 允许块级注释紧跟在函数或块的开始。
allowClassStart: true, // allowClassStart: 允许块级注释紧跟在类定义的开始。
allowObjectStart: true, // allowObjectStart: 允许块级注释紧跟在对象字面量的开始。
allowArrayStart: true // allowArrayStart: 允许块级注释紧跟在数组字面量的开始。
}
]
}
};

56
.gitignore vendored

@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

@ -0,0 +1,14 @@
module.exports = {
printWidth: 160, // 每行代码的最大长度
tabWidth: 4, // 每个缩进级别的空格数,用来覆盖默认的2个空格
useTabs: false, // 使用空格而不是制表符进行缩进
semi: true, // 语句末尾使用分号
singleQuote: true, // 使用单引号
quoteProps: 'consistent', // 对象字面量属性名的引号风格
trailingComma: 'all', // 行尾逗号
bracketSpacing: true, // 对象字面量属性后使用空格
jsxBracketSameLine: false, // JSX 标签的闭括号放在下一行
arrowParens: 'always', // 箭头函数参数括号
proseWrap: 'preserve', // 保持文本换行
endOfLine: 'auto', // 换行符自动处理
};

@ -0,0 +1,131 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ pnpm install
```
## Running the app
```bash
# development
$ pnpm run start
# watch mode
$ pnpm run start:dev
# production mode
$ pnpm run start:prod
```
## Test
```bash
# unit tests
$ pnpm run test
# e2e tests
$ pnpm run test:e2e
# test coverage
$ pnpm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).
## dirTree
```
/src
│ app.module.ts # 主模块
│ main.ts # 应用入口文件
├──/common # 公共服务目录
│ ├──/filters # 异常过滤器等
│ │ ├──/http-exception.filter.ts
│ │ ├──/validation-filter.ts
│ │ └──/... # 更多过滤器
│ ├──/guards # 守卫
│ │ ├──/auth.guard.ts
│ │ ├──/roles.guard.ts
│ │ └──/... # 更多守卫
│ ├──/interceptors # 拦截器
│ │ ├──/logging.interceptor.ts
│ │ ├──/timeout.interceptor.ts
│ │ └──/... # 更多拦截器
│ ├──/pipes # 管道
│ │ ├──/validation.pipe.ts
│ │ ├──/transform.pipe.ts
│ │ └──/... # 更多管道
│ └──/decorators # 装饰器
│ ├──/auth.decorator.ts
│ ├──/log.decorator.ts
│ └──/... # 更多装饰器
├──/config # 配置文件
│ ├──/database.config.ts
│ ├──/app.config.ts
│ └──/... # 更多配置
├──/modules # 模块目录
│ ├──/user # 用户模块
│ │ ├──/user.module.ts
│ │ ├──/controllers # 模块控制器
│ │ ├──/services # 模块服务
│ │ ├──/dtos # 模块 DTOs
│ │ ├──/entities # 模块实体
│ │ └──/... # 模块其他文件
│ └──/... # 更多模块
├──/providers # 服务提供者
│ ├──/app.provider.ts
│ └──/... # 更多提供者
├──/entities # 数据实体定义
│ ├──/user.entity.ts
│ └──/... # 更多实体
└──/utils # 工具类和实用函数
├──/logger.ts
└──/... # 更多工具
```

@ -0,0 +1,110 @@
# 关于标准化系统的用户系统与若依用户系统的研讨会
> 参会人:全体研发(选择性),产品(选择性)
>
> 一次会议
# 若依系统的优点
1. 较完善,且稳定,能满足大多单个系统的需求
2. 成体系,插件丰富,生态,一直在更新
3. 双token
4. 不用费工夫自己写
5. 通用
6. 用的人多
补充。。。
# 若依系统的缺陷
1. 可复制系统的批量移植问题
2. 针对用户系统的二开造成的版本维护困难
3. UI问题,和一些操作的优化
# 关于若依系统(权限)的一些问题
## 关于功能权限
1. 功能权限用的什么工具
spring security
2. 实现原理
注解加拦截
3. 是否必须按照`:`规则添加
## 关于数据权限
1. 数据权限用的什么工具
2. 实现原理
3. 是否只局限于查询操作(只能作用于查询)
4. 其它系统如何调用,调用原理是什么,【档案 运维 其他】,连表查询,采用数据库
## 其他
1. 岗位标识的作用
# 标准化系统的核心需求
1. 公司信息可配
2. 菜单编辑提到更高层次,增加系统参数
3. 系统变量提到更高层次
4. 建立个人用户操作习惯变量
5. 双tocken验证
6. 请求限制(ip、请求数量)
7. 账户封禁、试错配置
8. 验证配置
9. 系统发件配置
10. 多组织多部门
11. 多岗位
12. 多角色
13. 组织架构增加参数
14. 角色结构化
### 其他基础子系统/功能
- 文件系统
- 消息提醒(控制中心)
-
# 实施计划
## 普通可复制系统
## 细粒度数据权限系统
## 特殊角色互斥系统
# 管理维护计划
其他问题
普通业务记录,如果要加装流程管理怎么办
流程管理要触发其他系统操作怎么办,还有数据传递
- 跨部门:选更高一级,数据权限可以分配多个组织的权限

@ -0,0 +1,56 @@
# RBAC<92>
> 1992 , **RBAC0**
- 组织架构
- 岗位:账户上的属性标签
- 角色:权限的集合
- 权限:功能权限+数据权限
# RBAC<96>
> 96年对RBAC的优化
# RBAC1
> 角色结构化,角色继承
- 一般继承:无规则,从最底层开始向上继承,不利于管理(结构化),存在多个多个上级,【最末端的可以用具体权限条目来代替了】
- 受限继承:向下有限继承,方便建立
将角色结构化,解决扁平化角色的管理复杂度
# RBAC2
> 角色限制
- SSD:静态职责分离
- 互斥角色:不能同时将两个角色分给同一个人
- 基数约束:账户能被分配的角色数量,角色能容纳的最大账户数量
- ~~先决条件角色:用户想要成为上级角色,必须先成为下一级角色,比如游戏中的转职~~【此情况应该不存在于受限继承中】
- DSD:动态职责分离
- 用户可以有两个互斥的角色,但是在运行时只能选择一个(BOOS直聘,老板和员工)
# RBAC3
> RBAC3=RBAC1+RBAC2
# 完善的RBAC
> 新用户的权限,可以来源于所在部门的**角色**或权限
方便新用户进来不用手动配角色
问题:用户和账户的定义,一个用户存在多账户,如腾讯云的子账户

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

@ -0,0 +1,241 @@
在 Drizzle ORM 中,`.where()` 方法接受各种条件操作符来构建查询条件。以下是一些常用的条件操作符及其含义:
1. **eq**(等于)
```typescript
import { eq } from "drizzle-orm";
db.select().from(table).where(eq(table.column, 5));
```
```sql
SELECT * FROM table WHERE table.column = 5
```
用于检查列的值是否等于某个值 [(1)](https://orm.drizzle.team/docs/operators) 。
2. **ne**(不等于)
```typescript
import { ne } from "drizzle-orm";
db.select().from(table).where(ne(table.column1, table.column2));
```
```sql
SELECT * FROM table WHERE table.column1 <> table.column2
```
用于检查列的值是否不等于某个值。
3. **gt**(大于)
```typescript
import { gt } from "drizzle-orm";
db.select().from(table).where(gt(table.column, 5));
```
```sql
SELECT * FROM table WHERE table.column > 5
```
用于检查列的值是否大于某个值。
4. **gte**(大于等于)
```typescript
import { gte } from "drizzle-orm";
db.select().from(table).where(gte(table.column, 5));
```
```sql
SELECT * FROM table WHERE table.column >= 5
```
用于检查列的值是否大于或等于某个值。
5. **lt**(小于)
```typescript
import { lt } from "drizzle-orm";
db.select().from(table).where(lt(table.column, 5));
```
```sql
SELECT * FROM table WHERE table.column < 5
```
用于检查列的值是否小于某个值。
6. **lte**(小于等于)
```typescript
import { lte } from "drizzle-orm";
db.select().from(table).where(lte(table.column, 5));
```
```sql
SELECT * FROM table WHERE table.column <= 5
```
用于检查列的值是否小于或等于某个值。
7. **isNull**(为空)
```typescript
import { isNull } from "drizzle-orm";
db.select().from(table).where(isNull(table.column));
```
```sql
SELECT * FROM table WHERE table.column IS NULL
```
用于检查列的值是否为 `NULL`
8. **isNotNull**(不为空)
```typescript
import { isNotNull } from "drizzle-orm";
db.select().from(table).where(isNotNull(table.column));
```
```sql
SELECT * FROM table WHERE table.column IS NOT NULL
```
用于检查列的值是否不为 `NULL`
9. **inArray**(在数组中)
```typescript
import { inArray } from "drizzle-orm";
db.select().from(table).where(inArray(table.column, [1, 2, 3, 4]));
```
```sql
SELECT * FROM table WHERE table.column IN (1, 2, 3, 4)
```
用于检查列的值是否在给定的数组中。
10. **notInArray**(不在数组中)
```typescript
import { notInArray } from "drizzle-orm";
db.select().from(table).where(notInArray(table.column, [1, 2, 3, 4]));
```
```sql
SELECT * FROM table WHERE table.column NOT IN (1, 2, 3, 4)
```
用于检查列的值是否不在给定的数组中。
11. **exists**(存在)
```typescript
import { exists } from "drizzle-orm";
const query = db.select().from(table2);
db.select().from(table).where(exists(query));
```
```sql
SELECT * FROM table WHERE EXISTS (SELECT * FROM table2)
```
用于检查子查询是否返回任何行。
12. **notExists**(不存在)
```typescript
import { notExists } from "drizzle-orm";
const query = db.select().from(table2);
db.select().from(table).where(notExists(query));
```
```sql
SELECT * FROM table WHERE NOT EXISTS (SELECT * FROM table2)
```
用于检查子查询是否不返回任何行。
13. **between**(在范围之间)
```typescript
import { between } from "drizzle-orm";
db.select().from(table).where(between(table.column, 2, 7));
```
```sql
SELECT * FROM table WHERE table.column BETWEEN 2 AND 7
```
用于检查列的值是否在两个值之间。
14. **notBetween**(不在范围之间)
```typescript
import { notBetween } from "drizzle-orm";
db.select().from(table).where(notBetween(table.column, 2, 7));
```
```sql
SELECT * FROM table WHERE table.column NOT BETWEEN 2 AND 7
```
用于检查列的值是否不在两个值之间。
15. **like**(匹配模式)
```typescript
import { like } from "drizzle-orm";
db.select().from(table).where(like(table.column, "%llo wor%"));
```
```sql
SELECT * FROM table WHERE table.column LIKE '%llo wor%'
```
用于执行大小写敏感的模式匹配。
16. **ilike**(不区分大小写的模式匹配)
```typescript
import { ilike } from "drizzle-orm";
db.select().from(table).where(ilike(table.column, "%llo wor%"));
```
```sql
SELECT * FROM table WHERE table.column ILIKE '%llo wor%'
```
用于执行不区分大小写的模式匹配。
17. **notIlike**(不区分大小写的不匹配)
```typescript
import { notIlike } from "drizzle-orm";
db.select().from(table).where(notIlike(table.column, "%llo wor%"));
```
```sql
SELECT * FROM table WHERE table.column NOT ILIKE '%llo wor%'
```
用于执行不区分大小写的不匹配。
18. **not**(所有条件必须返回 `false`
```typescript
import { eq, not } from "drizzle-orm";
db.select().from(table).where(not(eq(table.column, 5)));
```
```sql
SELECT * FROM table WHERE NOT (table.column = 5)
```
用于检查条件是否不满足。
19. **and**(所有条件必须返回 `true`
```typescript
import { gt, lt, and } from "drizzle-orm";
db.select().from(table).where(and(gt(table.column, 5), lt(table.column, 7)));
```
```sql
SELECT * FROM table WHERE (table.column > 5 AND table.column < 7)
```
用于将多个条件组合在一起,所有条件必须满足。
20. **or**(一个或多个条件必须返回 `true`
```typescript
import { gt, lt, or } from "drizzle-orm";
db.select().from(table).where(or(gt(table.column, 5), lt(table.column, 7)));
```
```sql
SELECT * FROM table WHERE (table.column > 5 OR table.column < 7)
```
用于将多个条件组合在一起,任意一个条件满足即可。
21. **arrayContains**(数组包含)
```typescript
import { arrayContains } from "drizzle-orm";
const contains = await db.select({ id: posts.id }).from(posts)
.where(arrayContains(posts.tags, ['Typescript', 'ORM']));
```
```sql
select "id" from "posts" where "posts"."tags" @> {Typescript,ORM};
```
用于检查某列或表达式是否包含列表中的所有元素。
22. **arrayContained**(数组被包含)
```typescript
import { arrayContained } from "drizzle-orm";
const contained = await db.select({ id: posts.id }).from(posts)
.where(arrayContained(posts.tags, ['Typescript', 'ORM']));
```
```sql
select "id" from "posts" where "posts"."tags" <@ {Typescript,ORM};
```
用于检查某列或表达式是否被列表中的所有元素包含。
23. **arrayOverlaps**(数组重叠)
```typescript
import { arrayOverlaps } from "drizzle-orm";
const overlaps = await db.select({ id: posts.id }).from(posts)
.where(arrayOverlaps(posts.tags, ['Typescript', 'ORM']));
```
```sql
select "id" from "posts" where "posts"."tags" && {Typescript,ORM}
```
用于检查某列或表达式是否与列表中的任何元素重叠。
这些条件操作符使得 Drizzle ORM 在构建复杂查询时非常灵活,能够满足各种不同的查询需求。
### 引用:
: [Filter and conditional operators](https://orm.drizzle.team/docs/operators)

@ -0,0 +1,50 @@
这个输出是 NestJS CLI (`nest-cli`) 的帮助命令 `nest g -h``nest generate -h` 的结果,它解释了如何使用 `nest generate` 或其简写 `nest g` 命令来生成 NestJS 应用程序的不同部分。以下是命令和选项的详细解释:
### 命令用法
```plaintext
nest generate|g [options] <schematic> [name] [path]
```
- `nest generate``nest g`:这是用来生成新元素的命令。
- `[options]`:可选的命令行选项。
- `<schematic>`:要生成的元素类型,例如 `controller`、`service` 等。
- `[name]`:新生成元素的名称。
- `[path]`:指定一个路径,在该路径下生成元素(如果未指定,则在项目根目录生成)。
### 可用的 Schematics
列表显示了所有可用的 schematics(即元素类型),它们是:
- `application`:`application`生成一个新的应用程序工作区。
- `class`:`cl`生成一个新的类。
- `configuration`:`config`生成一个 CLI 配置文件。
- `controller`:`co`生成一个控制器声明。
- `decorator`:`d`生成一个自定义装饰器。
- `filter`:`f`生成一个异常过滤器声明。
- `gateway`:`ga`生成一个网关声明(用于 NestJS 微服务)。
- `guard`:`gu`生成一个守卫声明。
- `interceptor`:`itc`生成一个拦截器声明。
- `interface`:`itf`生成一个接口。
- `library`:`lib`在 monorepo(单一代码库)中生成一个新的库。
- `middleware`:`mi`生成一个中间件声明。
- `module`:`mo`生成一个模块声明。
- `pipe`:`pi`生成一个管道声明。
- `provider`:`pr`生成一个服务提供者声明。
- `resolver`:`r`生成一个 GraphQL 解析器声明。
- `resource`:`res`生成一个新的 CRUD 资源。
- `service`:`s`生成一个服务声明。
- `sub-app`:`app`在 monorepo 中生成一个新的应用程序。
### 选项
- `--dry-run``-d`:模拟操作,报告将要执行的动作,但不会实际写入任何结果。
- `--project [project]``-p`:指定在哪个项目中生成文件。
- `--flat`:强制生成的元素结构为扁平化,即不创建额外的目录。
- `--no-flat`:确保为生成的元素创建目录。
- `--spec`:强制生成相应的测试文件(spec 文件),这是默认行为。
- `--spec-file-suffix [suffix]`:为测试文件使用自定义的后缀。
- `--skip-import`:跳过导入步骤。
- `--no-spec`:禁用生成测试文件。
- `--collection [collectionName]``-c`:使用指定的 schematics 集合。
### 帮助选项
- `--help``-h`:输出命令的使用信息。
通过这些命令和选项,你可以快速生成 NestJS 应用程序的骨架代码,从而加速开发过程。

File diff suppressed because it is too large Load Diff

@ -0,0 +1,337 @@
这些装饰器是 `@nestjs/swagger` 包提供的一部分,用于增强 Swagger 文档的可读性和完整性。以下是每个装饰器的详细介绍和使用方法:
### @ApiOperation()
- **位置**: 方法
- **用途**: 为 API 操作添加一个简短的描述。
```typescript
@ApiOperation({ summary: '描述这个操作', description: '更详细的描述信息' })
```
### @ApiResponse()
- **位置**: 方法/控制器
- **用途**: 定义一个预期的响应。
```typescript
@ApiResponse({ status: 200, description: '描述响应', type: SomeType })
```
### @ApiProduces()
- **位置**: 方法/控制器
- **用途**: 指定 API 可以返回哪些 MIME 类型。
```typescript
@ApiProduces(['application/json'])
```
### @ApiConsumes()
- **位置**: 方法/控制器
- **用途**: 指定 API 可以接收哪些 MIME 类型。
```typescript
@ApiConsumes(['application/json'])
```
### @ApiBearerAuth()
- **位置**: 方法/控制器
- **用途**: 表示该 API 使用 Bearer Token 进行身份验证。
```typescript
@ApiBearerAuth()
```
### @ApiOAuth2()
- **位置**: 方法/控制器
- **用途**: 表示该 API 使用 OAuth2 进行身份验证,并允许指定授权流程的详细信息。
```typescript
@ApiOAuth2(['read', 'write'])
```
### @ApiBasicAuth()
- **位置**: 方法/控制器
- **用途**: 表示该 API 使用基本认证。
```typescript
@ApiBasicAuth()
```
### @ApiSecurity()
- **位置**: 方法/控制器
- **用途**: 为 API 添加安全定义的引用。
```typescript
@ApiSecurity('APIKey')
```
### @ApiExtraModels()
- **位置**: 方法/控制器
- **用途**: 允许在 Swagger 文档中包含额外的模型,即使它们没有被直接使用。
```typescript
@ApiExtraModels(ExtraModelType)
```
### @ApiBody()
- **位置**: 方法
- **用途**: 描述请求体。
```typescript
@ApiBody({ type: CreateCatDto })
```
### @ApiParam()
- **位置**: 方法
- **用途**: 描述一个路径参数或查询参数。
```typescript
@ApiParam({ name: 'id', required: true })
```
### @ApiQuery()
- **位置**: 方法
- **用途**: 描述一个查询参数。
```typescript
@ApiQuery({ name: 'query', required: false })
```
### @ApiHeader()
- **位置**: 方法/控制器
- **用途**: 描述一个 HTTP 头参数。
```typescript
@ApiHeader({ name: 'X-Custom-Header', required: false })
```
### @ApiExcludeEndpoint()
- **位置**: 方法
- **用途**: 从 Swagger 文档中排除一个特定的端点。
```typescript
@ApiExcludeEndpoint()
```
### @ApiTags()
- **位置**: 方法/控制器
- **用途**: 为 API 端点添加一个或多个标签。
```typescript
@ApiTags('cats')
```
### @ApiProperty()
- **位置**: 模型
- **用途**: 为模型的属性添加元数据。
```typescript
class CatDto {
@ApiProperty()
name: string;
}
```
### @ApiPropertyOptional()
- **位置**: 模型
- **用途**: 表示模型属性是可选的。
```typescript
class CatDto {
@ApiPropertyOptional()
age?: number;
}
```
### @ApiHideProperty()
- **位置**: 模型
- **用途**: 在 Swagger 文档中隐藏模型的属性。
```typescript
class CatDto {
@ApiHideProperty()
privateInfo: string;
}
```
### @ApiExtension()
- **位置**: 模型
- **用途**: 添加自定义的字段到 Swagger 模型定义。
```typescript
class CatDto {
@ApiExtension('x-omitempty', true)
@ApiProperty()
name: string;
}
```
使用这些装饰器,你可以为 NestJS 应用中的每个 API 端点提供丰富的 Swagger 文档,从而帮助开发者更好地理解和使用你的 API。
`@ApiProperty` 装饰器有许多参数,可以用来描述属性的各种信息。以下是常用的参数及其描述:
### 常用参数
1. **description**: 属性的描述。
```typescript
@ApiProperty({ description: 'The username of the user' })
username: string;
```
2. **type**: 属性的数据类型。可以是基本类型、数组或类类型。
```typescript
@ApiProperty({ type: String })
username: string;
@ApiProperty({ type: [String] })
roles: string[];
```
3. **example**: 属性的示例值。
```typescript
@ApiProperty({ example: 'john_doe' })
username: string;
```
4. **enum**: 属性的枚举值。
```typescript
enum UserRole {
Admin = 'admin',
User = 'user',
}
@ApiProperty({ enum: UserRole })
role: UserRole;
```
5. **default**: 属性的默认值。
```typescript
@ApiProperty({ default: 'user' })
role: string;
```
6. **required**: 属性是否为必需的(默认为 true)。
```typescript
@ApiProperty({ required: false })
email?: string;
```
7. **format**: 属性的数据格式,例如 `email`, `date-time`, `uuid` 等。
```typescript
@ApiProperty({ format: 'email' })
email: string;
```
8. **minimum**: 数字属性的最小值。
```typescript
@ApiProperty({ minimum: 1 })
age: number;
```
9. **maximum**: 数字属性的最大值。
```typescript
@ApiProperty({ maximum: 100 })
age: number;
```
10. **minLength**: 字符串属性的最小长度。
```typescript
@ApiProperty({ minLength: 5 })
password: string;
```
11. **maxLength**: 字符串属性的最大长度。
```typescript
@ApiProperty({ maxLength: 20 })
username: string;
```
12. **minItems**: 数组属性的最小项目数。
```typescript
@ApiProperty({ minItems: 1 })
tags: string[];
```
13. **maxItems**: 数组属性的最大项目数。
```typescript
@ApiProperty({ maxItems: 10 })
tags: string[];
```
14. **uniqueItems**: 数组项目是否唯一。
```typescript
@ApiProperty({ uniqueItems: true })
tags: string[];
```
15. **pattern**: 字符串属性的正则表达式模式。
```typescript
@ApiProperty({ pattern: '/^[a-z0-9]+$/i' })
username: string;
```
### 示例
以下是一个包含多个 `@ApiProperty` 参数的示例 DTO 类:
```typescript
import { IsString, Length, IsEmail, IsEnum, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
enum UserRole {
Admin = 'admin',
User = 'user',
}
export class CreateUserDto {
@ApiProperty({
description: 'The username of the user',
example: 'john_doe',
minLength: 5,
maxLength: 20,
pattern: '^[a-zA-Z0-9]+$',
})
@IsString()
@Length(5, 20)
username: string;
@ApiProperty({
description: 'The password of the user',
example: 's3cr3t',
minLength: 8,
})
@IsString()
@Length(8, 50)
password: string;
@ApiProperty({
description: 'The email of the user',
example: 'john_doe@example.com',
format: 'email',
})
@IsEmail()
email: string;
@ApiProperty({
description: 'The role of the user',
enum: UserRole,
example: UserRole.User,
default: UserRole.User,
})
@IsEnum(UserRole)
role: UserRole;
@ApiProperty({
description: 'The tags associated with the user',
type: [String],
example: ['admin', 'user'],
required: false,
})
@IsOptional()
tags?: string[];
}
```
通过配置这些参数,可以为属性提供丰富的元数据,从而生成更详细的 Swagger 文档。

@ -0,0 +1,58 @@
在 NestJS 应用中,拦截器(Interceptors)、中间件(Middlewares)、过滤器(Filters)、管道(Pipes)、守卫(Guards)和控制器(Controllers)都是构成应用程序请求-响应周期的不同组件,每个组件都有其特定的用途和执行时机。下面是它们各自的作用和区别:
1. **控制器(Controllers)**
- 作用:处理 HTTP 请求并返回响应。控制器定义了应用程序的 API 端点。
- 执行时机:请求被路由到特定的控制器方法时。
2. **中间件(Middlewares)**
- 作用:在请求处理管道的任何阶段执行自定义代码,可以处理请求体、修改请求头、结束请求或将请求传递给下一个中间件。
- 执行时机:请求进入 NestJS 应用后,控制器之前。
3. **守卫(Guards)**
- 作用:保护路由或端点,可以基于权限、角色或其他条件允许或拒绝请求。
- 执行时机:请求通过中间件后,管道中的下一个拦截器、过滤器或管道之前。
4. **过滤器(Filters)**
- 作用:处理请求过程中发生的错误。可以捕获异常或处理应用程序错误。
- 执行时机:请求通过守卫后,如果发生错误,响应通过管道之前。
5. **管道(Pipes)**
- 作用:用于数据验证和转换,可以应用于路由处理程序的参数、返回值或 HTTP 客户端。
- 执行时机:请求通过守卫后,控制器方法之前。
6. **拦截器(Interceptors)**
- 作用:拦截请求或响应,可以改变请求数据、响应数据或执行附加操作。
- 执行时机:请求通过管道后,控制器方法之后,响应通过管道之前。
7. **守卫(Guards)**(再次提及,以强调其在请求处理流程中的位置):
- 作用:守卫通常用于权限控制,确保只有符合条件的请求能够到达控制器。
- 执行时机:在过滤器之前,管道之后。
这些组件按照请求处理的顺序排列如下:
```
请求进入应用
|
v
中间件
|
v
守卫
|
v
管道
|
v
控制器
|
v
拦截器
|
v
过滤器(处理错误)
|
v
响应返回给客户端
```
每个组件都是 NestJS 请求处理管道中的一部分,它们协同工作,提供了一种灵活和模块化的方式来构建和管理应用程序的请求-响应周期。

@ -0,0 +1,36 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: drizzle20240508
// | @文件描述: drizzle.config.js -
// | @创建时间: 2024-05-08 15:59
// | @更新时间: 2024-05-08 15:59
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { defineConfig } from 'drizzle-kit';
const config = {
dialect: 'mysql',
schema: './src/entities/schema.ts',
out: './src/entities',
// 'pg' | 'mysql2' | 'better-sqlite' | 'libsql' | 'turso'
// driver: 'mysql2',
dbCredentials: {
host: '172.16.1.10',
port: 3306,
user: 'root',
password: 'Hxl1314521',
database: 'pacauth',
},
verbose: true,
introspect: {
// 启用驼峰命名
casing: 'camel',
},
};
export default defineConfig(config);

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

@ -0,0 +1,98 @@
{
"name": "pac-auth",
"version": "0.0.1",
"description": "",
"author": "",
"type": "commonjs",
"private": true,
"license": "UNLICENSED",
"imports": {
"#/*": "./src/*",
"#config/*": "./src/config/*",
"#common/*": "./src/common/*",
"#utils/*": "./src/utils/*"
},
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"sendEmail": "node ./test/testsend.js",
"makeEntity": "drizzle-kit introspect",
"sqlV": "drizzle-kit studio",
"getOldPackage": "pnpm outdated",
"updatePackage": "pnpm update --latest"
},
"dependencies": {
"@fastify/static": "^7.0.4",
"@nestjs/common": "^10.3.9",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.3.9",
"@nestjs/platform-express": "^10.3.9",
"@nestjs/platform-fastify": "^10.3.9",
"@nestjs/swagger": "^7.3.1",
"change-case-all": "^2.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"colors": "^1.4.0",
"drizzle-orm": "^0.31.0",
"fastify": "^4.27.0",
"mysql2": "^3.10.0",
"nodemailer": "^6.9.13",
"redis": "^4.6.14",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"ua-parser-js": "^1.0.38",
"winston": "^3.13.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@nestjs/cli": "^10.3.2",
"@nestjs/schematics": "^10.1.1",
"@nestjs/testing": "^10.3.9",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.1",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"drizzle-kit": "^0.22.1",
"eslint": "=8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.7.0",
"prettier": "^3.3.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.4",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.4.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

@ -0,0 +1,30 @@
import { Controller, Get, Req } from '@nestjs/common';
import { AppService } from './app.service';
import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger';
import { FastifyRequest } from 'fastify';
import { RedisService } from '@common/service/redis/redis.service';
@ApiTags('首页Tags')
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private readonly redisService: RedisService,
) {}
@ApiOperation({
summary: 'Get操作',
description: '从这里访问根路径',
tags: ['xsx'],
})
@ApiTags('首页默认方法')
@ApiProduces('application/json')
@Get()
async getHello(@Req() req: FastifyRequest): Promise<string> {
// console.log(req.raw['browserInfo']);
this.redisService.setNX('init', 'bigwang').then((value) => {
// console.log(value);
});
return this.appService.getHello();
}
}

@ -0,0 +1,62 @@
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import configuration from '@config/configuration';
import { LogRequestInfoMiddleware } from '@common/middleware/log-request-info/log-request-info.middleware';
import { TestModule } from '@app/test/test.module';
import { UserOperationLogInterceptor } from '@common/interceptor/user-operation-log/user-operation-log.interceptor';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { AppController } from '@app/app.controller';
import { AppService } from '@app/app.service';
import { UserAgentMiddleware } from '@common/middleware/useragent/useragent.middleware';
import { CoreServiceModule } from './core-service/core-service.module';
import { HttpExceptFilter } from '@common/http-except/http-except.filter';
import { CoreDictModule } from './core-dict/core-dict.module';
import { GlobalModule } from '@app/global.module';
import { CoreEnvModule } from './core-env/core-env.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
}),
TestModule,
CoreServiceModule,
CoreDictModule,
GlobalModule,
CoreEnvModule,
],
controllers: [AppController],
providers: [
HttpExceptFilter,
/**
*
* */
UserOperationLogInterceptor,
{
provide: APP_INTERCEPTOR,
useExisting: UserOperationLogInterceptor,
},
AppService,
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// 请求信息中间件
consumer
.apply(LogRequestInfoMiddleware)
// .exclude(
// { path: 'cats', method: RequestMethod.GET },
// { path: 'cats', method: RequestMethod.POST },
// 'cats/(.*)',
// )
.forRoutes({ path: '*', method: RequestMethod.ALL });
/**
*
* */
consumer.apply(UserAgentMiddleware).forRoutes({ path: '*', method: RequestMethod.ALL });
}
}

@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
async getHello(): Promise<string> {
return 'Hello';
return await new Promise((res) => {
setTimeout(() => {
res('Hello!');
}, 30);
});
// return 'Hello World!';
}
}

@ -0,0 +1,74 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { CoreDictService } from './core-dict.service';
import { CreateCoreDictDto } from './dto/create-core-dict.dto';
import { UpdateCoreDictDto } from './dto/update-core-dict.dto';
import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger';
import { PacInfo } from '@common/decorator/pac-info/pac-info.decorator';
import { PacInfoType } from '@utils/myType';
import { GetPacCoreDictAllDto, PacCoreDictIdDto, PacCoreDictKeyDto, PacCoreDictTargetListDto } from '@app/core-dict/dto/get-core-dict.dto';
@ApiTags('字典服务')
@Controller('coredict')
export class CoreDictController {
constructor(private readonly coreDictService: CoreDictService) {}
@ApiOperation({
summary: '添加字典',
description: '增加一个字典内容,可以是具体的项,也可以是一个层级',
})
@ApiProduces('application/json')
@Post()
create(@Body() createCoreDictDto: CreateCoreDictDto, @PacInfo() pacInfo: PacInfoType) {
return this.coreDictService.create(createCoreDictDto, pacInfo);
}
@ApiOperation({
summary: '查字典',
description: '查询字典分页或者列表',
})
@ApiProduces('application/json')
@Get()
findAll(@Query() getCoreDictAllDto: GetPacCoreDictAllDto, @PacInfo() pacInfo: PacInfoType) {
return this.coreDictService.findAll(getCoreDictAllDto);
}
@ApiOperation({
summary: '查字典详情',
description: '查询字典详细信息',
})
@ApiProduces('application/json')
@Get('/details/:dictId')
findOne(@Param() pacCoreDictIdDto: PacCoreDictIdDto) {
return this.coreDictService.findOne(pacCoreDictIdDto);
}
@ApiOperation({
summary: '查字典下的项/树',
description: '查询字典下的项,或者树',
})
@ApiProduces('application/json')
@Get('/list/:dictKey')
findTargetList(@Param() pacCoreDictKeyDto: PacCoreDictKeyDto, @Query() pacCoreDictTreeDto: PacCoreDictTargetListDto) {
return this.coreDictService.findTargetList(pacCoreDictKeyDto, pacCoreDictTreeDto);
}
@ApiOperation({
summary: '修改字典内容',
description: '。',
})
@ApiProduces('application/json')
@Patch(':dictId')
update(@Param() pacCoreDictIdDto: PacCoreDictIdDto, @Body() updateCoreDictDto: UpdateCoreDictDto, @PacInfo() pacInfo: PacInfoType) {
return this.coreDictService.update(pacCoreDictIdDto, updateCoreDictDto, pacInfo);
}
@ApiOperation({
summary: '删除指定字典项',
description: '删除指定字典项,子项存在不允许删除',
})
@ApiProduces('application/json')
@Delete(':dictId')
remove(@Param() pacCoreDictIdDto: PacCoreDictIdDto, @PacInfo() pacInfo: PacInfoType) {
return this.coreDictService.remove(pacCoreDictIdDto, pacInfo);
}
}

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { CoreDictService } from './core-dict.service';
import { CoreDictController } from './core-dict.controller';
@Module({
controllers: [CoreDictController],
providers: [CoreDictService],
})
export class CoreDictModule {}

@ -0,0 +1,414 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateCoreDictDto } from './dto/create-core-dict.dto';
import { UpdateCoreDictDto } from './dto/update-core-dict.dto';
import { LoggerService } from '@service/logger/logger.service';
import { MysqlService } from '@common/service/mysql/mysql.service';
import { RedisService } from '@common/service/redis/redis.service';
import { pacAuthUser, pacCoreDict, pacCoreService } from '@entities/schema';
import { and, asc, desc, eq, isNull, like, or, SQL, sql } from 'drizzle-orm';
import { Snowflake } from '@service/snowflake/snowflake.service';
import { GetPacCoreDictAllDto, PacCoreDictIdDto, PacCoreDictKeyDto, PacCoreDictTargetListDto } from '@app/core-dict/dto/get-core-dict.dto';
import { ConfigService } from '@nestjs/config';
import { likeQuery } from '@utils/likeQuery';
import { QueryBuilder } from 'drizzle-orm/mysql-core';
import { PacInfoType } from '@utils/myType';
import { isTrueEnum, isExistKey } from '@utils/boolean.enum';
import { customDrizzleRowWithRecursive } from '@utils/customDrizzleRowWithRecursive';
@Injectable()
export class CoreDictService {
// # 详细的字典信息
private readonly dictMoreDataType = {
dictId: pacCoreDict.dictId,
pid: pacCoreDict.pid,
dictKey: pacCoreDict.dictKey,
dictDesc: pacCoreDict.dictDesc,
dictName: pacCoreDict.dictName,
dictIcon: pacCoreDict.dictIcon,
dictType: pacCoreDict.dictType,
ownOfPac: pacCoreDict.ownOfPac,
orderNum: pacCoreDict.orderNum,
status: pacCoreDict.status,
serviceOf: pacCoreDict.serviceOf,
serviceName: pacCoreService.serviceName,
haveChildren: pacCoreDict.haveChildren,
createby: pacCoreDict.createby,
createName: pacAuthUser.nickname,
createtime: pacCoreDict.createtime,
updateby: pacCoreDict.updateby,
updatetime: pacCoreDict.updatetime,
};
// # 字典列表信息
private readonly dictListDataType = {
dictId: pacCoreDict.dictId,
pid: pacCoreDict.pid,
dictKey: pacCoreDict.dictKey,
dictName: pacCoreDict.dictName,
dictIcon: pacCoreDict.dictIcon,
dictType: pacCoreDict.dictType,
haveChildren: pacCoreDict.haveChildren,
};
constructor(
private readonly logger: LoggerService,
private readonly mysqlService: MysqlService,
private readonly redisService: RedisService,
private readonly snowflake: Snowflake,
private readonly config: ConfigService,
) {}
/** Service
* NAME: create
* DESC: 创建字典内容
* DATE: 2024-05-31 19:12:47 -
* */
public async create(createCoreDictDto: CreateCoreDictDto, userInfo) {
// ! 加目标锁
const lock = await this.redisService.distributedLock('DICT' + createCoreDictDto.dictKey, createCoreDictDto.dictKey);
// ? 存在正在进行写入的字典
if (!lock) throw new HttpException('服务繁忙,字典标识重复!', HttpStatus.CONFLICT);
// @ 核心内容
try {
// ! 查重
const result = await this.getDictDataForDictKey(createCoreDictDto.dictKey);
// ? 是否存在重复的字典
if (result.length > 0) throw new HttpException('字典标识重复!', HttpStatus.CONFLICT);
// ! 添加字典数据
const newPacCoreDict = await this.addDictData(createCoreDictDto, userInfo.userId);
// ! 解锁
lock();
// !更新父节点的子节点数量
await this.haveChildrenSelfIncreasing(createCoreDictDto.pid);
// ! 返回结果
return newPacCoreDict;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
}
/** Service
* NAME: findAll
* DESC: 查找字典分页/
* DATE: 2024-05-31 21:33:40 -
* */
public async findAll(getCoreDictAllDto: GetPacCoreDictAllDto) {
// ? 是否查询列表
if (isExistKey(getCoreDictAllDto, 'isList') && isTrueEnum(getCoreDictAllDto['isList'])) {
// ! 返回列表数据
return await this.getDictList(getCoreDictAllDto);
} else {
// ! 返回分页数据
return await this.getDictPage(getCoreDictAllDto);
}
}
/** Service
* NAME: findOne
* DESC: 查找字典项详细信息
* DATE: 2024-06-04 19:21:40 -
* */
public async findOne(pacCoreDictIdDto: PacCoreDictIdDto) {
return (await this.getMore(pacCoreDictIdDto.dictId))[0];
}
/** Service
* NAME: findTargetList
* DESC: 查找字典下的列表或树
* DATE: 2024-06-04 19:21:40 -
* */
public async findTargetList(pacCoreDictKeyDto: PacCoreDictKeyDto, pacCoreDictTargetListDto: PacCoreDictTargetListDto) {
const [result] = await this.getTargetDictList(pacCoreDictKeyDto, pacCoreDictTargetListDto);
return result;
}
/** Service
* NAME: update
* DESC: 更新字典
* DATE: 2024-06-04 23:24:18 -
* */
public async update(pacCoreDictIdDto: PacCoreDictIdDto, updateCoreDictDto: UpdateCoreDictDto, pacInfo: PacInfoType) {
// ! 上锁
const lock = await this.redisService.distributedLock('DICT' + updateCoreDictDto.dictKey, updateCoreDictDto.dictKey);
// ? 存在正在运行的字典
if (!lock) throw new HttpException('服务繁忙,字典标识重复!', HttpStatus.CONFLICT);
// @ 核心业务
try {
// ! 通过dictKey获取字典信息 重复
const checkRepeat = await this.getDictDataForDictKey(updateCoreDictDto.dictKey);
// ? 判断是否存在重名的字典,但是不包括自己
if (checkRepeat.length > 0 && checkRepeat[0].dictId != pacCoreDictIdDto.dictId) {
throw new HttpException('服务繁忙,字典标识重复!', HttpStatus.CONFLICT);
}
// ! 更新数据
const result = await this.updateDictData(pacCoreDictIdDto, updateCoreDictDto, pacInfo);
// ! 解锁
lock();
// ! 返回
return result;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
}
/** Service
* NAME: remove
* DESC: 删除字典
* DATE: 2024-06-04 22:01:11 -
* */
public async remove(pacCoreDictIdDto: PacCoreDictIdDto, pacInfo: PacInfoType) {
// ! 查看目标字典项
const result = await this.getMore(pacCoreDictIdDto.dictId);
// ? 判断是否存在
if (result.length > 0) {
// ! 存在目标字典项
// ? 判断是否存在子项
if (result[0].haveChildren == 0) {
// ! 删除字典
await this.deleteDictItem(pacCoreDictIdDto, pacInfo);
} else {
throw new HttpException('该字典存在子项!', HttpStatus.BAD_REQUEST);
}
// ! 判断父节点是否存在
if (result[0].pid != 0) {
// ! 减少父级子节点数量
await this.haveChildrenSelfIncreasing(result[0].pid, false);
}
return '删除字典成功';
} else {
// ! 不存在目标字典项,直接返回
return '删除字典成功';
}
}
// DB 根据serviceKey查找服务数据 可用于查重
private async getDictDataForDictKey(dictKey: string) {
return await this.mysqlService.db.select().from(pacCoreDict).where(eq(pacCoreDict.dictKey, dictKey));
}
// DB 写入Dict数据
private async addDictData(createCoreDictDto: CreateCoreDictDto, userId) {
// ! 生成雪花id,用于字典主键
const id = await this.snowflake.generate();
// ? 判断身份是否是pac,如果是pac才能写入是pac的数据
const isPac = this.config.get<number>('masterId') == userId && isTrueEnum(createCoreDictDto.ownOfPac);
// ! 定义写入的字典数据
const newDictData: typeof pacCoreDict.$inferInsert = {
pid: createCoreDictDto.pid,
dictId: id as any,
dictKey: createCoreDictDto.dictKey,
dictDesc: createCoreDictDto.dictDesc,
dictName: createCoreDictDto.dictName,
dictIcon: createCoreDictDto.dictIcon,
dictType: createCoreDictDto.dictType,
ownOfPac: isPac ? 0 : 1,
orderNum: createCoreDictDto.orderNum,
serviceOf: createCoreDictDto.serviceOf,
createby: userId,
createtime: sql`now()` as any,
};
return await this.mysqlService.db.insert(pacCoreDict).values(newDictData);
}
// DB 更新父级子元素数量
private async haveChildrenSelfIncreasing(dictId: string, isAdd = true) {
return this.mysqlService.db
.update(pacCoreDict)
.set({
haveChildren: isAdd ? sql`${pacCoreDict.haveChildren} + 1` : sql`${pacCoreDict.haveChildren} - 1`,
})
.where(eq(pacCoreDict.dictId as any, dictId));
}
// DB 查字典项的查询函数QueryBuilder,作用于列表和分页查询
private getDict(data, selectData = undefined) {
this.logger.info(JSON.stringify(data, null, 2));
// ! 定义基础查询函数
// 启用动态查询模式 $dynamic
let query = this.mysqlService.db
.select(selectData)
.from(pacCoreDict)
.where(isNull(pacCoreDict.deleteby))
.orderBy(
isTrueEnum(data.isAsc) ? asc(pacCoreDict.orderNum) : desc(pacCoreDict.orderNum),
isTrueEnum(data.isAsc) ? asc(pacCoreDict.dictId) : desc(pacCoreDict.dictId),
)
.$dynamic();
// ? 模糊查询
if (isExistKey(data, 'dictInfo')) {
query = query.where(
or(
like(pacCoreDict.dictKey, likeQuery(data.dictInfo)),
like(pacCoreDict.dictName, likeQuery(data.dictInfo)),
like(pacCoreDict.dictDesc, likeQuery(data.dictInfo)),
),
);
}
// ? 是否查pac的数据
if (isExistKey(data, 'ownOfPac') && isTrueEnum(data['ownOfPac'])) {
query = query.where(eq(pacCoreDict.ownOfPac, isTrueEnum(data['ownOfPac']) ? 0 : 1));
}
// ? 按照层级查
if (isExistKey(data, 'hierarchy')) {
query = query.where(eq(pacCoreDict.pid, data.hierarchy));
}
// ? 是否存在目标service
if (isExistKey(data, 'serviceOf')) {
query = query.where(eq(pacCoreDict.serviceOf, data.serviceOf));
}
// ? 是否查字典类型
if (isExistKey(data, 'dictType')) {
query = query.where(eq(pacCoreDict.dictType, data.dictType));
}
// ? 是否查字典状态
if (isExistKey(data, 'status')) {
query = query.where(eq(pacCoreDict.status, data.status));
}
return query;
}
// DB 查Dict列表
private getDictList(getCoreDictAllDto: GetPacCoreDictAllDto) {
return this.getDict(getCoreDictAllDto, this.dictListDataType);
}
// DB 查Dict分页
private async getDictPage(getCoreDictAllDto: GetPacCoreDictAllDto) {
const offset = (getCoreDictAllDto.pageNumber - 1) * getCoreDictAllDto.pageSize;
// 使用基础查询构建查询总记录数
const totalCountQuery = this.getDict(getCoreDictAllDto, {
totalCount: sql`COUNT(*)`,
});
// 使用基础查询构建分页查询
const paginatedQuery = this.getDict(getCoreDictAllDto, this.dictMoreDataType)
.leftJoin(pacAuthUser, eq(pacCoreDict.createby, pacAuthUser.userId))
.leftJoin(pacCoreService, eq(pacCoreService.serviceKey, pacCoreDict.serviceOf))
.limit(getCoreDictAllDto.pageSize)
.offset(offset);
return {
total: (await totalCountQuery)[0].totalCount,
rowData: await paginatedQuery,
searchData: getCoreDictAllDto,
};
}
// DB 通过ID查找Dict详细信息
private async getMore(dictId: string) {
return this.mysqlService.db
.select(this.dictMoreDataType)
.from(pacCoreDict)
.leftJoin(pacAuthUser, eq(pacCoreDict.createby, pacAuthUser.userId))
.leftJoin(pacCoreService, eq(pacCoreService.serviceKey, pacCoreDict.serviceOf))
.where(and(eq(pacCoreDict.dictId, dictId), isNull(pacCoreDict.deleteby)));
}
// DB 通过Key查找目标字典的信息?树结构
private getTargetDictList(pacCoreDictKeyDto: PacCoreDictKeyDto, pacCoreDictTargetListDto: PacCoreDictTargetListDto) {
if (isExistKey(pacCoreDictTargetListDto, 'isTree') && isTrueEnum(pacCoreDictTargetListDto['isTree'])) {
// ! 基础层级
const baseQueryBuilder = new QueryBuilder();
const baseQuery = baseQueryBuilder
.select({
...this.dictListDataType,
level: sql`0`.as('level'),
})
.from(pacCoreDict)
.where(and(isNull(pacCoreDict.deleteby), eq(pacCoreDict.dictKey, pacCoreDictKeyDto.dictKey)));
// ! 递归层级
const recursiveQueryBuilder = new QueryBuilder();
const recursiveQuery = recursiveQueryBuilder
.select({
...this.dictListDataType,
level: sql`dictHierarchy.level + 1`.as('level'),
})
.from(pacCoreDict)
.where(isNull(pacCoreDict.deleteby))
.innerJoin(sql`dictHierarchy`, sql`dictHierarchy.dictId = ${pacCoreDict.pid}`);
const rowName: SQL = customDrizzleRowWithRecursive(this.dictListDataType);
// ! 执行原始SQL查询
return this.mysqlService.db.execute(
sql`WITH RECURSIVE dictHierarchy(${rowName}) AS(${baseQuery} UNION ALL ${recursiveQuery}) SELECT * FROM dictHierarchy`,
);
} else {
// ! 查询自己
return this.mysqlService.db
.select(this.dictMoreDataType)
.from(pacCoreDict)
.leftJoin(pacAuthUser, eq(pacCoreDict.createby, pacAuthUser.userId))
.leftJoin(pacCoreService, eq(pacCoreService.serviceKey, pacCoreDict.serviceOf))
.where(and(eq(pacCoreDict.dictKey, pacCoreDictKeyDto.dictKey), isNull(pacCoreDict.deleteby)));
}
}
// DB 通过ID删除字典项
private deleteDictItem(pacCoreDictIdDto: PacCoreDictIdDto, pacInfo: PacInfoType) {
return this.mysqlService.db
.update(pacCoreDict)
.set({
deletetime: sql`now()`,
deleteby: pacInfo.userId,
})
.where(eq(pacCoreDict.dictId, pacCoreDictIdDto.dictId));
}
// DB 更新字典
private updateDictData(pacCoreDictIdDto: PacCoreDictIdDto, updateCoreDictDto: UpdateCoreDictDto, pacInfo: PacInfoType) {
// ? 判断身份是否是pac,如果是pac才能写入是pac的数据
const isPac = this.config.get<number>('masterId') == pacInfo.userId && isTrueEnum(updateCoreDictDto.ownOfPac);
return this.mysqlService.db
.update(pacCoreDict)
.set({
dictKey: updateCoreDictDto.dictKey,
dictDesc: updateCoreDictDto.dictDesc,
dictName: updateCoreDictDto.dictName,
dictIcon: updateCoreDictDto.dictIcon,
dictType: updateCoreDictDto.dictType,
orderNum: updateCoreDictDto.orderNum,
status: updateCoreDictDto.status,
ownOfPac: isPac ? 0 : 1,
serviceOf: updateCoreDictDto.serviceOf,
updateby: pacInfo.userId,
updatetime: sql`now()`,
})
.where(eq(pacCoreDict.dictId, pacCoreDictIdDto.dictId));
}
}

@ -0,0 +1,146 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsBoolean,
IsEnum,
IsInt,
IsOptional,
IsString,
Length,
Max,
Min
} from "class-validator";
import Trim from '@common/decorator/trim/trim.decorator';
import {
BooleanEnum
} from "@utils/boolean.enum";
export class CreateCoreDictDto {
@ApiProperty({
description: '字典父ID',
type: String,
example: '0',
required: true,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsString({ message: '字典关联属性应为字符串格式!' })
@IsOptional()
readonly pid?: string = '0';
@ApiProperty({
description: '字典标志',
type: String,
example: 'Country',
required: true,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsString({ message: '字典标志应为字符串格式!' })
@Length(1, 32, { message: '请将字典标志长度控制在1到32位之间!' })
readonly dictKey: string;
@ApiProperty({
description: '字典描述信息',
type: String,
example: '这是一个关于国家的字典',
required: false,
minLength: 1,
maxLength: 255,
})
@Trim()
@IsString({ message: '字典描述信息应为字符串格式!' })
@Length(1, 255, { message: '请将字典描述信息长度控制在1到255位之间!' })
@IsOptional()
readonly dictDesc: string;
@ApiProperty({
description: '字典名称',
type: String,
example: '国家集合',
required: true,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsString({ message: '字典名称应为字符串格式!' })
@Length(1, 32, { message: '请将字典名称长度控制在1到32位之间!' })
readonly dictName: string;
@ApiProperty({
description: '字典图标',
type: String,
example: 'happy',
required: false,
minLength: 1,
maxLength: 255,
})
@Trim()
@IsString({ message: '字典图标应为字符串格式!' })
@Length(1, 255, { message: '请将字典图标长度控制在1到255位之间!' })
@IsOptional()
readonly dictIcon: string;
@ApiProperty({
description: '字典类型',
type: Number,
example: 0,
required: false,
minimum: 0,
})
@Min(0, {
message: '字典类型异常,需要大于等于0!',
})
@IsInt({
message: '字典类型异常,是一个整数!',
})
@IsOptional()
readonly dictType?: number = 0;
@ApiProperty({
description: '是否属于PAC',
type: BooleanEnum,
enum: BooleanEnum,
example: false,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'ownOfPac参数格式错误' })
@IsOptional()
readonly ownOfPac?: BooleanEnum = BooleanEnum.FALSE;
@ApiProperty({
description: '排序',
type: Number,
example: 10,
required: false,
minimum: -1000,
maximum: 1000,
})
@IsOptional()
@IsInt({
message: '排序必须是整数!',
})
@Min(-1000, {
message: '排序不能小于-1000!',
})
@Max(1000, {
message: '排序不能超过1000',
})
readonly orderNum?: number = 0;
@ApiProperty({
description: '服务的唯一标识',
type: String,
example: 'CORE-PAC-AUTH',
required: false,
minLength: 2,
maxLength: 16,
})
@Trim()
@IsString({ message: '服务标识应为字符串格式!' })
@Length(2, 16, { message: '请将服务标识长度控制在2到16位之间!' })
@IsOptional()
readonly serviceOf: string;
}

@ -0,0 +1,151 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: get-core-dict.dto.ts -
// | @创建时间: 2024-05-31 18:53
// | @更新时间: 2024-05-31 18:53
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { GetDto } from '@dto/get.dto';
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import { IsEnum, IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';
import { BooleanEnum } from '@utils/boolean.enum';
export class GetPacCoreDictAllDto extends GetDto {
@ApiProperty({
description: '字典信息',
type: String,
example: '国家',
required: false,
minLength: 1,
maxLength: 128,
})
@Trim()
@IsString({ message: '字典信息应为字符串格式!' })
@Length(0, 128, { message: '请将字典信息长度控制在1到128位之间!' })
@IsOptional()
readonly dictInfo?: string;
@ApiProperty({
description: '字典类型',
type: String,
example: 0,
required: false,
})
@Trim()
@IsString({ message: '字典类型应为字符串格式!' })
@IsOptional()
readonly dictType?: string;
@ApiProperty({
description: '是否属于PAC',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'ownOfPac参数格式错误' })
@IsOptional()
readonly ownOfPac: BooleanEnum;
@ApiProperty({
description: '服务的唯一标识',
type: String,
example: 'CORE-PAC-AUTH',
required: false,
minLength: 2,
maxLength: 16,
})
@Trim()
@IsString({ message: '服务标识应为字符串格式!' })
@Length(2, 16, { message: '请将服务标识长度控制在2到16位之间!' })
@IsOptional()
readonly serviceOf: string;
@ApiProperty({
description: '字典状态',
type: Number,
example: 0,
required: false,
minimum: -100,
maximum: 100,
})
@Trim()
@IsInt({
message: '字典状态必须是整数!',
})
@Min(-100, {
message: '字典状态需要大于-100!',
})
@Max(100, {
message: '字典状态不能超过100',
})
@IsOptional()
readonly status?: number;
@ApiProperty({
description: '字典层级id',
type: Number,
example: 0,
required: false,
minimum: 0,
maximum: 100,
})
@Trim()
@IsString({ message: '字典层级id应为字符串格式!' })
@Length(1, 20, { message: '字典层级id格式错误!' })
@IsOptional()
readonly hierarchy?: string;
}
export class PacCoreDictIdDto {
@ApiProperty({
description: '字典ID',
type: String,
example: '1123817263136',
required: true,
minLength: 19,
maxLength: 19,
})
@IsString()
@Length(19, 19, { message: '字典ID格式错误!' })
readonly dictId: string;
}
export class PacCoreDictKeyDto {
@ApiProperty({
description: '字典标志',
type: String,
example: 'Country',
required: true,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsString({ message: '字典标志应为字符串格式!' })
@Length(1, 32, { message: '请将字典标志长度控制在1到32位之间!' })
readonly dictKey: string;
}
export class PacCoreDictTargetListDto {
@ApiProperty({
description: '是否查找树结构',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, {
message: '查找树结构数据错误',
})
@IsOptional()
readonly isTree: BooleanEnum;
}

@ -0,0 +1,25 @@
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { CreateCoreDictDto } from './create-core-dict.dto';
import { IsInt, IsOptional, Max, Min } from 'class-validator';
export class UpdateCoreDictDto extends PartialType(CreateCoreDictDto) {
@ApiProperty({
description: '状态',
type: Number,
example: 10,
required: false,
minimum: -100,
maximum: 100,
})
@IsOptional()
@IsInt({
message: '状态必须是整数!',
})
@Min(-1000, {
message: '状态不能小于-100!',
})
@Max(1000, {
message: '状态不能超过100',
})
readonly status: number;
}

@ -0,0 +1,74 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { CoreEnvService } from './core-env.service';
import { CreateCoreEnvDto } from './dto/create-core-env.dto';
import { UpdateCoreEnvDto } from './dto/update-core-env.dto';
import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger';
import { PacInfo } from '@common/decorator/pac-info/pac-info.decorator';
import { PacInfoType } from '@utils/myType';
import { CoreEnvIdDTO, CoreEnvKeyDTO, CoreEnvTargetListDTO, GetCoreEnvDTO } from '@app/core-env/dto/get-core-env.dto';
@ApiTags('环境变量服务')
@Controller('coreenv')
export class CoreEnvController {
constructor(private readonly coreEnvService: CoreEnvService) {}
@ApiOperation({
summary: '添加环境变量',
description: '增加一个环境变量内容,可以是具体的项,也可以是一个层级',
})
@ApiProduces('application/json')
@Post()
create(@Body() createCoreEnvDto: CreateCoreEnvDto, @PacInfo() pacInfo: PacInfoType) {
return this.coreEnvService.create(createCoreEnvDto, pacInfo);
}
@ApiOperation({
summary: '查环境变量',
description: '查询环境变量分页或者列表',
})
@ApiProduces('application/json')
@Get()
findAll(@Query() getCoreEnvDTO: GetCoreEnvDTO, @PacInfo() pacInfo: PacInfoType) {
return this.coreEnvService.findAll(getCoreEnvDTO);
}
@ApiOperation({
summary: '查环境变量详情',
description: '查询环境变量详细信息',
})
@ApiProduces('application/json')
@Get('/details/:envId')
findOne(@Param() coreEnvIdDTO: CoreEnvIdDTO) {
return this.coreEnvService.findOne(coreEnvIdDTO);
}
@ApiOperation({
summary: '查环境变量下的项/树',
description: '查询环境变量下的项,或者树',
})
@ApiProduces('application/json')
@Get('/list/:envKey')
findTargetList(@Param() coreEnvKeyDTO: CoreEnvKeyDTO, @Query() coreEnvTargetListDTO: CoreEnvTargetListDTO) {
return this.coreEnvService.findTargetList(coreEnvKeyDTO, coreEnvTargetListDTO);
}
@ApiOperation({
summary: '修改环境变量内容',
description: '。',
})
@ApiProduces('application/json')
@Patch(':envId')
update(@Param() coreEnvIdDTO: CoreEnvIdDTO, @Body() updateCoreEnvDto: UpdateCoreEnvDto, @PacInfo() pacInfo: PacInfoType) {
return this.coreEnvService.update(coreEnvIdDTO, updateCoreEnvDto, pacInfo);
}
@ApiOperation({
summary: '删除指定环境变量项',
description: '删除指定环境变量项,子项存在不允许删除',
})
@ApiProduces('application/json')
@Delete(':envId')
remove(@Param() coreEnvIdDTO: CoreEnvIdDTO, @PacInfo() pacInfo: PacInfoType) {
return this.coreEnvService.remove(coreEnvIdDTO, pacInfo);
}
}

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { CoreEnvService } from './core-env.service';
import { CoreEnvController } from './core-env.controller';
@Module({
controllers: [CoreEnvController],
providers: [CoreEnvService],
})
export class CoreEnvModule {}

@ -0,0 +1,438 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateCoreEnvDto } from './dto/create-core-env.dto';
import { UpdateCoreEnvDto } from './dto/update-core-env.dto';
import { LoggerService } from '@service/logger/logger.service';
import { MysqlService } from '@common/service/mysql/mysql.service';
import { RedisService } from '@common/service/redis/redis.service';
import { Snowflake } from '@service/snowflake/snowflake.service';
import { ConfigService } from '@nestjs/config';
import { pacAuthUser, pacCoreDict, pacCoreEnv, pacCoreService } from '@entities/schema';
import { and, asc, desc, eq, isNull, like, or, sql } from 'drizzle-orm';
import { isExistKey, isTrueEnum } from '@utils/boolean.enum';
import { CoreEnvIdDTO, CoreEnvKeyDTO, CoreEnvTargetListDTO, GetCoreEnvDTO } from '@app/core-env/dto/get-core-env.dto';
import { likeQuery } from '@utils/likeQuery';
import { QueryBuilder } from 'drizzle-orm/mysql-core';
import { customDrizzleRowWithRecursive } from '@utils/customDrizzleRowWithRecursive';
import { PacInfoType } from '@utils/myType';
import { PacCoreDictIdDto } from '@app/core-dict/dto/get-core-dict.dto';
import { UpdateCoreDictDto } from '@app/core-dict/dto/update-core-dict.dto';
@Injectable()
export class CoreEnvService {
// # 详细的环境变量信息
private readonly envListDataType = {
envId: pacCoreEnv.envId,
pid: pacCoreEnv.pid,
envName: pacCoreEnv.envName,
envKey: pacCoreEnv.envKey,
envVal: pacCoreEnv.envVal,
envDictVal: pacCoreDict.dictKey,
envDictName: pacCoreDict.dictName,
valIsDict: pacCoreEnv.valIsDict,
haveChildren: pacCoreEnv.haveChildren,
ownOfPac: pacCoreEnv.ownOfPac,
orderNum: pacCoreEnv.orderNum,
status: pacCoreEnv.status,
serviceOf: pacCoreEnv.serviceOf,
};
// # 环境变量列表信息
private readonly envMoreDataType = {
envId: pacCoreEnv.envId,
pid: pacCoreEnv.pid,
envName: pacCoreEnv.envName,
envKey: pacCoreEnv.envKey,
envVal: pacCoreEnv.envVal,
envDictVal: pacCoreDict.dictKey,
envDictName: pacCoreDict.dictName,
valIsDict: pacCoreEnv.valIsDict,
envDesc: pacCoreEnv.envDesc,
haveChildren: pacCoreEnv.haveChildren,
ownOfPac: pacCoreEnv.ownOfPac,
orderNum: pacCoreEnv.orderNum,
status: pacCoreEnv.status,
serviceOf: pacCoreEnv.serviceOf,
serviceName: pacCoreService.serviceName,
createby: pacCoreEnv.createby,
createName: pacAuthUser.nickname,
createtime: pacCoreEnv.createtime,
updateby: pacCoreEnv.updateby,
updatetime: pacCoreEnv.updatetime,
};
constructor(
private readonly logger: LoggerService,
private readonly mysqlService: MysqlService,
private readonly redisService: RedisService,
private readonly snowflake: Snowflake,
private readonly config: ConfigService,
) {}
/** Service
* NAME: create
* DESC: 创建环境变量内容
* DATE: 2024-06-05 11:48:04 -
* */
public async create(createCoreEnvDto: CreateCoreEnvDto, userInfo) {
// ! 加目标锁
const lock = await this.redisService.distributedLock('ENV' + createCoreEnvDto.envKey, createCoreEnvDto.envKey);
// ? 存在正在进行写入的环境变量
if (!lock) throw new HttpException('服务繁忙,环境变量标识重复!', HttpStatus.CONFLICT);
// @ 核心内容
try {
// ! 查重
const result = await this.getEnvForEnvKey(createCoreEnvDto.envKey);
// ? 是否存在重复的环境变量
if (result.length > 0) throw new HttpException('环境变量标识重复!', HttpStatus.CONFLICT);
// ! 添加环境变量数据
const newPacCoreEnv = await this.addEnvData(createCoreEnvDto, userInfo);
// ! 解锁
lock();
// !更新父节点的子节点数量
await this.haveChildrenSelfIncreasing(createCoreEnvDto.pid);
// ! 返回结果
return newPacCoreEnv;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
}
/** Service
* NAME: findAll
* DESC: 查找环境变量分页/
* DATE: 2024-06-05 12:58:53 -
* */
public async findAll(getCoreEnvDTO: GetCoreEnvDTO) {
// ? 是否查询列表
if (isExistKey(getCoreEnvDTO, 'isList') && isTrueEnum(getCoreEnvDTO['isList'])) {
// ! 返回列表数据
return await this.getEnvList(getCoreEnvDTO);
} else {
// ! 返回分页数据
return await this.getEnvPage(getCoreEnvDTO);
}
}
/** Service
* NAME: findOne
* DESC: 查找环境变量项详细信息
* DATE: 2024-06-05 14:58:44 -
* */
public async findOne(coreEnvIdDTO: CoreEnvIdDTO) {
return (await this.getMore(coreEnvIdDTO.envId))[0];
}
/** Service
* NAME: findTargetList
* DESC: 查找环境变量下的列表或树
* DATE: 2024-06-05 15:15:33 -
* */
public async findTargetList(coreEnvKeyDTO: CoreEnvKeyDTO, coreEnvTargetListDTO: CoreEnvTargetListDTO) {
const [result] = await this.getTargetDictList(coreEnvKeyDTO, coreEnvTargetListDTO);
return result;
}
public async update(coreEnvIdDTO: CoreEnvIdDTO, updateCoreEnvDto: UpdateCoreEnvDto, pacInfo: PacInfoType) {
// ! 上锁
const lock = await this.redisService.distributedLock('ENV' + updateCoreEnvDto.envKey, updateCoreEnvDto.envKey);
// ? 存在正在运行的环境变量
if (!lock) throw new HttpException('服务繁忙,环境变量标识重复!', HttpStatus.CONFLICT);
// @ 核心业务
try {
// ! 通过dictKey获取环境变量信息 重复
const checkRepeat = await this.getEnvForEnvKey(updateCoreEnvDto.envKey);
// ? 判断是否存在重名的环境变量,但是不包括自己
if (checkRepeat.length > 0 && checkRepeat[0].envId != coreEnvIdDTO.envId) {
throw new HttpException('环境变量标识重复!', HttpStatus.CONFLICT);
}
// ! 更新数据
const result = await this.updateEnvData(coreEnvIdDTO, updateCoreEnvDto, pacInfo);
// ! 解锁
lock();
// ! 返回
return result;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
}
/** Service
* NAME: remove
* DESC: 删除环境变量
* DATE: 2024-06-05 17:17:28 -
* */
public async remove(coreEnvIdDTO: CoreEnvIdDTO, pacInfo: PacInfoType) {
// ! 查看目标环境变量
const result = await this.getMore(coreEnvIdDTO.envId);
// ? 判断是否存在
if (result.length > 0) {
// ! 存在目标环境变量
// ? 判断是否存在子项
if (result[0].haveChildren == 0) {
// ! 删除目标环境变量
await this.deleteEnvItem(coreEnvIdDTO, pacInfo);
} else {
throw new HttpException('目标环境变量存在子项!', HttpStatus.BAD_REQUEST);
}
// ! 判断父节点是否存在
if (result[0].pid != 0) {
// ! 减少父级子节点数量
await this.haveChildrenSelfIncreasing(result[0].pid, false);
}
return '删除目标环境变量成功';
} else {
// ! 不存在目标环境变量,直接返回
return '删除目标环境变量成功';
}
}
// DB 通过EnvKey获取env信息,主要用于查重
private getEnvForEnvKey(envKey: string) {
return this.mysqlService.db.select().from(pacCoreEnv).where(eq(pacCoreEnv.envKey, envKey));
}
// DB 添加Env数据
private async addEnvData(createCoreEnvDto: CreateCoreEnvDto, userInfo) {
// ! 生成雪花id,用于环境变量主键
const id = await this.snowflake.generate();
// ? 判断身份是否是pac,如果是pac才能写入
const isPac = this.config.get<number>('masterId') == userInfo.userId && isTrueEnum(createCoreEnvDto.ownOfPac);
// ! 定义即将写入的环境变量数据
const newEnvData = {
envId: id,
pid: createCoreEnvDto.pid,
envName: createCoreEnvDto.envName,
envKey: createCoreEnvDto.envKey,
envVal: createCoreEnvDto.envVal,
valIsDict: isTrueEnum(createCoreEnvDto.valIsDict) ? 1 : 0,
envDesc: createCoreEnvDto.envDesc,
ownOfPac: isPac ? 0 : 1,
orderNum: createCoreEnvDto.orderNum,
serviceOf: createCoreEnvDto.serviceOf,
createby: userInfo.userId,
createtime: sql`now()`,
};
// ! 返回写入的数据
return this.mysqlService.db.insert(pacCoreEnv).values(newEnvData);
}
// DB 更新父级子元素数量
private haveChildrenSelfIncreasing(envId: string, isAdd = true) {
return this.mysqlService.db
.update(pacCoreEnv)
.set({
haveChildren: isAdd ? sql`${pacCoreEnv.haveChildren} + 1` : sql`${pacCoreEnv.haveChildren} - 1`,
})
.where(eq(pacCoreEnv.envId as any, envId));
}
// DB 查Env项的查询函数QueryBuilder,作用于列表和分页查询
private getEnv(data, selectData = undefined) {
this.logger.info(JSON.stringify(data, null, 2));
// ! 定义基础查询函数
// 启用动态查询模式 $dynamic
let query = this.mysqlService.db
.select(selectData)
.from(pacCoreEnv)
.where(isNull(pacCoreEnv.deleteby))
.leftJoin(pacCoreDict, and(eq(pacCoreDict.dictId, pacCoreEnv.envVal), eq(pacCoreEnv.valIsDict, 1)))
.orderBy(
isTrueEnum(data.isAsc) ? asc(pacCoreEnv.orderNum) : desc(pacCoreEnv.orderNum),
isTrueEnum(data.isAsc) ? asc(pacCoreEnv.envId) : desc(pacCoreEnv.envId),
)
.where(
or(
like(pacCoreEnv.envKey, likeQuery(data.envInfo)),
like(pacCoreEnv.envName, likeQuery(data.envInfo)),
like(pacCoreEnv.envDesc, likeQuery(data.envInfo)),
like(pacCoreEnv.envVal, likeQuery(data.envInfo)),
like(pacCoreDict.dictKey, likeQuery(data.envInfo)),
like(pacCoreDict.dictName, likeQuery(data.envInfo)),
).if(isExistKey(data, 'envInfo')),
)
.$dynamic();
// ? 模糊查询
// if (isExistKey(data, 'envInfo')) {
// query = query.where(
// or(
// like(pacCoreEnv.envKey, likeQuery(data.envInfo)),
// like(pacCoreEnv.envName, likeQuery(data.envInfo)),
// like(pacCoreEnv.envDesc, likeQuery(data.envInfo)),
// like(pacCoreEnv.envVal, likeQuery(data.envInfo)),
// like(pacCoreDict.dictKey, likeQuery(data.envInfo)),
// like(pacCoreDict.dictName, likeQuery(data.envInfo)),
// ),
// );
// }
// ? 来源于字典
// if (isTrueEnum(data.valIsDict)) {
// }
// ? 是否查pac的数据
if (isExistKey(data, 'ownOfPac') && isTrueEnum(data['ownOfPac'])) {
query = query.where(eq(pacCoreEnv.ownOfPac, isTrueEnum(data['ownOfPac']) ? 0 : 1));
}
// ? 按照层级查
if (isExistKey(data, 'hierarchy')) {
query = query.where(eq(pacCoreEnv.pid, data.hierarchy));
}
// ? 是否存在目标service
if (isExistKey(data, 'serviceOf')) {
query = query.where(eq(pacCoreEnv.serviceOf, data.serviceOf));
}
// ? 是否查环境变量状态
if (isExistKey(data, 'status')) {
query = query.where(eq(pacCoreEnv.status, data.status));
}
return query;
}
// DB 查Env列表
private getEnvList(getCoreEnvDTO: GetCoreEnvDTO) {
return this.getEnv(getCoreEnvDTO, this.envListDataType);
}
// DB 查Env分页
private async getEnvPage(getCoreEnvDTO: GetCoreEnvDTO) {
const offset = (getCoreEnvDTO.pageNumber - 1) * getCoreEnvDTO.pageSize;
// 使用基础查询构建查询总记录数
const totalCountQuery = this.getEnv(getCoreEnvDTO, {
totalCount: sql`COUNT(*)`,
});
// 使用基础查询构建分页查询
const paginatedQuery = this.getEnv(getCoreEnvDTO, this.envMoreDataType)
.leftJoin(pacAuthUser, eq(pacCoreEnv.createby, pacAuthUser.userId))
.leftJoin(pacCoreService, eq(pacCoreEnv.serviceOf, pacCoreService.serviceKey))
.limit(getCoreEnvDTO.pageSize)
.offset(offset);
return {
total: (await totalCountQuery)[0].totalCount,
rowData: await paginatedQuery,
searchData: getCoreEnvDTO,
};
}
// DB 通过ID查找Env详细信息
private getMore(envId: string) {
return this.mysqlService.db
.select(this.envMoreDataType)
.from(pacCoreEnv)
.leftJoin(pacAuthUser, eq(pacCoreEnv.createby, pacAuthUser.userId))
.leftJoin(pacCoreService, eq(pacCoreEnv.serviceOf, pacCoreService.serviceKey))
.leftJoin(pacCoreDict, and(eq(pacCoreDict.dictId, pacCoreEnv.envVal), eq(pacCoreEnv.valIsDict, 1)))
.where(and(eq(pacCoreEnv.envId, envId), isNull(pacCoreEnv.deleteby)));
}
// DB 通过Key查找目标Env的信息?树结构
private getTargetDictList(coreEnvKeyDTO: CoreEnvKeyDTO, coreEnvTargetListDTO: CoreEnvTargetListDTO) {
if (isExistKey(coreEnvTargetListDTO, 'isTree') && isTrueEnum(coreEnvTargetListDTO['isTree'])) {
// ! 基础层级
const baseQueryBuilder = new QueryBuilder();
const baseQuery = baseQueryBuilder
.select({
...this.envListDataType,
level: sql`0`.as('level'),
})
.from(pacCoreEnv)
.leftJoin(pacCoreDict, and(eq(pacCoreDict.dictId, pacCoreEnv.envVal), eq(pacCoreEnv.valIsDict, 1)))
.where(and(isNull(pacCoreEnv.deleteby), eq(pacCoreEnv.envKey, coreEnvKeyDTO.envKey)));
// ! 递归层级
const recursiveQueryBuilder = new QueryBuilder();
const recursiveQuery = recursiveQueryBuilder
.select({
...this.envListDataType,
level: sql`envHierarchy.level + 1`.as('level'),
})
.from(pacCoreEnv)
.leftJoin(pacCoreDict, and(eq(pacCoreDict.dictId, pacCoreEnv.envVal), eq(pacCoreEnv.valIsDict, 1)))
.where(isNull(pacCoreEnv.deleteby))
.innerJoin(sql`envHierarchy`, sql`envHierarchy.envId = ${pacCoreEnv.pid}`);
const rowName = customDrizzleRowWithRecursive(this.envListDataType);
// ! 定义SQL
const SQL = sql`WITH RECURSIVE envHierarchy (${rowName}) AS(${baseQuery} UNION ALL ${recursiveQuery}) SELECT * FROM envHierarchy`;
// ! 执行原始SQL查询
return this.mysqlService.db.execute(SQL);
} else {
// ! 查询自己
return this.mysqlService.db
.select(this.envMoreDataType)
.from(pacCoreEnv)
.leftJoin(pacAuthUser, eq(pacCoreEnv.createby, pacAuthUser.userId))
.leftJoin(pacCoreService, eq(pacCoreEnv.serviceOf, pacCoreService.serviceKey))
.leftJoin(pacCoreDict, and(eq(pacCoreDict.dictId, pacCoreEnv.envVal), eq(pacCoreEnv.valIsDict, 1)))
.where(and(eq(pacCoreEnv.envKey, coreEnvKeyDTO.envKey), isNull(pacCoreEnv.deleteby)));
}
}
// DB 通过ID删除Env
private deleteEnvItem(coreEnvIdDTO: CoreEnvIdDTO, pacInfo: PacInfoType) {
return this.mysqlService.db
.update(pacCoreEnv)
.set({
deletetime: sql`now()`,
deleteby: pacInfo.userId,
})
.where(eq(pacCoreEnv.envId, coreEnvIdDTO.envId));
}
// DB 更新Env
private updateEnvData(coreEnvIdDTO: CoreEnvIdDTO, updateCoreEnvDto: UpdateCoreEnvDto, pacInfo: PacInfoType) {
// ? 判断身份是否是pac,如果是pac才能写入
const isPac = this.config.get<number>('masterId') == pacInfo.userId && isTrueEnum(updateCoreEnvDto.ownOfPac);
return this.mysqlService.db
.update(pacCoreEnv)
.set({
envName: updateCoreEnvDto.envName,
envKey: updateCoreEnvDto.envKey,
envVal: updateCoreEnvDto.envVal,
valIsDict: isTrueEnum(updateCoreEnvDto.valIsDict) ? 1 : 0,
envDesc: updateCoreEnvDto.envDesc,
ownOfPac: isPac ? 0 : 1,
orderNum: updateCoreEnvDto.orderNum,
status: updateCoreEnvDto.status,
serviceOf: updateCoreEnvDto.serviceOf,
updateby: pacInfo.userId,
updatetime: sql`now()`,
})
.where(eq(pacCoreEnv.envId, coreEnvIdDTO.envId));
}
}

@ -0,0 +1,133 @@
import { BooleanEnum } from '@utils/boolean.enum';
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import { IsEnum, IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';
import ChangeCase, { CaseType } from '@common/decorator/change-case/change-case.decorator';
export class CreateCoreEnvDto {
@ApiProperty({
description: '环境变量父ID',
type: String,
example: '0',
required: true,
minLength: 1,
maxLength: 20,
})
@Trim()
@IsString({ message: '环境变量关联属性应为字符串格式!' })
@IsOptional()
public readonly pid?: string = '0';
@ApiProperty({
description: '环境变量名称',
type: String,
example: '主题',
required: true,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsString({ message: '环境变量名称应为字符串格式!' })
@Length(1, 32, { message: '请将环境变量名称长度控制在1到32位之间!' })
public readonly envName: string;
@ApiProperty({
description: '环境变量标志',
type: String,
example: 'THEME',
required: true,
minLength: 1,
maxLength: 32,
})
@ChangeCase(CaseType.constant)
@Trim()
@IsString({ message: '环境变量标志应为字符串格式!' })
@Length(1, 32, { message: '请将环境变量标志长度控制在1到32位之间!' })
public readonly envKey: string;
@ApiProperty({
description: '环境变量值',
type: String,
example: '#33333333a',
required: false,
minLength: 1,
maxLength: 255,
})
@Trim()
@IsString({ message: '环境变量值应为字符串格式!' })
@Length(1, 255, { message: '请将环境变量值长度控制在1到255位之间!' })
@IsOptional()
public readonly envVal: string;
@ApiProperty({
description: '是否来源于字典',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'envIsDict参数格式错误' })
@IsOptional()
public readonly valIsDict?: BooleanEnum.FALSE;
@ApiProperty({
description: '环境变量描述信息',
type: String,
example: '这里是web短的颜色主题',
required: false,
minLength: 1,
maxLength: 255,
})
@Trim()
@IsString({ message: '环境变量描述信息应为字符串格式!' })
@Length(1, 255, { message: '请将环境变量描述信息长度控制在1到255位之间!' })
@IsOptional()
public readonly envDesc?: string;
@ApiProperty({
description: '是否属于PAC',
type: BooleanEnum,
enum: BooleanEnum,
example: false,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'envIsDict参数格式错误' })
@IsOptional()
public readonly ownOfPac: BooleanEnum = BooleanEnum.FALSE;
@ApiProperty({
description: '排序',
type: Number,
example: 10,
required: false,
minimum: -1000,
maximum: 1000,
})
@IsOptional()
@IsInt({
message: '排序必须是整数!',
})
@Min(-1000, {
message: '排序不能小于-1000!',
})
@Max(1000, {
message: '排序不能超过1000',
})
public readonly orderNum?: number = 0;
@ApiProperty({
description: '服务的唯一标识',
type: String,
example: 'CORE-PAC-AUTH',
required: false,
minLength: 2,
maxLength: 16,
})
@Trim()
@IsString({ message: '服务标识应为字符串格式!' })
@Length(2, 16, { message: '请将服务标识长度控制在2到16位之间!' })
@IsOptional()
public readonly serviceOf: string;
}

@ -0,0 +1,142 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: get-core-env.dto.ts -
// | @创建时间: 2024-06-05 11:41
// | @更新时间: 2024-06-05 11:41
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { GetDto } from '@dto/get.dto';
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import { IsEnum, IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';
import { BooleanEnum } from '@utils/boolean.enum';
import ChangeCase, { CaseType } from '@common/decorator/change-case/change-case.decorator';
export class GetCoreEnvDTO extends GetDto {
@ApiProperty({
description: '环境变量信息',
type: String,
example: '主题',
required: false,
minLength: 1,
maxLength: 128,
})
@Trim()
@IsString({ message: '环境变量信息应为字符串格式!' })
@Length(0, 128, { message: '请将环境变量信息长度控制在1到128位之间!' })
@IsOptional()
readonly envInfo?: string;
@ApiProperty({
description: '是否属于PAC',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'ownOfPac参数格式错误' })
@IsOptional()
readonly ownOfPac: BooleanEnum;
@ApiProperty({
description: '服务的唯一标识',
type: String,
example: 'CORE-PAC-AUTH',
required: false,
minLength: 2,
maxLength: 16,
})
@Trim()
@IsString({ message: '服务标识应为字符串格式!' })
@Length(2, 16, { message: '请将服务标识长度控制在2到16位之间!' })
@IsOptional()
readonly serviceOf: string;
@ApiProperty({
description: '环境变量状态',
type: Number,
example: 0,
required: false,
minimum: -100,
maximum: 100,
})
@Trim()
@IsInt({
message: '环境变量状态必须是整数!',
})
@Min(-100, {
message: '环境变量状态需要大于-100!',
})
@Max(100, {
message: '环境变量状态不能超过100',
})
@IsOptional()
readonly status?: number;
@ApiProperty({
description: '环境变量层级id',
type: Number,
example: 0,
required: false,
minimum: 0,
maximum: 100,
})
@Trim()
@IsString({ message: '环境变量层级id应为字符串格式!' })
@Length(1, 20, { message: '环境变量层级id格式错误!' })
@IsOptional()
readonly hierarchy?: string;
}
export class CoreEnvIdDTO {
@ApiProperty({
description: '环境变量ID',
type: String,
example: '1123817263136',
required: true,
minLength: 19,
maxLength: 19,
})
@IsString()
@Length(19, 19, { message: '环境变量ID格式错误!' })
readonly envId: string;
}
export class CoreEnvKeyDTO {
@ApiProperty({
description: '环境变量标志',
type: String,
example: 'Country',
required: true,
minLength: 1,
maxLength: 32,
})
@Trim()
@ChangeCase(CaseType.constant)
@IsString({ message: '环境变量标志应为字符串格式!' })
@Length(1, 32, { message: '请将环境变量标志长度控制在1到32位之间!' })
readonly envKey: string;
}
export class CoreEnvTargetListDTO {
@ApiProperty({
description: '是否查找树结构',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, {
message: '查找数结构数据错误',
})
@IsOptional()
readonly isTree: BooleanEnum;
}

@ -0,0 +1,25 @@
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { CreateCoreEnvDto } from './create-core-env.dto';
import { IsInt, IsOptional, Max, Min } from 'class-validator';
export class UpdateCoreEnvDto extends PartialType(CreateCoreEnvDto) {
@ApiProperty({
description: '状态',
type: Number,
example: 10,
required: false,
minimum: -100,
maximum: 100,
})
@IsOptional()
@IsInt({
message: '状态必须是整数!',
})
@Min(-1000, {
message: '状态不能小于-100!',
})
@Max(1000, {
message: '状态不能超过100',
})
readonly status: number;
}

@ -0,0 +1 @@
export class CoreEnv {}

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CoreServiceController } from './core-service.controller';
import { CoreServiceService } from './core-service.service';
describe('CoreServiceController', () => {
let controller: CoreServiceController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CoreServiceController],
providers: [CoreServiceService],
}).compile();
controller = module.get<CoreServiceController>(CoreServiceController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

@ -0,0 +1,65 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { CoreServiceService } from './core-service.service';
import { CreateCoreServiceDto } from './dto/create-core-service.dto';
import { UpdateCoreServiceDto } from './dto/update-core-service.dto';
import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger';
import { PacInfo } from '@common/decorator/pac-info/pac-info.decorator';
import { PacInfoType } from '@utils/myType';
import { GetCoreServiceDto } from './dto/get-core-service.dto';
import { ServiceKeyDto } from '@app/core-service/dto/serviceKey.dto';
@ApiTags('系统服务')
@Controller('coreservice')
export class CoreServiceController {
constructor(private readonly coreServiceService: CoreServiceService) {}
@ApiOperation({
summary: '添加服务',
description: '增加新服务',
})
@ApiProduces('application/json')
@Post()
create(@Body() createCoreServiceDto: CreateCoreServiceDto, @PacInfo() pacInfo: PacInfoType): Promise<object> {
return this.coreServiceService.create(createCoreServiceDto, pacInfo);
}
@ApiOperation({
summary: '服务列表',
description: '获取服务分页/列表',
})
@ApiProduces('application/json')
@Get()
findAll(@Query() getCoreServiceDto: GetCoreServiceDto): Promise<object> {
return this.coreServiceService.findAll(getCoreServiceDto);
}
// @ApiOperation({
// summary: '服务详情',
// description: '查找指定服务的详细信息',
// })
// @ApiProduces('application/json')
// @Get(':id')
// findOne(@Param('id') id: string) {
// return this.coreServiceService.findOne(+id);
// }
@ApiOperation({
summary: '更新服务',
description: '更新指定服务的信息',
})
@ApiProduces('application/json')
@Patch(':serviceKey')
update(@Param() serviceKey: ServiceKeyDto, @Body() updateCoreServiceDto: UpdateCoreServiceDto, @PacInfo() pacInfo: PacInfoType) {
return this.coreServiceService.update(serviceKey, updateCoreServiceDto, pacInfo);
}
@ApiOperation({
summary: '删除服务',
description: '删除指定服务',
})
@ApiProduces('application/json')
@Delete(':serviceKey')
remove(@Param() serviceKey: ServiceKeyDto, @PacInfo() pacInfo: PacInfoType) {
return this.coreServiceService.remove(serviceKey, pacInfo);
}
}

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { CoreServiceService } from './core-service.service';
import { CoreServiceController } from './core-service.controller';
@Module({
controllers: [CoreServiceController],
providers: [CoreServiceService],
})
export class CoreServiceModule {}

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CoreServiceService } from './core-service.service';
describe('CoreServiceService', () => {
let service: CoreServiceService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CoreServiceService],
}).compile();
service = module.get<CoreServiceService>(CoreServiceService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

@ -0,0 +1,234 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateCoreServiceDto } from './dto/create-core-service.dto';
import { UpdateCoreServiceDto } from './dto/update-core-service.dto';
import { LoggerService } from '@service/logger/logger.service';
import { MysqlService } from '@common/service/mysql/mysql.service';
import { pacAuthUser, pacCoreService } from '@entities/schema';
import { asc, desc, eq, isNull, like, or, sql } from 'drizzle-orm';
import { PacInfoType } from '@utils/myType';
import { GetCoreServiceDto } from '@app/core-service/dto/get-core-service.dto';
import { likeQuery } from '@utils/likeQuery';
import { ServiceKeyDto } from '@app/core-service/dto/serviceKey.dto';
import { RedisService } from '@common/service/redis/redis.service';
import { isTrueEnum } from '@utils/boolean.enum';
@Injectable()
export class CoreServiceService {
constructor(
private readonly logger: LoggerService,
private readonly mysqlService: MysqlService,
private readonly redisService: RedisService,
) {}
/** Service
* NAME: create
* DESC: 创建服务
* DATE: 2024-05-30 21:06:24 -
* */
async create(createCoreServiceDto: CreateCoreServiceDto, pacInfo: PacInfoType) {
// ! 上锁
const lock = await this.redisService.distributedLock('SERVICE' + createCoreServiceDto.serviceKey, createCoreServiceDto.serviceKey);
// ? 存在正在进行写入的服务
if (!lock) throw new HttpException('服务繁忙,服务标识重复!', HttpStatus.CONFLICT);
// @ 核心业务
try {
// ! 查重
const result = await this.getServiceDataForServiceKey(createCoreServiceDto.serviceKey);
// ? 存在重复
if (result.length > 0) throw new HttpException('服务标识重复!', HttpStatus.CONFLICT);
// ! 添加服务数据
const newPacCoreService = await this.addServiceData(createCoreServiceDto, pacInfo);
// ! 解锁
lock();
// ! 返回结果
return newPacCoreService;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
}
/** Service
* NAME: findAll
* DESC: 查找服务分页/
* DATE: 2024-05-30 21:07:40 -
* */
async findAll(getCoreServiceDto: GetCoreServiceDto) {
// ? 是否查询列表
if (isTrueEnum(getCoreServiceDto.isList)) {
// ! 返回列表数据
return await this.getServiceList(getCoreServiceDto);
} else {
// ! 返回分页数据
return await this.getServicePage(getCoreServiceDto);
}
}
/** Service
* NAME: update
* DESC: 更新数据
* DATE: 2024-05-30 21:20:19 -
* */
async update(serviceKey: ServiceKeyDto, updateCoreServiceDto: UpdateCoreServiceDto, pacInfo: PacInfoType) {
// ! 上锁
const lock = await this.redisService.distributedLock('SERVICE' + updateCoreServiceDto.serviceKey, updateCoreServiceDto.serviceKey);
// ? 存在正在运行的服务
if (!lock) throw new HttpException('服务繁忙,服务标识重复!', HttpStatus.CONFLICT);
// @ 核心业务
try {
if (serviceKey.serviceKey != updateCoreServiceDto.serviceKey) {
// ! 查重
const result = await this.getServiceDataForServiceKey(updateCoreServiceDto.serviceKey);
if (result.length > 0) {
throw new HttpException('服务标识重复!', HttpStatus.CONFLICT);
}
}
// ! 更新数据
const result = await this.updateServiceItem(serviceKey, updateCoreServiceDto, pacInfo);
// ! 解锁
lock();
// ! 返回数据
return result;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
}
/** Service
* NAME: remove
* DESC: 删除数据
* DATE: 2024-05-30 21:24:33 -
* */
async remove(serviceKey: ServiceKeyDto, pacInfo: PacInfoType) {
// ! 执行删除并返回
return await this.deleteServiceItem(serviceKey, pacInfo);
}
// DB 根据serviceKey查找服务数据 可用于查重
async getServiceDataForServiceKey(serviceKey: string) {
return await this.mysqlService.db.select().from(pacCoreService).where(eq(pacCoreService.serviceKey, serviceKey));
}
// DB 添加服务数据
async addServiceData(createCoreServiceDto: CreateCoreServiceDto, pacInfo: PacInfoType) {
const newServiceData: typeof pacCoreService.$inferInsert = {
createby: pacInfo.userId,
createtime: sql`now()` as any,
serviceKey: createCoreServiceDto.serviceKey,
serviceName: createCoreServiceDto.serviceName,
serviceVersion: createCoreServiceDto.serviceVersion,
serviceDesc: createCoreServiceDto.serviceDesc,
};
return await this.mysqlService.db.insert(pacCoreService).values(newServiceData);
}
// DB 获取服务分页
async getServicePage(getCoreServiceDto: GetCoreServiceDto) {
const offset = (getCoreServiceDto.pageNumber - 1) * getCoreServiceDto.pageSize;
// 定义基础查询函数
const buildBaseQuery = (selectData = undefined) => {
// 启用动态查询模式 $dynamic
let query = this.mysqlService.db.select(selectData).from(pacCoreService).where(isNull(pacCoreService.deleteby)).$dynamic();
// 根据条件添加额外的WHERE子句
if (getCoreServiceDto.serviceInfo) {
query = query.where(
or(
like(pacCoreService.serviceKey, likeQuery(getCoreServiceDto.serviceInfo)),
like(pacCoreService.serviceName, likeQuery(getCoreServiceDto.serviceInfo)),
like(pacCoreService.serviceDesc, likeQuery(getCoreServiceDto.serviceInfo)),
),
);
}
return query;
};
// 使用基础查询构建查询总记录数
const totalCountQuery = buildBaseQuery({
totalCount: sql`COUNT(*)`,
});
// 使用基础查询构建分页查询
const paginatedQuery = buildBaseQuery({
serviceKey: pacCoreService.serviceKey,
serviceName: pacCoreService.serviceName,
serviceVersion: pacCoreService.serviceVersion,
serviceDesc: pacCoreService.serviceDesc,
createby: pacCoreService.createby,
createName: pacAuthUser.nickname,
createtime: pacCoreService.createtime,
})
.leftJoin(pacAuthUser, eq(pacCoreService.createby, pacAuthUser.userId))
.orderBy(isTrueEnum(getCoreServiceDto.isAsc) ? asc(pacCoreService.createtime) : desc(pacCoreService.createtime))
.limit(getCoreServiceDto.pageSize)
.offset(offset);
return {
total: (await totalCountQuery)[0].totalCount,
rowData: await paginatedQuery,
searchData: getCoreServiceDto,
};
}
// DB 获取服务列表
async getServiceList(getCoreServiceDto: GetCoreServiceDto) {
return await this.mysqlService.db
.select({
serviceKey: pacCoreService.serviceKey,
serviceName: pacCoreService.serviceName,
})
.from(pacCoreService)
.orderBy(isTrueEnum(getCoreServiceDto.isAsc) ? asc(pacCoreService.createtime) : desc(pacCoreService.createtime))
.where(isNull(pacCoreService.deleteby));
}
// DB 删除服务
async deleteServiceItem(serviceKey: ServiceKeyDto, pacInfo: PacInfoType) {
const deleteData = {
serviceKey: serviceKey.serviceKey,
userId: pacInfo.userId,
};
return await this.mysqlService.db
.update(pacCoreService)
.set({
deletetime: sql`now()`,
deleteby: deleteData.userId,
})
.where(eq(pacCoreService.serviceKey, deleteData.serviceKey));
}
// DB 更新服务
updateServiceItem(serviceKey: ServiceKeyDto, updateCoreServiceDto: UpdateCoreServiceDto, pacInfo: PacInfoType) {
return this.mysqlService.db
.update(pacCoreService)
.set({
serviceKey: updateCoreServiceDto.serviceKey,
serviceName: updateCoreServiceDto.serviceName,
serviceVersion: updateCoreServiceDto.serviceVersion,
serviceDesc: updateCoreServiceDto.serviceDesc,
updatetime: sql`now()`,
updateby: pacInfo.userId,
})
.where(eq(pacCoreService.serviceKey, serviceKey.serviceKey));
}
}

@ -0,0 +1,62 @@
import { IsOptional, IsString, Length } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
export class CreateCoreServiceDto {
@ApiProperty({
description: '服务的唯一标识',
type: String,
example: 'CORE-PAC-AUTH',
required: true,
// format: 'email', // email, date-time, uuid
// minimum: 1, // 数字属性的最小值
minLength: 2,
maxLength: 16,
// minItems: 1, // 数组属性的最小项目数
})
@Trim()
@IsString({ message: '服务标识应为字符串格式!' })
@Length(2, 16, { message: '请将服务标识长度控制在2到16位之间!' })
readonly serviceKey: string;
@ApiProperty({
description: '服务名称',
type: String,
example: 'PAC核心用户鉴权服务',
required: true,
minLength: 2,
maxLength: 32,
})
@Trim()
@IsString({ message: '服务名称应为字符串格式!' })
@Length(2, 32, { message: '请将服务名称长度控制在2到32位之间!' })
readonly serviceName: string;
@ApiProperty({
description: '服务版本',
type: String,
example: '2.1',
required: false,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsOptional()
@Length(1, 32, { message: '请将服务版本长度控制在1到32位之间!' })
readonly serviceVersion: string;
@ApiProperty({
description: '服务描述',
type: String,
example: '所有系统的前驱,管理用户',
required: false,
minLength: 1,
maxLength: 255,
})
@Trim()
@IsOptional()
@Length(1, 255, { message: '请将服务描述长度控制在1到255位之间!' })
readonly serviceDesc: string;
}

@ -0,0 +1,33 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: get-core-service.dto.ts -
// | @创建时间: 2024-05-28 18:43
// | @更新时间: 2024-05-28 18:43
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { GetDto } from '@/dto/get.dto';
import { IsOptional, IsString, Length } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
export class GetCoreServiceDto extends GetDto {
@ApiProperty({
description: '服务信息',
type: String,
example: 'PAC核心用户鉴权服务',
required: false,
minLength: 1,
maxLength: 128,
})
@Trim()
@IsOptional()
@IsString({ message: '服务信息应为字符串格式!' })
@Length(1, 128, { message: '请将服务信息长度控制在1到128位之间!' })
readonly serviceInfo?: string;
}

@ -0,0 +1,36 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: serviceKey.dto.ts -
// | @创建时间: 2024-05-30 15:14
// | @更新时间: 2024-05-30 15:14
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { ApiProperty } from '@nestjs/swagger';
import { IsString, Length } from 'class-validator';
import Trim from '@common/decorator/trim/trim.decorator';
export class ServiceKeyDto {
@ApiProperty({
description: '服务的唯一标识',
type: String,
example: 'CORE-PAC-AUTH',
required: true,
// format: 'email', // email, date-time, uuid
// minimum: 1, // 数字属性的最小值
minLength: 2,
maxLength: 16,
// minItems: 1, // 数组属性的最小项目数
})
@Trim()
@IsString({ message: '服务标识应为字符串格式!' })
@Length(2, 16, { message: '请将服务标识长度控制在2到16位之间!' })
readonly serviceKey: string;
}

@ -0,0 +1,5 @@
import { PartialType } from '@nestjs/swagger';
import { CreateCoreServiceDto } from './create-core-service.dto';
// 使用PartialType来创建一个所有属性都是可选的DTO
export class UpdateCoreServiceDto extends PartialType(CreateCoreServiceDto) {}

@ -0,0 +1,26 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: global.module.ts -
// | @创建时间: 2024-05-31 20:42
// | @更新时间: 2024-05-31 20:42
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { Global, Module } from '@nestjs/common';
import { LoggerService } from '@service/logger/logger.service';
import { RedisService } from '@common/service/redis/redis.service';
import { MysqlService } from '@common/service/mysql/mysql.service';
import { Snowflake } from '@service/snowflake/snowflake.service';
@Global()
@Module({
providers: [LoggerService, RedisService, MysqlService, Snowflake],
exports: [LoggerService, RedisService, MysqlService, Snowflake],
imports: [],
})
export class GlobalModule {}

@ -0,0 +1 @@
export class CreateTestDto {}

@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateTestDto } from './create-test.dto';
export class UpdateTestDto extends PartialType(CreateTestDto) {}

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { TestController } from './test.controller';
import { TestService } from './test.service';
describe('TestController', () => {
let controller: TestController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [TestController],
providers: [TestService],
}).compile();
controller = module.get<TestController>(TestController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

@ -0,0 +1,42 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { TestService } from './test.service';
import { CreateTestDto } from './dto/create-test.dto';
import { UpdateTestDto } from './dto/update-test.dto';
import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger';
@ApiTags('Test测试')
@Controller('test')
export class TestController {
constructor(private readonly testService: TestService) {}
@ApiOperation({
summary: 'Post操作',
description: '这个操作的详细描述',
})
@ApiTags('Test测试AAA')
@ApiProduces('application/json')
@Post()
create(@Body() createTestDto: CreateTestDto) {
return this.testService.create(createTestDto);
}
@Get()
findAll() {
return this.testService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.testService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateTestDto: UpdateTestDto) {
return this.testService.update(+id, updateTestDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.testService.remove(+id);
}
}

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { TestService } from './test.service';
import { TestController } from './test.controller';
@Module({
controllers: [TestController],
providers: [TestService],
})
export class TestModule {}

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { TestService } from './test.service';
describe('TestService', () => {
let service: TestService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [TestService],
}).compile();
service = module.get<TestService>(TestService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { CreateTestDto } from './dto/create-test.dto';
import { UpdateTestDto } from './dto/update-test.dto';
@Injectable()
export class TestService {
create(createTestDto: CreateTestDto) {
return 'This action adds a new test';
}
findAll() {
return 'This action returns all test';
}
findOne(id: number) {
return `This action returns a #${id} test`;
}
update(id: number, updateTestDto: UpdateTestDto) {
return `This action updates a #${id} test`;
}
remove(id: number) {
return `This action removes a #${id} test`;
}
}

@ -0,0 +1,109 @@
// import { SetMetadata } from '@nestjs/common';
//
// export const ChangeCase = (...args: string[]) => SetMetadata('change-case', args);
import { Transform } from 'class-transformer';
import { Case } from 'change-case-all';
// camel = Case.camel(str); // testString
// capital = Case.capital(str); // Test String
// constant = Case.constant(str); // TEST_STRING
// dot = Case.dot(str); // test.string
// no = Case.no(str); // test string
// pascal = Case.pascal(str); // TestString
// path = Case.path(str); // test/string
// sentence = Case.sentence(str); // Test string
// snake = Case.snake(str); // test_string
// train = Case.train(str); // Test-String
// kebap = Case.kebap(str); // test-string
// sponge = Case.sponge(str); // TeSt StRiNg
// swapCase = Case.swap(str); // TEST STRING
// title = Case.title(str); // Test String
// uppper = Case.upper(str); // TEST STRING
// localeUpper = Case.localeUpper(str, 'en'); // TEST STRING
// lower = Case.lower(str); // test string
// localeLower = Case.localeLower(str, 'en'); // test string
// lowerFirst = Case.lowerFirst(str); // test string
// upperFirst = Case.upperFirst(str); // Test string
// isUpper = Case.isUpper(str); // false
// isLower = Case.isLower(str); // true
export enum CaseType {
// camel = Case.camel(str); // testString
camel = 'camel',
// capital = Case.capital(str); // Test String
capital = 'capital',
// constant = Case.constant(str); // TEST_STRING
constant = 'constant', // xs
// dot = Case.dot(str); // test.string
dot = 'dot',
// no = Case.no(str); // test string
no = 'no',
// pascal = Case.pascal(str); // TestString
pascal = 'pascal',
// path = Case.path(str); // test/string
path = 'path',
// sentence = Case.sentence(str); // Test string
sentence = 'sentence',
// snake = Case.snake(str); // test_string
snake = 'snake',
// train = Case.train(str); // Test-String
train = 'train',
// kebap = Case.kebap(str); // test-string
kebap = 'kebap',
// sponge = Case.sponge(str); // TeSt StRiNg
sponge = 'sponge',
// swapCase = Case.swap(str); // TEST STRING
swapCase = 'swapCase',
// title = Case.title(str); // Test String
title = 'title',
// uppper = Case.upper(str); // TEST STRING
uppper = 'uppper',
// localeUpper = Case.localeUpper(str, 'en'); // TEST STRING
localeUpper = 'localeUpper',
// lower = Case.lower(str); // test string
lower = 'lower',
// localeLower = Case.localeLower(str, 'en'); // test string
localeLower = 'localeLower',
// lowerFirst = Case.lowerFirst(str); // test string
lowerFirst = 'lowerFirst',
// upperFirst = Case.upperFirst(str); // Test string
upperFirst = 'upperFirst',
// isUpper = Case.isUpper(str); // false
isUpper = 'isUpper',
// isLower = Case.isLower(str); // true
isLower = 'isLower',
}
export default function ChangeCase(caseName: CaseType) {
return Transform(({ value }) => {
if (typeof value === 'string') {
if (value.trim() == '') {
return undefined;
}
return Case[caseName](value.trim());
}
return value;
});
}

@ -0,0 +1,15 @@
import { createParamDecorator, ExecutionContext, SetMetadata } from '@nestjs/common';
import { PacInfoType } from '@utils/myType';
// export const PacInfo = (...args: string[]) => SetMetadata('pac-info', args);
// PacInfo
export const PacInfo = createParamDecorator((data: string, ctx: ExecutionContext) => {
const info: PacInfoType = {
username: 'pac',
userId: 0,
};
return info;
// const req = ctx.switchToHttp().getRequest();
});

@ -0,0 +1,13 @@
import { Transform } from 'class-transformer';
export default function Trim() {
return Transform(({ value }) => {
if (typeof value === 'string') {
if (value.trim() == '') {
return undefined;
}
return value.trim();
}
return value;
});
}

@ -0,0 +1,7 @@
import { HttpExceptFilter } from './http-except.filter';
describe('HttpExceptFilter', () => {
it('should be defined', () => {
expect(new HttpExceptFilter()).toBeDefined();
});
});

@ -0,0 +1,48 @@
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common';
import { LoggerService } from '@service/logger/logger.service';
import { FastifyRequest, FastifyReply } from 'fastify';
import { randomHEX } from '@utils/random';
@Catch()
export class HttpExceptFilter implements ExceptionFilter {
constructor(private readonly logger: LoggerService) {}
catch(exception: HttpException, host: ArgumentsHost) {
console.log(exception);
const ctx = host.switchToHttp();
const request = ctx.getRequest<FastifyRequest>();
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
let errData;
if (exception?.getResponse) {
errData = exception.getResponse();
if (!errData.statusCode || !errData.timestamp) {
errData = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: errData?.message || errData,
};
}
this.logger.warn(JSON.stringify(errData, null, 4));
} else {
let message = '';
const errCode = randomHEX();
if (Object.keys(exception).includes('sql')) {
message = `数据库写入故障,请联系管理员! 错误码: ${errCode}`;
} else {
message = `服务发现未知故障,请联系管理员! 错误码: ${errCode}`;
}
const logData = JSON.stringify(exception, null, 4);
this.logger.error('ERRCODE: ' + errCode + ' ' + logData);
errData = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
};
}
errData.data = null;
const response = ctx.getResponse<FastifyReply>();
response.send(errData);
}
}

@ -0,0 +1,7 @@
import { FormatResponseInterceptor } from './format-response.interceptor';
describe('FormatResponseInterceptor', () => {
it('should be defined', () => {
expect(new FormatResponseInterceptor()).toBeDefined();
});
});

@ -0,0 +1,15 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { map, Observable } from 'rxjs';
@Injectable()
export class FormatResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => ({
statusCode: context.switchToHttp().getResponse().statusCode,
message: 'Successful',
data,
})),
);
}
}

@ -0,0 +1,7 @@
import { LogRequestInfoInterceptor } from './log-request-info.interceptor';
describe('LogRequestInfoInterceptor', () => {
it('should be defined', () => {
expect(new LogRequestInfoInterceptor()).toBeDefined();
});
});

@ -0,0 +1,58 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { catchError, Observable, tap, throwError } from 'rxjs';
import { FastifyReply, FastifyRequest } from 'fastify';
import { LoggerService } from '@service/logger/logger.service';
import formatBytes from '@utils/formatBytes';
import * as colors from 'colors';
import mathodColor from '@utils/mathodColor';
@Injectable()
export class LogRequestInfoInterceptor implements NestInterceptor {
constructor(private readonly logger: LoggerService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// 请求数据体
const req: FastifyRequest = context.switchToHttp().getRequest<FastifyRequest>();
// Fastify 响应对象
const reply: FastifyReply = context.switchToHttp().getResponse<FastifyReply>();
// 高精度时间戳
const start = process.hrtime();
// 计算请求大小(如果请求有body)
const requestSize = req.headers['content-length'] ? parseInt(req.headers['content-length'], 10) : 0;
this.logger.debug(`<== ${mathodColor(req.method)} ${req.url} - ${requestSize}`);
return next.handle().pipe(
tap((data) => {
// 转换为毫秒,保留三位小数
const end = process.hrtime(start);
const responseTime = ((end[0] + end[1] / 1e9) * 1000).toFixed(3);
let size: number | string = 0;
const contentType: string | any = reply.getHeader('Content-Type');
// 检查Content-Type来判断响应是否为文件
if (contentType?.includes('application/octet-stream') || contentType?.includes('multipart/form-data')) {
// 如果是文件或其他二进制流,通常无法直接获取大小,可以记录为unknown或特殊值
size = 'unknown (stream or binary data)';
} else {
try {
// 尝试将响应体转换为字符串来计算大小
const dataStr = JSON.stringify(data);
size = Buffer.byteLength(dataStr, 'utf8');
} catch (e) {
// 如果响应体不是JSON,则不计算大小或记录为特殊值
size = 'non-JSON';
}
}
// 打印日志
this.logger.debug(`=>> ${mathodColor(req.method)} ${req.url} ${formatBytes(size)} ${responseTime} ms`);
}),
catchError((err) => {
// console.error(err);
return throwError(() => err);
}),
);
}
}

@ -0,0 +1,7 @@
import { UserOperationLogInterceptor } from './user-operation-log.interceptor';
describe('UserOperationLogInterceptor', () => {
it('should be defined', () => {
expect(new UserOperationLogInterceptor()).toBeDefined();
});
});

@ -0,0 +1,54 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { FastifyRequest } from 'fastify';
import { LoggerService } from '@service/logger/logger.service';
@Injectable()
export class UserOperationLogInterceptor implements NestInterceptor {
constructor(
private readonly reflector: Reflector,
private readonly logger: LoggerService,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest<FastifyRequest>(); // & { browserInfo?: any }
const handler = context.getHandler();
const controller = context.getClass();
/**
* todo: 获取注解信息
* // 获取注解Keys 类和方法都行
* const classMetadataKeys = Reflect.getMetadataKeys(controller);
* const interfaceMetaKeys = Reflect.getMetadataKeys(handler);
* // 拿到Keys获取Value
* const controllerMetaData = classMetadataKeys.map((key) => ({
* key,
* value: Reflect.getMetadata(key, controller),
* }));
*
* */
// 获取IP地址 protocol
const clientIp = request.ip;
// 浏览器及版本
const browserInfo = request.raw['browserInfo'];
// 请求路径
const path = request.url;
// 请求方法
const method = request.method;
// 获取swagger注解接口Operation操作信息
const methodOperation = this.reflector.get<object>('swagger/apiOperation', handler);
// 获取swagger注解模块类Tags
const controllerTags = Reflect.getMetadata('swagger/apiUseTags', controller);
this.logger.debug(`${method} - ${clientIp} => ${path} ${controllerTags[0]}/${methodOperation['summary']} ${browserInfo.name}:${browserInfo.version}`);
// 执行下一个拦截器或路由处理程序
return next.handle();
}
}

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LifecycleService } from './lifecycle.service';
describe('LifecycleService', () => {
let service: LifecycleService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LifecycleService],
}).compile();
service = module.get<LifecycleService>(LifecycleService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

@ -0,0 +1,39 @@
import { BeforeApplicationShutdown, Injectable, OnApplicationBootstrap, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { LoggerService } from '@service/logger/logger.service';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class LifecycleService implements OnModuleInit, OnModuleDestroy, OnApplicationBootstrap, BeforeApplicationShutdown {
constructor(
private readonly logger: LoggerService,
private readonly config: ConfigService,
) {}
// 意义: 在模块初始化时调用。
// 用途: 可用于执行一些需要在模块加载时完成的任务,如依赖注入初始化、资源准备等。
// 实现: 需要实现OnModuleInit接口,并在onModuleInit方法中定义逻辑。
onModuleInit() {
this.logger.debug('The module has been initialized.');
}
// 意义: 在应用程序引导完成后调用。
// 用途: 可用于执行在应用程序完全启动并准备好处理请求之前需要完成的任务,如数据库连接验证、缓存预热等。
// 实现: 需要实现OnApplicationBootstrap接口,并在onApplicationBootstrap方法中定义逻辑。
onApplicationBootstrap() {
this.logger.debug('The application has bootstrapped.');
}
// 意义: 在应用程序关闭之前调用。
// 用途: 可用于执行一些清理工作,如关闭数据库连接、释放资源、保存未处理的数据等。
// 实现: 需要实现BeforeApplicationShutdown接口,并在beforeApplicationShutdown方法中定义逻辑。可以接收一个参数,该参数表示关闭信号。
beforeApplicationShutdown(signal: string) {
this.logger.debug(`The application is shutting down due to signal: ${signal}`);
}
// 意义: 在模块销毁时调用。
// 用途: 可用于执行在模块被卸载时需要完成的任务,如清理模块资源、注销事件监听等。
// 实现: 需要实现OnModuleDestroy接口,并在onModuleDestroy方法中定义逻辑。
onModuleDestroy() {
this.logger.debug('The module is being destroyed.');
}
}

@ -0,0 +1,7 @@
import { LogRequestInfoMiddleware } from './log-request-info.middleware';
describe('LogRequestInfoMiddleware', () => {
it('should be defined', () => {
expect(new LogRequestInfoMiddleware()).toBeDefined();
});
});

@ -0,0 +1,24 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { FastifyRequest, FastifyReply } from 'fastify';
@Injectable()
export class LogRequestInfoMiddleware implements NestMiddleware {
use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) {
// const startTime = process.hrtime.bigint();
// 我没辙了,这里只能处理请求,以及错误的响应
next();
// res.on('finish', (reply, payload) => {
// const status = res.statusCode;
// const method = req.method;
// const url = req.url;
// const duration = process.hrtime.bigint() - startTime;
//
// // 构建日志消息
// const logMessage = `${method} ${url} - (${duration / BigInt(1e6)}ms)`;
// console.log(logMessage);
// console.log(payload);
// console.log(JSON.stringify(payload).length);
// });
}
}

@ -0,0 +1,7 @@
import { UserAgentMiddleware } from './useragent.middleware';
describe('UseragentMiddleware', () => {
it('should be defined', () => {
expect(new UserAgentMiddleware()).toBeDefined();
});
});

@ -0,0 +1,30 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { FastifyReply, FastifyRequest } from 'fastify';
import * as UAParser from 'ua-parser-js';
@Injectable()
export class UserAgentMiddleware implements NestMiddleware {
use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) {
// 获取用户代理信息
const userAgent = req.headers['user-agent'];
let ua = {};
if (userAgent.includes('Apifox')) {
ua = {
name: 'Apifox',
version: userAgent,
major: '1',
};
} else {
ua = new UAParser().setUA(userAgent).getBrowser();
}
// 格式化UA
// 将解析后的浏览器信息添加到请求的 custom 属性中
req['browserInfo'] = ua;
// 确保要调用 next 函数继续处理请求
next();
}
}

@ -0,0 +1,7 @@
import { TrimPipe } from './trim.pipe';
describe('TrimPipe', () => {
it('should be defined', () => {
expect(new TrimPipe()).toBeDefined();
});
});

@ -0,0 +1,14 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class TrimPipe implements PipeTransform {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
transform(value: any, metadata: ArgumentMetadata) {
if (value instanceof String) {
return value.trim();
} else if (typeof value === 'string') {
return value.trim();
}
return value;
}
}

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MysqlService } from './mysql.service';
describe('MysqlService', () => {
let service: MysqlService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MysqlService],
}).compile();
service = module.get<MysqlService>(MysqlService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

@ -0,0 +1,54 @@
import { Injectable } from '@nestjs/common';
import { LoggerService } from '@service/logger/logger.service';
import { ConfigService } from '@nestjs/config';
import * as mysql from 'mysql2';
import { drizzle } from 'drizzle-orm/mysql2';
import * as schema from '@entities/schema';
@Injectable()
export class MysqlService {
public db;
public connection;
constructor(
private readonly configService: ConfigService,
private readonly logger: LoggerService,
) {
const connection = mysql.createConnection({
host: this.configService.get('database.mysql.host'),
port: this.configService.get('database.mysql.port'),
user: this.configService.get('database.mysql.username'),
password: this.configService.get('database.mysql.password'),
database: this.configService.get('database.mysql.database'),
// 启用此选项后,MySQL驱动程序将支持大数字(big numbers),这对于存储和处理 bigint 类型的数据尤为重要。
// 如果不启用此选项,MySQL驱动程序可能无法正确处理超过 JavaScript 数字精度范围的大数值,导致数据精度丢失。
supportBigNumbers: true,
// 启用此选项后,MySQL驱动程序将在接收 bigint 或其他大数值时,将其作为字符串返回,而不是作为JavaScript数字。
// 这种处理方式可以避免JavaScript本身的数值精度限制问题,确保大数值在应用程序中保持精确。
bigNumberStrings: true,
});
connection.on('connect', async () => {
try {
this.connection = connection;
this.db = drizzle(connection, {
schema,
mode: 'default',
});
this.logger.info('MySQL service initialized');
} catch (e) {
this.logger.fatal('MySQL service initialized Field');
this.logger.error(e);
}
try {
await this.db.select().from(schema.pacCoreService);
this.logger.info('MySQL service initialized successfully');
} catch (err) {
this.logger.fatal('MySQL service initialized Field');
this.logger.error(err);
}
});
}
async onModuleInit() {}
}

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { RedisService } from './redis.service';
describe('RedisService', () => {
let service: RedisService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RedisService],
}).compile();
service = module.get<RedisService>(RedisService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

@ -0,0 +1,89 @@
import { Injectable } from '@nestjs/common';
import { createClient, RedisClientType } from 'redis';
import { ConfigService } from '@nestjs/config';
import { LoggerService } from '@service/logger/logger.service';
@Injectable()
export class RedisService {
private readonly redis: RedisClientType;
constructor(
private readonly configService: ConfigService,
private readonly logger: LoggerService,
) {
const redisConfig = this.configService.get('database.redis');
this.redis = createClient({
name: redisConfig.connectName,
username: redisConfig.username,
password: redisConfig.password,
database: redisConfig.database,
url: `redis://${redisConfig.username}:${redisConfig.password}@${redisConfig.host}:${redisConfig.port}/${redisConfig.database}`,
});
}
async onModuleInit() {
this.redis.on('connect', async () => {
this.logger.info(await this.redis.set('init', 'iAMIron'));
this.logger.info('Redis service initialized');
});
this.redis.on('error', (err) => {
this.logger.error('Redis error: ', err);
});
// 连接到 Redis
await this.redis.connect();
}
onModuleDestroy() {
this.redis.quit();
}
get(key) {
return this.redis.get(key);
}
set(key, value) {
return this.redis.set(key, value);
}
setNX(key, value) {
return this.redis.setNX(key, value);
}
// 分布式锁
async distributedLock(businessName, key) {
// 不存在插入值的Key
const KEY = businessName + key;
// 不存在插入值的Value,设置为业务名
const VALUE = businessName;
// expireTimeInSeconds过期时间
const EX = 10;
// 获取锁状态
const result = await this.redis.SET(KEY, VALUE, {
NX: true,
EX,
});
// 不存在,可以上锁
if (result) {
// 定时器
const continuationLock = setInterval(async () => {
// 续锁
await this.redis.EXPIRE(KEY, EX);
}, 9000);
return () => {
// 主动解锁
// 清除定时器
clearInterval(continuationLock);
// 删除键
this.redis.DEL(KEY);
};
} else {
// 存在锁 返回false
return false;
}
}
}

@ -0,0 +1,48 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: configuration.ts -
// | @创建时间: 2024-05-10 13:44
// | @更新时间: 2024-05-10 13:44
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
export default () => ({
allEnv: process.env,
env: process.env['NODE_ENV'] || 'development',
masterId: 0,
main: {
host: '0.0.0.0' || 'localhost',
port: 3000,
title: 'PAC-Auth',
description: 'RBAC完善型用户鉴权与基础服务系统',
version: process.env.npm_package_version || '0.0.1',
},
logger: {
level: process.env['NODE_ENV'] === 'development' ? 'trace' : 'warning',
},
swagger: {
path: 'swagger',
},
database: {
mysql: {
host: '172.16.1.10',
port: 3306,
database: 'pacauth',
username: 'root',
password: 'Hxl1314521',
},
redis: {
host: '172.16.1.10',
port: 6379,
connectName: 'pacAuth',
database: 9,
username: 'default',
password: 'Hxl1314521',
},
},
});

@ -0,0 +1,89 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: get.dto.ts -
// | @创建时间: 2024-05-28 18:30
// | @更新时间: 2024-05-28 18:30
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { IsOptional, IsInt, Max, Min, IsEnum } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { BooleanEnum } from '@utils/boolean.enum';
import Trim from '@common/decorator/trim/trim.decorator';
export class GetDto {
@ApiProperty({
description: '页码',
type: Number,
example: 1,
required: false,
// format: 'email', // email, date-time, uuid
// 数字属性的最小值
minimum: 1,
// minLength: 2,
// maxLength: 16,
// minItems: 1, // 数组属性的最小项目数
})
@Trim()
@IsOptional()
@IsInt({
message: '页码必须是整数!',
})
@Min(1, {
message: '页码数字需要大于0!',
})
readonly pageNumber?: number = 1;
@ApiProperty({
description: '每页数量',
type: Number,
example: 10,
required: false,
minimum: 1,
maximum: 200,
})
@Trim()
@IsOptional()
@IsInt({
message: '每页数量必须是整数!',
})
@Min(1, {
message: '每页数量需要大于0!',
})
@Max(200, {
message: '每页数量不能超过200',
})
readonly pageSize?: number = 10;
@ApiProperty({
description: '请求列表',
type: BooleanEnum,
enum: BooleanEnum,
example: false,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'isList参数格式错误' })
@IsOptional()
readonly isList?: BooleanEnum = BooleanEnum.FALSE;
@ApiProperty({
description: '顺序查询',
type: BooleanEnum,
enum: BooleanEnum,
example: false,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'isAsc参数格式错误' })
@IsOptional()
readonly isAsc?: BooleanEnum = BooleanEnum.FALSE;
}

@ -0,0 +1,180 @@
-- Current sql file was generated after introspecting the database
-- If you want to run this migration please uncomment this code before executing migrations
/*
CREATE TABLE `pac_auth_dept` (
`index` int AUTO_INCREMENT NOT NULL,
`dept_id` int NOT NULL,
`pid` int NOT NULL DEFAULT 0,
`grade` int NOT NULL,
`dept_name` varchar(255),
`dept_desc` varchar(255),
`dept_type` int NOT NULL DEFAULT 0,
`dept_leader` int,
`default_role` int,
`order_num` int NOT NULL DEFAULT 0,
`status` int NOT NULL DEFAULT 0,
CONSTRAINT `pac_auth_dept_dept_id` PRIMARY KEY(`dept_id`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_auth_link_role_menu` (
`index` int AUTO_INCREMENT NOT NULL,
`role_id` int NOT NULL,
`menu_id` int NOT NULL,
CONSTRAINT `pac_auth_link_role_menu_index` PRIMARY KEY(`index`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_auth_link_user_dept` (
`index` int AUTO_INCREMENT NOT NULL,
`user_id` int NOT NULL,
`dept_id` int NOT NULL,
CONSTRAINT `pac_auth_link_user_dept_index` PRIMARY KEY(`index`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_auth_link_user_post` (
`index` int AUTO_INCREMENT NOT NULL,
`user_id` int NOT NULL,
`post_id` int NOT NULL,
CONSTRAINT `pac_auth_link_user_post_index` PRIMARY KEY(`index`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_auth_link_user_role` (
`index` int AUTO_INCREMENT NOT NULL,
`user_id` int NOT NULL,
`role_id` int NOT NULL,
CONSTRAINT `pac_auth_link_user_role_index` PRIMARY KEY(`index`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_auth_post` (
`index` int AUTO_INCREMENT NOT NULL,
`post_id` int NOT NULL,
`post_key` varchar(255) NOT NULL,
`post_name` varchar(255),
`post_desc` varchar(255),
`order_num` int NOT NULL DEFAULT 0,
`status` int NOT NULL DEFAULT 0,
CONSTRAINT `pac_auth_post_post_id` PRIMARY KEY(`post_id`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_auth_role` (
`index` int NOT NULL,
`role_id` int NOT NULL,
`pid` int NOT NULL DEFAULT 0,
`role_name` varchar(255) NOT NULL,
`role_key` varchar(255) NOT NULL,
`role_desc` varchar(255),
`role_type` int NOT NULL DEFAULT 0,
`role_data_scope` int NOT NULL DEFAULT 1,
`own_of_pac` int NOT NULL DEFAULT 0,
`order_num` int NOT NULL DEFAULT 0,
`status` int NOT NULL DEFAULT 0,
CONSTRAINT `pac_auth_role_role_id` PRIMARY KEY(`role_id`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_auth_role_data_scope` (
`index` int AUTO_INCREMENT NOT NULL,
`role_id` int NOT NULL,
`dept_id` int NOT NULL,
`scope_id` int NOT NULL,
CONSTRAINT `pac_auth_role_data_scope_scope_id` PRIMARY KEY(`scope_id`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_auth_user` (
`index` int AUTO_INCREMENT NOT NULL,
`user_id` int NOT NULL,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`nickname` varchar(255) NOT NULL,
`user_type` int NOT NULL DEFAULT 0,
`user_email` varchar(255),
`pid` int NOT NULL DEFAULT 0,
`wx_appid` int,
`avatar` varchar(255),
`user_phone` varchar(255),
`user_desc` varchar(255),
`status` int NOT NULL DEFAULT 0,
`have_children` int NOT NULL DEFAULT 0,
CONSTRAINT `pac_auth_user_user_id` PRIMARY KEY(`user_id`),
CONSTRAINT `pac_index` UNIQUE(`index`),
CONSTRAINT `username` UNIQUE(`username`)
);
--> statement-breakpoint
CREATE TABLE `pac_core_dict` (
`index` int AUTO_INCREMENT NOT NULL,
`dict_id` int NOT NULL,
`pid` int,
`dict_key` varchar(255) NOT NULL,
`dict_desc` varchar(255),
`dict_name` varchar(255) NOT NULL,
`dict_icon` varchar(255) NOT NULL DEFAULT '',
`dict_type` int NOT NULL,
`own_of_pac` int NOT NULL DEFAULT 0,
`order_num` int NOT NULL DEFAULT 0,
`status` int NOT NULL DEFAULT 0,
`service_of` int NOT NULL DEFAULT 0,
`have_children` int NOT NULL DEFAULT 0,
CONSTRAINT `pac_core_dict_dict_id` PRIMARY KEY(`dict_id`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_core_env` (
`index` int AUTO_INCREMENT NOT NULL,
`env_id` int NOT NULL,
`pid` int NOT NULL DEFAULT 0,
`env_name` varchar(255) NOT NULL,
`env_key` varchar(255) NOT NULL,
`env_val` varchar(255) NOT NULL,
`val_is_dict` int NOT NULL DEFAULT 0,
`env_desc` varchar(255),
`have_children` int NOT NULL DEFAULT 0,
`own_of_pac` int NOT NULL DEFAULT 0,
`order_num` int NOT NULL DEFAULT 0,
`status` int NOT NULL DEFAULT 0,
`service_of` int NOT NULL DEFAULT 0,
CONSTRAINT `pac_core_env_env_id` PRIMARY KEY(`env_id`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_core_menu` (
`index` int AUTO_INCREMENT NOT NULL,
`menu_id` int NOT NULL,
`pid` int NOT NULL,
`have_children` int NOT NULL DEFAULT 0,
`api_path` varchar(255) NOT NULL,
`web_path` varchar(255) NOT NULL,
`menu_name` varchar(255) NOT NULL,
`menu_desc` varchar(255),
`menu_type` int NOT NULL DEFAULT 0,
`menu_icon` varchar(255),
`is_frame` int NOT NULL DEFAULT 0,
`is_visible` int NOT NULL DEFAULT 0,
`is_activate` int NOT NULL DEFAULT 0,
`order_num` int NOT NULL,
`status` int NOT NULL DEFAULT 0,
`service_of` int NOT NULL DEFAULT 0,
CONSTRAINT `pac_core_menu_menu_id` PRIMARY KEY(`menu_id`),
CONSTRAINT `pac_index` UNIQUE(`index`)
);
--> statement-breakpoint
CREATE TABLE `pac_core_service` (
`service_key` varchar(255) NOT NULL,
`service_name` varchar(255) NOT NULL,
`service_version` varchar(255),
`service_desc` varchar(255),
`createby` int NOT NULL,
`createtime` datetime NOT NULL,
`updateby` int,
`updatetime` datetime NOT NULL,
`deleteby` int,
`deletetime` datetime,
CONSTRAINT `pac_core_service_service_key` PRIMARY KEY(`service_key`)
);
*/

@ -0,0 +1,24 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: customType.ts -
// | @创建时间: 2024-06-04 16:27
// | @更新时间: 2024-06-04 16:27
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
// 定义自定义类型
import { customType } from 'drizzle-orm/mysql-core';
// 写入度取是将bigint转化为string
export const bigintString = customType<{ data: string }>({
dataType() {
return 'bigint';
},
fromDriver(value) {
return value.toString();
},
});

File diff suppressed because it is too large Load Diff

@ -0,0 +1,13 @@
{
"version": "6",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1716803133171,
"tag": "0000_productive_zarda",
"breakpoints": true
}
]
}

@ -0,0 +1,3 @@
import { relations } from "drizzle-orm/relations";
import { } from "./schema";

@ -0,0 +1,342 @@
import { mysqlTable, mysqlSchema, AnyMySqlColumn, primaryKey, unique, int, varchar, datetime } from 'drizzle-orm/mysql-core';
import { sql } from 'drizzle-orm';
import { bigintString } from '@entities/customType';
const bigint = bigintString;
export const pacAuthDept = mysqlTable(
'pac_auth_dept',
{
index: int('index').autoincrement().notNull(),
deptId: int('dept_id').notNull(),
pid: int('pid').default(0).notNull(),
grade: int('grade').notNull(),
deptName: varchar('dept_name', { length: 255 }),
deptDesc: varchar('dept_desc', { length: 255 }),
deptType: int('dept_type').default(0).notNull(),
deptLeader: int('dept_leader'),
defaultRole: int('default_role'),
orderNum: int('order_num').default(0).notNull(),
status: int('status').default(0).notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthDeptDeptId: primaryKey({ columns: [table.deptId], name: 'pac_auth_dept_dept_id' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacAuthLinkRoleMenu = mysqlTable(
'pac_auth_link_role_menu',
{
index: int('index').autoincrement().notNull(),
roleId: int('role_id').notNull(),
menuId: int('menu_id').notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthLinkRoleMenuIndex: primaryKey({ columns: [table.index], name: 'pac_auth_link_role_menu_index' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacAuthLinkUserDept = mysqlTable(
'pac_auth_link_user_dept',
{
index: int('index').autoincrement().notNull(),
userId: int('user_id').notNull(),
deptId: int('dept_id').notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthLinkUserDeptIndex: primaryKey({ columns: [table.index], name: 'pac_auth_link_user_dept_index' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacAuthLinkUserPost = mysqlTable(
'pac_auth_link_user_post',
{
index: int('index').autoincrement().notNull(),
userId: int('user_id').notNull(),
postId: int('post_id').notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthLinkUserPostIndex: primaryKey({ columns: [table.index], name: 'pac_auth_link_user_post_index' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacAuthLinkUserRole = mysqlTable(
'pac_auth_link_user_role',
{
index: int('index').autoincrement().notNull(),
userId: int('user_id').notNull(),
roleId: int('role_id').notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthLinkUserRoleIndex: primaryKey({ columns: [table.index], name: 'pac_auth_link_user_role_index' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacAuthPost = mysqlTable(
'pac_auth_post',
{
index: int('index').autoincrement().notNull(),
postId: int('post_id').notNull(),
postKey: varchar('post_key', { length: 255 }).notNull(),
postName: varchar('post_name', { length: 255 }),
postDesc: varchar('post_desc', { length: 255 }),
orderNum: int('order_num').default(0).notNull(),
status: int('status').default(0).notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthPostPostId: primaryKey({ columns: [table.postId], name: 'pac_auth_post_post_id' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacAuthRole = mysqlTable(
'pac_auth_role',
{
index: int('index').notNull(),
roleId: int('role_id').notNull(),
pid: int('pid').default(0).notNull(),
roleName: varchar('role_name', { length: 255 }).notNull(),
roleKey: varchar('role_key', { length: 255 }).notNull(),
roleDesc: varchar('role_desc', { length: 255 }),
roleType: int('role_type').default(0).notNull(),
roleDataScope: int('role_data_scope').default(1).notNull(),
ownOfPac: int('own_of_pac').default(0).notNull(),
orderNum: int('order_num').default(0).notNull(),
status: int('status').default(0).notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthRoleRoleId: primaryKey({ columns: [table.roleId], name: 'pac_auth_role_role_id' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacAuthRoleDataScope = mysqlTable(
'pac_auth_role_data_scope',
{
index: int('index').autoincrement().notNull(),
roleId: int('role_id').notNull(),
deptId: int('dept_id').notNull(),
scopeId: int('scope_id').notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthRoleDataScopeScopeId: primaryKey({ columns: [table.scopeId], name: 'pac_auth_role_data_scope_scope_id' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacAuthUser = mysqlTable(
'pac_auth_user',
{
index: int('index').autoincrement().notNull(),
userId: bigint('user_id', { mode: 'number' }).notNull(),
username: varchar('username', { length: 255 }).notNull(),
password: varchar('password', { length: 255 }).notNull(),
nickname: varchar('nickname', { length: 255 }).notNull(),
userType: int('user_type').default(0).notNull(),
userEmail: varchar('user_email', { length: 255 }),
pid: bigint('pid', { mode: 'number' }).notNull(),
wxAppid: int('wx_appid'),
avatar: varchar('avatar', { length: 255 }),
userPhone: varchar('user_phone', { length: 255 }),
userDesc: varchar('user_desc', { length: 255 }),
status: int('status').default(0).notNull(),
haveChildren: int('have_children').default(0).notNull(),
createby: bigint('createby', { mode: 'number' }).notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: bigint('updateby', { mode: 'number' }),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: bigint('deleteby', { mode: 'number' }),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacAuthUserUserId: primaryKey({ columns: [table.userId], name: 'pac_auth_user_user_id' }),
pacIndex: unique('pac_index').on(table.index),
username: unique('username').on(table.username),
};
},
);
export const pacCoreDict = mysqlTable(
'pac_core_dict',
{
index: int('index').autoincrement().notNull(),
dictId: bigint('dict_id', { mode: 'number' }).notNull(),
pid: bigint('pid', { mode: 'number', unsigned: true }),
dictKey: varchar('dict_key', { length: 255 }).notNull(),
dictDesc: varchar('dict_desc', { length: 255 }),
dictName: varchar('dict_name', { length: 255 }).notNull(),
dictIcon: varchar('dict_icon', { length: 255 }).default('').notNull(),
dictType: int('dict_type', { unsigned: true }).default(0).notNull(),
ownOfPac: int('own_of_pac', { unsigned: true }).default(0).notNull(),
orderNum: int('order_num', { unsigned: true }).default(0).notNull(),
status: int('status', { unsigned: true }).default(0).notNull(),
serviceOf: varchar('service_of', { length: 255 }).default('').notNull(),
haveChildren: int('have_children').default(0).notNull(),
createby: bigint('createby', { mode: 'number' }).notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: bigint('updateby', { mode: 'number' }),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: bigint('deleteby', { mode: 'number' }),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacCoreDictDictId: primaryKey({ columns: [table.dictId], name: 'pac_core_dict_dict_id' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacCoreEnv = mysqlTable(
'pac_core_env',
{
index: int('index').autoincrement().notNull(),
envId: bigint('env_id', { mode: 'number' }).notNull(),
pid: bigint('pid', { mode: 'number' }).notNull(),
envName: varchar('env_name', { length: 255 }).notNull(),
envKey: varchar('env_key', { length: 255 }).notNull(),
envVal: varchar('env_val', { length: 255 }).notNull(),
valIsDict: int('val_is_dict').default(0).notNull(),
envDesc: varchar('env_desc', { length: 255 }),
haveChildren: int('have_children').default(0).notNull(),
ownOfPac: int('own_of_pac').default(0).notNull(),
orderNum: int('order_num').default(0).notNull(),
status: int('status').default(0).notNull(),
serviceOf: varchar('service_of', { length: 255 }).notNull(),
createby: bigint('createby', { mode: 'number' }).notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: bigint('updateby', { mode: 'number' }),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: bigint('deleteby', { mode: 'number' }),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacCoreEnvEnvId: primaryKey({ columns: [table.envId], name: 'pac_core_env_env_id' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacCoreMenu = mysqlTable(
'pac_core_menu',
{
index: int('index').autoincrement().notNull(),
menuId: bigint('menu_id', { mode: 'number' }).notNull(),
pid: bigint('pid', { mode: 'number' }).notNull(),
haveChildren: int('have_children').default(0).notNull(),
apiPath: varchar('api_path', { length: 255 }).notNull(),
webPath: varchar('web_path', { length: 255 }).notNull(),
menuName: varchar('menu_name', { length: 255 }).notNull(),
menuDesc: varchar('menu_desc', { length: 255 }),
menuType: int('menu_type').default(0).notNull(),
menuIcon: varchar('menu_icon', { length: 255 }),
isFrame: int('is_frame').default(0).notNull(),
isVisible: int('is_visible').default(0).notNull(),
isActivate: int('is_activate').default(0).notNull(),
orderNum: int('order_num').default(0).notNull(),
status: int('status').default(0).notNull(),
serviceOf: varchar('service_of', { length: 255 }).notNull(),
createby: bigint('createby', { mode: 'number' }).notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: bigint('updateby', { mode: 'number' }),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: bigint('deleteby', { mode: 'number' }),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacCoreMenuMenuId: primaryKey({ columns: [table.menuId], name: 'pac_core_menu_menu_id' }),
pacIndex: unique('pac_index').on(table.index),
};
},
);
export const pacCoreService = mysqlTable(
'pac_core_service',
{
serviceKey: varchar('service_key', { length: 255 }).notNull(),
serviceName: varchar('service_name', { length: 255 }).notNull(),
serviceVersion: varchar('service_version', { length: 255 }),
serviceDesc: varchar('service_desc', { length: 255 }),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {
return {
pacCoreServiceServiceKey: primaryKey({ columns: [table.serviceKey], name: 'pac_core_service_service_key' }),
};
},
);

@ -0,0 +1,87 @@
// # nestjs核心框架
import { NestFactory } from '@nestjs/core';
/**
* FastifyAdapter: NestJS Fastify
* Fastify NestJS HTTP FastifyAdapter 使 NestJS Fastify
* NestFastifyApplication: 一个接口
* NestJS INestApplication Fastify 特有的功能: 使用 Fastify , Fastify schema , 使 Fastify
* */
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
// # 程序入口文件
import { AppModule } from '@app/app.module';
// # 配置服务: 来源于AppModule导入的configuration
import { ConfigService } from '@nestjs/config';
// # 日志服务
import { LoggerService } from '@service/logger/logger.service';
// # 使用swagger
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { LogRequestInfoInterceptor } from '@common/interceptor/log-request-info/log-request-info.interceptor';
import { ValidationPipe } from '@nestjs/common';
import { FormatResponseInterceptor } from '@common/interceptor/format-response/format-response.interceptor';
import { HttpExceptFilter } from '@common/http-except/http-except.filter';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({
logger: false,
}),
);
// 全局开启参数验证--需要安装class-validator 又依赖 class-transformer, 开启了参数转换
app.useGlobalPipes(
new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
}),
);
// 全局统一响应
app.useGlobalInterceptors(new FormatResponseInterceptor());
// 全局错误拦截
const httpExceptFilter: HttpExceptFilter = app.get(HttpExceptFilter);
app.useGlobalFilters(httpExceptFilter);
// 从全局获取配置文件模块
const config = app.get<ConfigService>(ConfigService);
// 获取日志模块
const logger = app.get(LoggerService);
// console.log(config);
// 开发环境
if (config.get<string>('env') === 'development') {
// 在开发环境下,开启openAPI
SwaggerModule.setup(
config.get<string>('swagger.path'),
app,
SwaggerModule.createDocument(
app,
new DocumentBuilder()
.setTitle(config.get<string>('main.title'))
.setDescription(config.get<string>('main.description'))
.setVersion(config.get<string>('main.version'))
.addTag('ROOT')
.build(),
),
);
logger.debug(`Swagger Started: http://localhost:${config.get<string>('main.port')}/${config.get<string>('swagger.path')}`);
// 在开发环境下,记录请求日志
app.useGlobalInterceptors(new LogRequestInfoInterceptor(logger));
logger.debug('LogRequestInfoInterceptor Started');
}
await app.listen(config.get<number>('main.port'), config.get<string>('main.host'));
}
bootstrap();

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LoggerService } from './logger.service';
describe('LoggerService', () => {
let service: LoggerService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LoggerService],
}).compile();
service = module.get<LoggerService>(LoggerService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

@ -0,0 +1,145 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as winston from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import * as colors from 'colors';
@Injectable()
export class LoggerService {
constructor(private readonly configService: ConfigService) {
// winston.addColors(this.myCustomLevels.colors);
}
// 自定义日志等级和颜色
private readonly myCustomLevels = {
levels: {
fatal: 0,
error: 1,
warn: 2,
info: 3,
debug: 4,
trace: 5,
},
colors: {
fatal: colors.red.bgYellow,
error: colors.red,
warn: colors.yellow,
info: colors.green,
debug: colors.cyan,
trace: colors.gray,
},
};
private readonly colorLevel = (level, value = null) => {
const colorizer = this.myCustomLevels.colors[level];
if (colorizer) {
return !value ? colorizer.bold(`${level.toUpperCase().padStart(6, ' ')}`) : colorizer(value);
} else {
return value ? value : level;
}
};
// 创建一个 Winston 的 Logger 实例
private readonly logger: winston.Logger = winston.createLogger({
// 设置日志级别
level: this.configService.get<string>('logger.level'),
// 设置自定义日志级别
levels: this.myCustomLevels.levels,
// 添加日志传输
transports: [
// 在控制台显示的日志
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY/MM/DD HH:mm:ss',
}),
winston.format.printf(
(info) =>
`${colors.green(`[Nest] ${process.pid} -`)} ${info.timestamp} ${this.colorLevel(info.level)} ${colors.bgGreen(' ')} ${info.message}`,
),
),
}),
// 创建一个日志传输类,用于每日轮转文件
new DailyRotateFile({
// 日志文件名格式
filename: 'logs/%DATE%.log',
// 日志文件名中的日期格式
datePattern: 'YYYY-MM-DD',
// 是否将旧的日志文件压缩
zippedArchive: true,
// 日志文件的最大大小
maxSize: '20m',
// 保留日志文件的最大天数
maxFiles: '14d',
// 为日志添加时间戳, 以 JSON 格式记录日志
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.printf((info) => `${info.timestamp} [${info.level.toUpperCase().padEnd(5, '')}] ${info.message}`),
),
}),
// 创建一个记录错误以上级别的日志
new DailyRotateFile({
level: 'error',
// 日志文件名格式
filename: 'logs/error-%DATE%.log',
// 日志文件名中的日期格式
datePattern: 'YYYY-MM-DD',
// 是否将旧的日志文件压缩
zippedArchive: true,
// 日志文件的最大大小
maxSize: '20m',
// 保留日志文件的最大天数
maxFiles: '365d',
// 为日志添加时间戳, 以 JSON 格式记录日志
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
}),
],
// 错误日志级别的传输,仅用于错误日志
exceptionHandlers: [
new DailyRotateFile({
filename: 'logs/exception-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '365d',
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
}),
],
});
fatal(...args: [string | number | boolean]) {
this.logger.log('fatal', args.join(','));
}
error(...args) {
this.logger.error(args);
}
warn(...args) {
this.logger.warn(args);
}
info(...args) {
this.logger.info(args);
}
debug(...args) {
this.logger.debug(args);
}
trace(...args: [string | number | boolean]) {
this.logger.log('trace', args.join(','));
}
}

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SnowflakeService } from './snowflake.service';
describe('SnowflakeService', () => {
let service: SnowflakeService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SnowflakeService],
}).compile();
service = module.get<SnowflakeService>(SnowflakeService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

@ -0,0 +1,74 @@
import { Injectable } from '@nestjs/common';
import { generateMachineId } from '@utils/generateMachineId';
@Injectable()
export class Snowflake {
// 1位:符号位,始终为0,因为ID是正数。
// 41位:用于时间戳,表示自某个特定时间(例如Twitter的Snowflake算法中是2010-11-04 00:00:00 UTC)以来的毫秒数。
// 10位:序列号,用于同一毫秒内生成的多个ID。
// 12位:机器ID或数据中心ID,用于区分不同的机器或数据中心。
// Twitter的起始时间点1288834974657 ?19991107是 941932800n
private static readonly EPOC: bigint = 1288834974657n;
// 时间戳位
private static readonly MAX_TIMESTAMP: number = 41;
// 机器ID位
private static readonly MAX_MACHINE_ID: bigint = 10n;
// 序列号位
private static readonly MAX_SEQUENCE: bigint = 12n;
// 机器ID掩码
private static readonly MACHINE_ID_MASK: bigint = (1n << Snowflake.MAX_MACHINE_ID) - 1n;
// 序列号掩码
private static readonly SEQUENCE_MASK: bigint = (1n << Snowflake.MAX_SEQUENCE) - 1n;
// 当前机器的ID
private machineId: bigint;
// 序列号
private sequence: bigint = 0n;
// 上一次生成ID的时间戳
private lastTimestamp: bigint = 0n;
constructor() {
const machineId: bigint = BigInt(generateMachineId());
if (machineId < 0 || machineId > Snowflake.MACHINE_ID_MASK) {
throw new Error('Machine ID must be between 0 and ' + Snowflake.MACHINE_ID_MASK.toString());
}
console.log(machineId);
this.machineId = BigInt(machineId);
}
public async generate(): Promise<bigint> {
const timestamp: bigint = BigInt(Date.now() - Number(Snowflake.EPOC));
if (timestamp < this.lastTimestamp) {
throw new Error('Clock is moving backwards. Refusing to generate id for ' + (this.lastTimestamp - timestamp) + ' milliseconds');
}
if (this.lastTimestamp === timestamp) {
this.sequence = (this.sequence + 1n) & Snowflake.SEQUENCE_MASK;
if (this.sequence === 0n) {
const waitNextMs = Number(timestamp + 1n - BigInt(Date.now() - Number(Snowflake.EPOC)));
if (waitNextMs > 0) {
await this.sleep(waitNextMs);
}
}
} else {
this.sequence = 0n;
}
this.lastTimestamp = timestamp;
return timestamp << BigInt(Snowflake.MAX_MACHINE_ID + Snowflake.MAX_SEQUENCE) | this.machineId << BigInt(Snowflake.MAX_SEQUENCE) | this.sequence;
}
private async sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}

@ -0,0 +1,40 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: boolean.enum.ts -
// | @创建时间: 2024-06-05 09:37
// | @更新时间: 2024-06-05 09:37
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
// 定义正确和错误的枚举
export enum BooleanEnum {
YES = 'yes',
NO = 'no',
no = 'NO',
yes = 'YES',
TRUE = 'true',
FALSE = 'false',
n1 = '1',
n0 = '0',
}
// 定义正确或错误
export const booleanType = {
yes: ['YES', 'yes', 'true', true, 1, '1'],
no: ['no', 'NO', 'false', false, 0, '0'],
};
// 判断是否是正确的枚举
export function isTrueEnum(target) {
return booleanType.yes.includes(target);
}
// 判断是否存在目标值
export function isExistKey(target, key) {
return Object.keys(target).includes(key) && target[key] !== undefined && target[key] !== null && target[key] != '';
}

@ -0,0 +1,32 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: customDrizzleRow.ts -
// | @创建时间: 2024-06-05 16:46
// | @更新时间: 2024-06-05 16:46
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { sql } from 'drizzle-orm';
// 将递归查询的自定义列明返回,数据来源于要查的列命,并增加level字段
export function customDrizzleRowWithRecursive(obj) {
// ! 获取所有的列别名
const rowNameList = [...Object.keys(obj), 'level'];
// ! 制造drizzle专属的列名称
const rowName = sql.empty();
rowNameList.forEach((i, index) => {
rowName.append(sql`${sql.raw(i)}`);
if (index < rowNameList.length - 1) {
rowName.append(sql`, `);
}
});
return rowName;
}

@ -0,0 +1,25 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: extractBits.ts -
// | @创建时间: 2024-06-14 10:51
// | @更新时间: 2024-06-14 10:51
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
// 提取二进制指定长度位的数字
export default function extractBits(value, start: bigint = 0n) {
// 创建一个掩码,用于保留后10位
const mask: bigint = (1n << 10n) - 1n;
return (value >> start) & mask;
}
// // 示例使用
// const bigIntValue = 0x12345678901234567890n; // 一个大的BigInt值
// const extractedBits = extractBits(bigIntValue, 40); // 从第40位开始提取10个二进制位
// console.log(extractedBits.toString(2)); // 以二进制形式输出提取的位

@ -0,0 +1,35 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: filesize.ts -
// | @创建时间: 2024-05-11 11:57
// | @更新时间: 2024-05-11 11:57
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as colors from 'colors';
export default function formatBytes(bytes: number | string, decimals: number = 3): string {
// 检查 bytes 参数是否为字符串,且字符串内容是否全部是数字
if (typeof bytes === 'string' && !isNaN(bytes as any)) {
// 如果字符串是数字,转换为数值
bytes = parseFloat(bytes);
} else if (typeof bytes !== 'number') {
// 如果 bytes 不是数字或有效的数字字符串,则返回提示
return colors.bold.cyan('-O^O-');
}
if (bytes === 0) {
return colors.bold.cyan('0 Bytes');
}
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return colors.bold.cyan(parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]);
}

@ -0,0 +1,64 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: generateMachineId.ts -
// | @创建时间: 2024-05-31 20:16
// | @更新时间: 2024-05-31 20:16
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as os from 'node:os';
import * as crypto from 'node:crypto';
import extractBits from '@utils/extractBits';
export function generateMachineId() {
// 获取网络接口信息
const networkInterfaces = os.networkInterfaces();
let interfaceDetails = '';
const ipList = [];
// 遍历所有网络接口
for (const ifaceName of Object.keys(networkInterfaces)) {
// 获取每个接口下的详细信息
const iface = networkInterfaces[ifaceName];
for (const details of iface) {
// 如果接口是活动的,并且不是内部或回环接口,那么将它的地址添加到interfaceDetails中
if (details.internal === false && details.family === 'IPv4' && !details.address.startsWith('127.0.')) {
interfaceDetails += details.address;
ipList.push(details.address);
}
}
}
// 获取CPU信息,这里使用了第一个CPU核心的模型和速度
const cpuInfo = os.cpus()[0].model + os.cpus()[0].speed;
// 获取系统字节序信息('LE'代表小端序,'BE'代表大端序)
const systemInfo = os.endianness();
// 将CPU信息、系统信息和网络接口信息拼接起来
const dataToHash = cpuInfo + systemInfo + interfaceDetails;
// 创建一个SHA-256哈希对象
const hash = crypto.createHash('sha256');
// 更新哈希对象的内容
hash.update(dataToHash);
// 生成哈希值的十六进制表示
const machineId = hash.digest('hex');
const index = ipList.findIndex((ip) => {
return ip.split('.')[0] !== '172';
});
if (index > -1) {
return ipList[index].split('.').slice(-1)[0];
} else {
// 返回机器ID
return extractBits(BigInt('0x' + machineId));
}
}

@ -0,0 +1,16 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: likeQuery.ts -
// | @创建时间: 2024-05-28 19:56
// | @更新时间: 2024-05-28 19:56
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
export function likeQuery(data) {
return `%${data}%`;
}

@ -0,0 +1,69 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: mathodColor.ts -
// | @创建时间: 2024-05-11 14:00
// | @更新时间: 2024-05-11 14:00
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as colors from 'colors';
/*enum MethodColor {
GET = 'cyan', // 青色
POST = 'green', // 绿色
PUT = 'yellow', // 黄色
DELETE = 'red', // 红色
PATCH = 'magenta', // 紫色
ALL = 'blue', // 蓝色
OPTIONS = 'grey', // 灰色
SEARCH = 'white', // 白色
}*/
// eslint-disable-next-line no-shadow
enum Method {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
DELETE = 'DELETE',
PATCH = 'PATCH',
ALL = 'ALL',
OPTIONS = 'OPTIONS',
SEARCH = 'SEARCH',
}
export default function mathodColor(method: string | Method) {
switch (method) {
case Method.GET: {
return colors.cyan.bold(method);
}
case Method.POST: {
return colors.green.bold(method);
}
case Method.PUT: {
return colors.yellow.bold(method);
}
case Method.DELETE: {
return colors.red.bold(method);
}
case Method.PATCH: {
return colors.magenta.bold(method);
}
case Method.ALL: {
return colors.blue.bold(method);
}
case Method.OPTIONS: {
return colors.gray.bold(method);
}
case Method.SEARCH: {
return colors.white.bold(method);
}
default: {
return method;
}
}
}

@ -0,0 +1,17 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: myType.d.ts -
// | @创建时间: 2024-05-28 15:38
// | @更新时间: 2024-05-28 15:38
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
export interface PacInfoType {
username: string;
userId: number;
}

@ -0,0 +1,36 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: random.ts -
// | @创建时间: 2024-05-28 18:08
// | @更新时间: 2024-05-28 18:08
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as crypto from 'node:crypto';
// 生成随机的指定长度的HEX
export function randomHEX(length = 8) {
return crypto.randomBytes(length).toString('hex').toUpperCase();
}
// 生成指定位数的随机数
export function randomBitNumber(length) {
const randomNumber = Math.floor(Math.random() * Math.pow(2, length));
return randomNumber;
}
// 生成指定长度的随机数,并在前面补0
export function paddedRandomNumber(length) {
const randomNumber = Math.floor(Math.random() * Math.pow(10, length)).toString();
return randomNumber.padStart(length, '0');
}
// 生成[min, max]范围内的随机整数
export function randomRangeNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

@ -0,0 +1,20 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: sleep.ts -
// | @创建时间: 2024-05-31 14:03
// | @更新时间: 2024-05-31 14:03
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
export default async function sleep(time: number) {
return new Promise((res) => {
setTimeout(() => {
res(undefined);
}, time);
});
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save