Compare commits

..

No commits in common. '5e4a81c3033661331afc85ff1c5f8ac216590798' and 'dcbe19c36ec0dc0bcd5f66e1ad6d1591a57fb971' have entirely different histories.

  1. 2030
      docs/pacAuth_database_0.2.ndm2
  2. 1
      package.json
  3. 84
      pnpm-lock.yaml
  4. 4
      src/application/app.module.ts
  5. 8
      src/application/auth-dept/auth-dept.controller.ts
  6. 45
      src/application/auth-dept/auth-dept.service.ts
  7. 26
      src/application/auth-dept/dto/get-auth-dept.dto.ts
  8. 64
      src/application/auth-post/auth-post.controller.ts
  9. 9
      src/application/auth-post/auth-post.module.ts
  10. 300
      src/application/auth-post/auth-post.service.ts
  11. 80
      src/application/auth-post/dto/create-auth-post.dto.ts
  12. 70
      src/application/auth-post/dto/get-auth-post.dto.ts
  13. 25
      src/application/auth-post/dto/update-auth-post.dto.ts
  14. 1
      src/application/auth-post/entities/auth-post.entity.ts
  15. 2
      src/application/auth-role/auth-role.controller.ts
  16. 8
      src/application/auth-role/auth-role.service.ts
  17. 3
      src/application/auth-role/dto/create-auth-role.dto.ts
  18. 127
      src/application/auth-user/auth-user.controller.ts
  19. 10
      src/application/auth-user/auth-user.module.ts
  20. 696
      src/application/auth-user/auth-user.service.ts
  21. 161
      src/application/auth-user/dto/create-auth-user.dto.ts
  22. 175
      src/application/auth-user/dto/get-auth-user.dto.ts
  23. 48
      src/application/auth-user/dto/password.dto.ts
  24. 46
      src/application/auth-user/dto/signin.dto.ts
  25. 25
      src/application/auth-user/dto/update-auth-user.dto.ts
  26. 31
      src/application/auth-user/dto/updateLink.dto.ts
  27. 1
      src/application/auth-user/entities/auth-user.entity.ts
  28. 9
      src/application/core-dict/core-dict.service.ts
  29. 9
      src/application/core-env/core-env.service.ts
  30. 26
      src/common/decorator/formatUsername/formatUsername.ts
  31. 28
      src/common/service/jwt/jwt.service.ts
  32. 18
      src/common/service/mysql/mysql.service.spec.ts
  33. 18
      src/common/service/redis/redis.service.spec.ts
  34. 2
      src/common/service/redis/redis.service.ts
  35. 30
      src/config/configuration.ts
  36. 3
      src/dto/AttLinkUser.dto.ts
  37. 10
      src/entities/schema.ts
  38. 17
      src/utils/MD5.ts
  39. 20
      src/utils/cryptoPassword.ts
  40. 16
      test/密码随机盐.js

File diff suppressed because it is too large Load Diff

@ -45,7 +45,6 @@
"colors": "^1.4.0",
"drizzle-orm": "^0.31.2",
"fastify": "^4.27.0",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.10.0",
"nodemailer": "^6.9.13",
"redis": "^4.6.14",

@ -47,9 +47,6 @@ importers:
fastify:
specifier: ^4.27.0
version: 4.27.0
jsonwebtoken:
specifier: ^9.0.2
version: 9.0.2
mysql2:
specifier: ^3.10.0
version: 3.10.0
@ -1401,9 +1398,6 @@ packages:
bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@ -1804,9 +1798,6 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@ -2578,16 +2569,6 @@ packages:
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
jsonwebtoken@9.0.2:
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
engines: {node: '>=12', npm: '>=6'}
jwa@1.4.1:
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
jws@3.2.2:
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@ -2627,33 +2608,12 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash.includes@4.3.0:
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
lodash.isboolean@3.0.3:
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
lodash.isinteger@4.0.4:
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
lodash.isnumber@3.0.3:
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
lodash.isstring@4.0.1:
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lodash.once@4.1.1:
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
@ -5157,8 +5117,6 @@ snapshots:
dependencies:
node-int64: 0.4.0
buffer-equal-constant-time@1.0.1: {}
buffer-from@1.1.2: {}
buffer@5.7.1:
@ -5455,10 +5413,6 @@ snapshots:
eastasianwidth@0.2.0: {}
ecdsa-sig-formatter@1.0.11:
dependencies:
safe-buffer: 5.2.1
ee-first@1.1.1: {}
electron-to-chromium@1.4.789: {}
@ -6548,30 +6502,6 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
jsonwebtoken@9.0.2:
dependencies:
jws: 3.2.2
lodash.includes: 4.3.0
lodash.isboolean: 3.0.3
lodash.isinteger: 4.0.4
lodash.isnumber: 3.0.3
lodash.isplainobject: 4.0.6
lodash.isstring: 4.0.1
lodash.once: 4.1.1
ms: 2.1.3
semver: 7.6.2
jwa@1.4.1:
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
jws@3.2.2:
dependencies:
jwa: 1.4.1
safe-buffer: 5.2.1
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@ -6607,24 +6537,10 @@ snapshots:
dependencies:
p-locate: 5.0.0
lodash.includes@4.3.0: {}
lodash.isboolean@3.0.3: {}
lodash.isinteger@4.0.4: {}
lodash.isnumber@3.0.3: {}
lodash.isplainobject@4.0.6: {}
lodash.isstring@4.0.1: {}
lodash.memoize@4.1.2: {}
lodash.merge@4.6.2: {}
lodash.once@4.1.1: {}
lodash@4.17.21: {}
log-symbols@4.1.0:

@ -16,8 +16,6 @@ import { CoreEnvModule } from './core-env/core-env.module';
import { CoreMenuModule } from './core-menu/core-menu.module';
import { AuthRoleModule } from './auth-role/auth-role.module';
import { AuthDeptModule } from './auth-dept/auth-dept.module';
import { AuthPostModule } from './auth-post/auth-post.module';
import { AuthUserModule } from './auth-user/auth-user.module';
@Module({
imports: [
@ -33,8 +31,6 @@ import { AuthUserModule } from './auth-user/auth-user.module';
CoreMenuModule,
AuthRoleModule,
AuthDeptModule,
AuthPostModule,
AuthUserModule,
],
controllers: [AppController],
providers: [

@ -6,8 +6,10 @@ import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger';
import { PacInfo } from '@common/decorator/pac-info/pac-info.decorator';
import { PacInfoType } from '@utils/myType';
import { GetAuthDeptDto } from '@app/auth-dept/dto/get-auth-dept.dto';
import { GetUserForAttDto } from '@dto/GetUserForAtt.dto';
import { DeptLinkUserDto } from '@dto/AttLinkUser.dto';
import {
GetUserForAttDto
} from "@dto/GetUserForAtt.dto";
import {DeptLinkUserDto} from "@dto/AttLinkUser.dto";
@ApiTags('部门服务')
@Controller('authDept')
@ -80,7 +82,7 @@ export class AuthDeptController {
})
@ApiProduces('application/json')
@Get('/user/:pid')
findUser(@Param('pid') pid: string, @Query() getUserForAttDto: GetUserForAttDto) {
findUser(@Param('pid') pid: string,@Query() getUserForAttDto: GetUserForAttDto) {
return this.authDeptService.findUser(pid, getUserForAttDto);
}

@ -15,7 +15,9 @@ import { likeQuery } from '@utils/likeQuery';
import { alias, QueryBuilder } from 'drizzle-orm/mysql-core';
import { customDrizzleRowWithRecursive } from '@utils/customDrizzleRowWithRecursive';
import { GetUserForAttDto } from '@dto/GetUserForAtt.dto';
import { DeptLinkUserDto } from '@dto/AttLinkUser.dto';
import {
DeptLinkUserDto
} from "@dto/AttLinkUser.dto";
@Injectable()
export class AuthDeptService {
@ -152,39 +154,7 @@ export class AuthDeptService {
* DATE: 2024-06-28 11:30:50 -
* */
public async update(id: string, updateAuthDeptDto: UpdateAuthDeptDto, pacInfo: PacInfoType) {
// ! 获取部门信息
const dept = await this.getDeptForDeptId(id);
// ? 不存在部门?
if (dept.length > 0) throw new HttpException('未找到目标部门信息,无法修改!', HttpStatus.BAD_REQUEST);
const pid = dept[0].pid;
// ! 加目标锁,同级,而不是全局
const lock = await this.redisService.distributedLock('DEPT' + updateAuthDeptDto.pid + '-' + updateAuthDeptDto.deptName, updateAuthDeptDto.deptName);
// ? 存在正在进行写入的部门
if (!lock) throw new HttpException('服务繁忙,部门名称重复!', HttpStatus.CONFLICT);
try {
// ! 同级查重
const result = await this.checkRepeatForDeptName(updateAuthDeptDto.deptName, updateAuthDeptDto.pid);
// ? 是否存在重复的部门
if (result.length > 0 && result[0].id != id) throw new HttpException('部门名称重复!', HttpStatus.CONFLICT);
// ! 解锁
lock();
// ! 返回结果
return await this.updateDept(id, updateAuthDeptDto, pacInfo.userId);
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
return this.updateDept(id, updateAuthDeptDto, pacInfo.userId);
}
/** Service
@ -226,9 +196,7 @@ export class AuthDeptService {
// DB 同级查重
private checkRepeatForDeptName(deptName: string, pid: string) {
return this.mysqlService.db
.select({
id: pacAuthDept.deptId,
})
.select()
.from(pacAuthDept)
.where(and(isNull(pacAuthDept.deleteby), eq(pacAuthDept.pid, pid), eq(pacAuthDept.deptName, deptName)));
}
@ -352,6 +320,8 @@ export class AuthDeptService {
// DB 查树
private getRoleTree(pid: string = '0') {
console.log(pid);
// ! 基础层级
const baseQueryBuilder = new QueryBuilder();
const baseQuery = baseQueryBuilder
@ -498,6 +468,7 @@ export class AuthDeptService {
// DB 查找部门下的账户
private async getDeptUser(deptId: string, getUserForAttDto: GetUserForAttDto) {
console.log(getUserForAttDto);
const offset = (getUserForAttDto.pageNumber - 1) * getUserForAttDto.pageSize;
// ! 使用基础查询构建查询总记录数

@ -20,7 +20,7 @@ import Int from '@common/decorator/int/int.descrator';
export class GetAuthDeptDto extends GetDto {
@ApiProperty({
description: '部门',
description: '角色',
type: String,
example: '管理员',
required: false,
@ -28,13 +28,13 @@ export class GetAuthDeptDto extends GetDto {
maxLength: 128,
})
@Trim()
@IsString({ message: '部门信息应为字符串格式!' })
@Length(0, 128, { message: '请将部门信息长度控制在1到128位之间!' })
@IsString({ message: '角色信息应为字符串格式!' })
@Length(0, 128, { message: '请将角色信息长度控制在1到128位之间!' })
@IsOptional()
readonly deptInfo?: string;
@ApiProperty({
description: '部门类型,来自于字典',
description: '角色类型,来自于字典',
type: String,
example: '0',
required: false,
@ -42,13 +42,13 @@ export class GetAuthDeptDto extends GetDto {
maxLength: 19,
})
@Trim()
@IsString({ message: '部门类型格式不正确!' })
@Length(19, 19, { message: '部门类型格式不正确!' })
@IsString({ message: '角色类型格式不正确!' })
@Length(19, 19, { message: '角色类型格式不正确!' })
@IsOptional()
readonly deptType: string;
@ApiProperty({
description: '部门状态',
description: '角色状态',
type: Number,
example: 0,
required: false,
@ -58,19 +58,19 @@ export class GetAuthDeptDto extends GetDto {
@Trim()
@Int()
@IsInt({
message: '部门状态必须是整数!',
message: '角色状态必须是整数!',
})
@Min(-100, {
message: '部门状态需要大于-100!',
message: '角色状态需要大于-100!',
})
@Max(100, {
message: '部门状态不能超过100',
message: '角色状态不能超过100',
})
@IsOptional()
readonly status?: string;
@ApiProperty({
description: '部门层级id',
description: '角色层级id',
type: Number,
example: 0,
required: false,
@ -78,8 +78,8 @@ export class GetAuthDeptDto extends GetDto {
maximum: 100,
})
@Trim()
@IsString({ message: '部门层级id应为字符串格式!' })
@Length(1, 20, { message: '部门层级id格式错误!' })
@IsString({ message: '角色层级id应为字符串格式!' })
@Length(1, 20, { message: '角色层级id格式错误!' })
@IsOptional()
readonly hierarchy?: string;
}

@ -1,64 +0,0 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { AuthPostService } from './auth-post.service';
import { CreateAuthPostDto } from './dto/create-auth-post.dto';
import { UpdateAuthPostDto } from './dto/update-auth-post.dto';
import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger';
import { PacInfo } from '@common/decorator/pac-info/pac-info.decorator';
import { PacInfoType } from '@utils/myType';
import { GetAuthPostDto } from '@app/auth-post/dto/get-auth-post.dto';
@ApiTags('岗位服务')
@Controller('authPost')
export class AuthPostController {
constructor(private readonly authPostService: AuthPostService) {}
@ApiOperation({
summary: '添加岗位',
description: '岗位',
})
@ApiProduces('application/json')
@Post()
create(@Body() createAuthPostDto: CreateAuthPostDto, @PacInfo() pacInfo: PacInfoType) {
return this.authPostService.create(createAuthPostDto, pacInfo);
}
@ApiOperation({
summary: '获取岗位列表',
description: '查询岗位分页或者列表',
})
@ApiProduces('application/json')
@Get()
findAll(@Query() getAuthPostDto: GetAuthPostDto) {
return this.authPostService.findAll(getAuthPostDto);
}
@ApiOperation({
summary: '获取岗位详细信息',
description: '查询岗位详细信息,目录菜单列表,数据权限范围',
})
@ApiProduces('application/json')
@Get(':id')
findOne(@Param('id') id: string) {
return this.authPostService.findOne(id);
}
@ApiOperation({
summary: '更新岗位信息',
description: '更新岗位信息',
})
@ApiProduces('application/json')
@Patch(':id')
update(@Param('id') id: string, @Body() updateAuthPostDto: UpdateAuthPostDto, @PacInfo() pacInfo: PacInfoType) {
return this.authPostService.update(id, updateAuthPostDto, pacInfo);
}
@ApiOperation({
summary: '删除目标岗位',
description: '删除目标岗位信息',
})
@ApiProduces('application/json')
@Delete(':id')
remove(@Param('id') id: string, @PacInfo() pacInfo: PacInfoType) {
return this.authPostService.remove(id, pacInfo);
}
}

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { AuthPostService } from './auth-post.service';
import { AuthPostController } from './auth-post.controller';
@Module({
controllers: [AuthPostController],
providers: [AuthPostService],
})
export class AuthPostModule {}

@ -1,300 +0,0 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateAuthPostDto } from './dto/create-auth-post.dto';
import { UpdateAuthPostDto } from './dto/update-auth-post.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 { pacAuthDept, pacAuthPost, pacAuthUser, pacCoreDict } from '@entities/schema';
import { and, asc, desc, eq, isNull, like, or, sql } from 'drizzle-orm';
import { PacInfoType } from '@utils/myType';
import { isExistKey, isTrueEnum } from '@utils/boolean.enum';
import { GetAuthPostDto } from '@app/auth-post/dto/get-auth-post.dto';
import { likeQuery } from '@utils/likeQuery';
import { alias } from 'drizzle-orm/mysql-core';
@Injectable()
export class AuthPostService {
// 分页数据格式
private readonly postPageType = {
postId: pacAuthPost.postId,
postKey: pacAuthPost.postKey,
postName: pacAuthPost.postName,
postType: pacAuthPost.postType,
postTypeName: pacCoreDict.dictName,
postTypeKey: pacCoreDict.dictKey,
postDesc: pacAuthPost.postDesc,
orderNum: pacAuthPost.orderNum,
status: pacAuthPost.status,
createby: pacAuthPost.createby,
createtime: pacAuthPost.createtime,
updateby: pacAuthPost.updateby,
updatetime: pacAuthPost.updatetime,
};
// 列表数据格式
private readonly postListType = {
postId: pacAuthPost.postId,
postKey: pacAuthPost.postKey,
postName: pacAuthPost.postName,
postType: pacAuthPost.postType,
postTypeName: pacCoreDict.dictName,
postTypeKey: pacCoreDict.dictKey,
orderNum: pacAuthPost.orderNum,
};
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-29 13:13:44 -
* */
public async create(createAuthPostDto: CreateAuthPostDto, pacInfo: PacInfoType) {
// ! 加目标锁,同级,而不是全局
const lock = await this.redisService.distributedLock('POST' + createAuthPostDto.postKey, createAuthPostDto.postKey);
// ? 存在正在进行写入的岗位
if (!lock) throw new HttpException('服务繁忙,岗位名称重复!', HttpStatus.CONFLICT);
// @ 核心逻辑
try {
// ! 查重
const result = await this.checkRepeatForPostKey(createAuthPostDto.postKey);
// ? 是否存在重复的岗位
if (result.length > 0) throw new HttpException('岗位标识重复!', HttpStatus.CONFLICT);
// ! 添加岗位数据
const newPacCoreDict = await this.addPost(createAuthPostDto, pacInfo.userId as any);
// ! 解锁
lock();
// ! 返回结果
return newPacCoreDict;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
}
/** Service
* NAME: findAll
* DESC: 获取岗位分页/
* DATE: 2024-06-29 13:13:47 -
* */
public async findAll(getAuthPostDto: GetAuthPostDto) {
if (isTrueEnum(getAuthPostDto['isList'])) {
return await this.getList(getAuthPostDto);
} else {
return await this.getPage(getAuthPostDto);
}
}
/** Service
* NAME: findOne
* DESC: 获取岗位详细信息
* DATE: 2024-06-29 13:13:49 -
* */
public findOne(id: string) {
return `This action returns a #${id} authPost`;
}
/** Service
* NAME: update
* DESC: 更新岗位信息
* DATE: 2024-06-29 13:13:52 -
* */
public async update(id: string, updateAuthPostDto: UpdateAuthPostDto, pacInfo: PacInfoType) {
return await this.updatePost(id, updateAuthPostDto, pacInfo.userId as any);
}
/** Service
* NAME: remove
* DESC: 删除岗位信息
* DATE: 2024-06-29 13:13:55 -
* */
public remove(id: string, pacInfo: PacInfoType) {
return this.deletePost(id, pacInfo.userId as any);
}
// DB 查重Key
private checkRepeatForPostKey(postKey) {
return this.mysqlService.db
.select({ id: pacAuthPost.postId })
.from(pacAuthPost)
.where(and(isNull(pacAuthPost.deleteby), eq(pacAuthPost.postKey, postKey)));
}
// DB 写入岗位信息
private async addPost(createAuthPostDto: CreateAuthPostDto, userId: string) {
// ! 生成雪花id,用于岗位主键
const id = await this.snowflake.generate();
// ! 定义写入的岗位数据
const newPostData: typeof pacAuthPost.$inferInsert = {
postId: id as any,
postKey: createAuthPostDto.postKey,
postName: createAuthPostDto.postName,
postType: createAuthPostDto.postType,
postDesc: createAuthPostDto.postDesc,
orderNum: createAuthPostDto.orderNum,
createby: userId,
createtime: sql`now()` as any,
};
return await this.mysqlService.db.insert(pacAuthPost).values(newPostData);
}
// DB 查询构建器
private queryBuilder(getAuthPostDto: GetAuthPostDto, selectData) {
// ! 定义基础查询函数
// 启用动态查询模式 $dynamic
const query = this.mysqlService.db
.select(selectData)
.from(pacAuthPost)
.orderBy(
isTrueEnum(getAuthPostDto.isAsc) ? asc(pacAuthPost.orderNum) : desc(pacAuthPost.orderNum),
isTrueEnum(getAuthPostDto.isAsc) ? asc(pacAuthPost.postId) : desc(pacAuthPost.postId),
)
.leftJoin(pacCoreDict, eq(pacAuthPost.postType, pacCoreDict.dictId))
.$dynamic();
// 查询条件集合
const wl = [];
// ? 未删除
wl.push(isNull(pacAuthPost.deleteby));
// ? 模糊查询
wl.push(
or(
like(pacAuthPost.postName, likeQuery(getAuthPostDto.postInfo)),
like(pacAuthPost.postKey, likeQuery(getAuthPostDto.postInfo)),
like(pacAuthPost.postDesc, likeQuery(getAuthPostDto.postInfo)),
).if(isExistKey(pacAuthPost, 'postInfo')),
);
// ? 按照层级查
// ? 是否查岗位类型
wl.push(eq(pacAuthPost.postType, getAuthPostDto.postType).if(isExistKey(getAuthPostDto, 'postType')));
// ? 是否查字典状态
wl.push(eq(pacAuthPost.status, getAuthPostDto.status as any).if(isExistKey(getAuthPostDto, 'status')));
query.where(and(...wl));
return query;
}
// DB 查分页
private async getPage(getAuthPostDto: GetAuthPostDto) {
const offset = (getAuthPostDto.pageNumber - 1) * getAuthPostDto.pageSize;
// ! 使用基础查询构建查询总记录数
const totalCountQuery = this.queryBuilder(getAuthPostDto, {
totalCount: sql`COUNT(*)`,
});
// ! 使用基础查询构建分页查询
// 重命名表
const userTable1 = alias(pacAuthUser, 'userTable1');
const userTable2 = alias(pacAuthUser, 'userTable2');
const paginatedQuery = this.queryBuilder(getAuthPostDto, {
...this.postPageType,
updateName: userTable1.nickname,
createName: userTable2.nickname,
})
.leftJoin(userTable2, eq(pacAuthPost.createby, userTable2.userId))
.leftJoin(userTable1, eq(pacAuthPost.updateby, userTable1.userId))
.limit(getAuthPostDto.pageSize)
.offset(offset);
return {
total: (await totalCountQuery)[0].totalCount,
rowData: await paginatedQuery,
searchData: getAuthPostDto,
};
}
// DB 查列表
private getList(getAuthPostDto: GetAuthPostDto) {
return this.queryBuilder(getAuthPostDto, this.postListType);
}
// DB 删除岗位
private deletePost(id: string, userId: string) {
// ! 删除岗位数据
return this.mysqlService.db
.update(pacAuthPost)
.set({
deletetime: sql`now()`,
deleteby: userId,
})
.where(eq(pacAuthPost.postId, id));
}
// DB 更新岗位
private async updatePost(id: string, updateAuthPostDto: UpdateAuthPostDto, userId: string) {
// ! 查岗位
const post = await this.mysqlService.db
.select()
.from(pacAuthPost)
.where(and(isNull(pacAuthPost.deleteby), eq(pacAuthPost.postId, id)));
if (post.length === 0) throw new HttpException('未找到目标岗位信息,无法修改!', HttpStatus.BAD_REQUEST);
// ! 加目标锁,同级,而不是全局
const lock = await this.redisService.distributedLock('POST' + updateAuthPostDto.postKey, updateAuthPostDto.postKey);
// ? 存在正在进行写入的岗位
if (!lock) throw new HttpException('服务繁忙,岗位标识重复!', HttpStatus.CONFLICT);
// @ 核心逻辑
try {
// ! 查重
const result = await this.checkRepeatForPostKey(updateAuthPostDto.postKey);
// ? 是否存在重复的岗位
if (result.length > 0 && result[0].id != id) throw new HttpException('岗位标识重复!', HttpStatus.CONFLICT);
// ! 添加岗位数据
const newPacCoreDict = await this.mysqlService.db
.update(pacAuthPost)
.set({
postKey: updateAuthPostDto.postKey,
postName: updateAuthPostDto.postName,
postType: updateAuthPostDto.postType,
postDesc: updateAuthPostDto.postDesc,
orderNum: updateAuthPostDto.orderNum,
status: updateAuthPostDto.status,
updateby: userId,
updatetime: sql`now()`,
})
.where(eq(pacAuthPost.postId, id));
// ! 解锁
lock();
// ! 返回结果
return newPacCoreDict;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
}
}

@ -1,80 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import ChangeCase, { CaseType } from '@common/decorator/change-case/change-case.decorator';
import { IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';
export class CreateAuthPostDto {
@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 postKey: string;
@ApiProperty({
description: '岗位名称',
type: String,
example: '研发经理',
required: true,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsString({ message: '岗位名称应为字符串格式!' })
@Length(1, 32, { message: '岗位名称长度控制在1到32位之间!' })
readonly postName: string;
@ApiProperty({
description: '岗位类型,来自于字典',
type: String,
example: '0',
required: true,
minLength: 19,
maxLength: 19,
})
@Trim()
@IsString({ message: '岗位类型格式不正确!' })
@Length(19, 19, { message: '岗位类型格式不正确!' })
readonly postType: string;
@ApiProperty({
description: '岗位描述',
type: String,
example: '0',
required: false,
minLength: 1,
maxLength: 255,
})
@Trim()
@IsString({ message: '岗位描述应为字符串格式!' })
@Length(1, 255, { message: '请将岗位描述长度控制在1到255位之间!' })
@IsOptional()
readonly postDesc: string;
@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;
}

@ -1,70 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: get-auth-dept.dto.ts -
// | @创建时间: 2024-06-28 11:20
// | @更新时间: 2024-06-28 11:20
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { GetDto } from '@dto/get.dto';
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import { IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';
import Int from '@common/decorator/int/int.descrator';
export class GetAuthPostDto extends GetDto {
@ApiProperty({
description: '岗位',
type: String,
example: '管理员',
required: false,
minLength: 1,
maxLength: 128,
})
@Trim()
@IsString({ message: '岗位信息应为字符串格式!' })
@Length(0, 128, { message: '请将岗位信息长度控制在1到128位之间!' })
@IsOptional()
readonly postInfo?: string;
@ApiProperty({
description: '岗位类型,来自于字典',
type: String,
example: '0',
required: false,
minLength: 19,
maxLength: 19,
})
@Trim()
@IsString({ message: '岗位类型格式不正确!' })
@Length(19, 19, { message: '岗位类型格式不正确!' })
@IsOptional()
readonly postType: string;
@ApiProperty({
description: '岗位状态',
type: Number,
example: 0,
required: false,
minimum: -100,
maximum: 100,
})
@Trim()
@Int()
@IsInt({
message: '岗位状态必须是整数!',
})
@Min(-100, {
message: '岗位状态需要大于-100!',
})
@Max(100, {
message: '岗位状态不能超过100',
})
@IsOptional()
readonly status?: string;
}

@ -1,25 +0,0 @@
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { CreateAuthPostDto } from './create-auth-post.dto';
import { IsInt, IsOptional, Max, Min } from 'class-validator';
export class UpdateAuthPostDto extends PartialType(CreateAuthPostDto) {
@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;
}

@ -7,7 +7,7 @@ import { PacInfo } from '@common/decorator/pac-info/pac-info.decorator';
import { PacInfoType } from '@utils/myType';
import { query } from 'express';
import { GetPacAuthRoleAllDto } from '@app/auth-role/dto/get-auth-role.dto';
import { GetUserForAttDto } from '@dto/GetUserForAtt.dto';
import {GetUserForAttDto} from "@dto/GetUserForAtt.dto";
import { RoleLinkUserDto } from '@dto/AttLinkUser.dto';
@ApiTags('角色服务')

@ -24,7 +24,7 @@ import { GetUserForAttDto } from '@dto/GetUserForAtt.dto';
import { likeQuery } from '@utils/likeQuery';
import { alias, QueryBuilder } from 'drizzle-orm/mysql-core';
import { customDrizzleRowWithRecursive } from '@utils/customDrizzleRowWithRecursive';
import { RoleLinkUserDto } from '@dto/AttLinkUser.dto';
import {RoleLinkUserDto} from "@dto/AttLinkUser.dto";
@Injectable()
export class AuthRoleService {
@ -198,7 +198,7 @@ export class AuthRoleService {
const checkRepeat = await this.getRoleForRoleKey(updateAuthRoleDto.roleKey);
// ? 是否存在重复的角色
if (checkRepeat.length > 0 && checkRepeat[0].id != roleId) throw new HttpException('角色标识重复!', HttpStatus.CONFLICT);
if (checkRepeat.length > 0 && checkRepeat[0].roleId != roleId) throw new HttpException('角色标识重复!', HttpStatus.CONFLICT);
// ! 修改角色数据
const result = await this.updateRole(roleId, updateAuthRoleDto, pacInfo.userId as any).catch((e) => {
@ -262,7 +262,7 @@ export class AuthRoleService {
// DB 通过roleKey查找角色信息
private getRoleForRoleKey(key: string) {
return this.mysqlService.db
.select({ id: pacAuthRole.roleId })
.select()
.from(pacAuthRole)
.where(and(isNull(pacAuthRole.deleteby), eq(pacAuthRole.roleKey, key)));
}
@ -529,7 +529,7 @@ export class AuthRoleService {
throw new HttpException('该角色存在子项!', HttpStatus.BAD_REQUEST);
}
// ! 清角色菜单关联表
// ! 清角色菜单关联表
await this.mysqlService.db.delete(pacAuthLinkRoleMenu).where(eq(pacAuthLinkRoleMenu.roleId as any, roleId));
// ? 判断是否是自定义数据范围,清角色部门关联表

@ -49,13 +49,14 @@ export class CreateAuthRoleDto {
description: '角色类型,来自于字典',
type: String,
example: '0',
required: true,
required: false,
minLength: 19,
maxLength: 19,
})
@Trim()
@IsString({ message: '角色类型格式不正确!' })
@Length(19, 19, { message: '角色类型格式不正确!' })
@IsOptional()
readonly roleType: string;
@ApiProperty({

@ -1,127 +0,0 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { AuthUserService } from './auth-user.service';
import { CreateAuthUserDto } from './dto/create-auth-user.dto';
import { UpdateAuthUserDto } from './dto/update-auth-user.dto';
import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger';
import { PacInfo } from '@common/decorator/pac-info/pac-info.decorator';
import { PacInfoType } from '@utils/myType';
import { GetPacAuthUserAllDto } from '@app/auth-user/dto/get-auth-user.dto';
import { PasswordDto } from '@app/auth-user/dto/password.dto';
import { UpdateLinkDto } from '@app/auth-user/dto/updateLink.dto';
import {UsernameSignInDto} from "@app/auth-user/dto/signin.dto";
@ApiTags('账户服务')
@Controller('authUser')
export class AuthUserController {
constructor(private readonly authUserService: AuthUserService) {}
@ApiOperation({
summary: '添加账户',
description: '账户',
})
@ApiProduces('application/json')
@Post()
create(@Body() createAuthUserDto: CreateAuthUserDto, @PacInfo() pacInfo: PacInfoType) {
return this.authUserService.create(createAuthUserDto, pacInfo);
}
@ApiOperation({
summary: '获取账户列表',
description: '查询账户分页或者列表',
})
@ApiProduces('application/json')
@Get()
findAll(@Query() getPacAuthUserAllDto: GetPacAuthUserAllDto) {
return this.authUserService.findAll(getPacAuthUserAllDto);
}
@ApiOperation({
summary: '获取账户详细信息',
description: '查询账户详细信息,目录菜单列表,数据权限范围',
})
@ApiProduces('application/json')
@Get(':id')
findOne(@Param('id') id: string) {
return this.authUserService.findOne(id);
}
@ApiOperation({
summary: '更新账户信息',
description: '更新账户信息',
})
@ApiProduces('application/json')
@Patch(':id')
update(@Param('id') id: string, @Body() updateAuthUserDto: UpdateAuthUserDto, @PacInfo() pacInfo: PacInfoType) {
return this.authUserService.update(id, updateAuthUserDto, pacInfo);
}
@ApiOperation({
summary: '删除目标账户',
description: '删除目标账户信息',
})
@ApiProduces('application/json')
@Delete(':id')
remove(@Param('id') id: string, @PacInfo() pacInfo: PacInfoType) {
return this.authUserService.remove(id, pacInfo);
}
@ApiOperation({
summary: '重置密码',
description: '给指定用户将密码恢复为系统默认账户密码',
})
@ApiProduces('application/json')
@Post('/password/:id')
resetTargetUserPassword(@Param('id') id: string) {
return this.authUserService.resetTargetUserPassword(id);
}
@ApiOperation({
summary: '修改密码',
description: '修改自己密码',
})
@ApiProduces('application/json')
@Patch('/password')
updatePassword(@Param('id') id: string, @Body() passwordDto: PasswordDto) {
return this.authUserService.updatePassword(id, passwordDto);
}
@ApiOperation({
summary: '更新账户角色',
description: '更新账户角色',
})
@ApiProduces('application/json')
@Patch('/role/:id')
updateLinkRole(@Param('id') id: string, @Body() updateLinkDto: UpdateLinkDto, @PacInfo() pacInfo: PacInfoType) {
return this.authUserService.updateLinkRole(id, updateLinkDto, pacInfo);
}
@ApiOperation({
summary: '更新账户部门',
description: '更新账户部门',
})
@ApiProduces('application/json')
@Patch('/dept/:id')
updateLinkDept(@Param('id') id: string, @Body() updateLinkDto: UpdateLinkDto, @PacInfo() pacInfo: PacInfoType) {
return this.authUserService.updateLinkDept(id, updateLinkDto, pacInfo);
}
@ApiOperation({
summary: '更新账户岗位',
description: '更新账户岗位',
})
@ApiProduces('application/json')
@Patch('/post/:id')
updateLinkPost(@Param('id') id: string, @Body() updateLinkDto: UpdateLinkDto, @PacInfo() pacInfo: PacInfoType) {
return this.authUserService.updateLinkPost(id, updateLinkDto, pacInfo);
}
@ApiOperation({
summary: '登陆系统',
description: '登陆系统',
})
@ApiProduces('application/json')
@Post('/signin')
signin(@Body() usernameSignInDto: UsernameSignInDto) {
return this.authUserService.signin(usernameSignInDto);
}
}

@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { AuthUserService } from './auth-user.service';
import { AuthUserController } from './auth-user.controller';
import { JwtService } from '@common/service/jwt/jwt.service';
@Module({
controllers: [AuthUserController],
providers: [AuthUserService, JwtService],
})
export class AuthUserModule {}

@ -1,696 +0,0 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateAuthUserDto } from './dto/create-auth-user.dto';
import { UpdateAuthUserDto } from './dto/update-auth-user.dto';
import { PacInfoType } from '@utils/myType';
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 {
pacAuthDept,
pacAuthLinkRoleMenu,
pacAuthLinkUserDept,
pacAuthLinkUserPost,
pacAuthLinkUserRole,
pacAuthPost,
pacAuthRole,
pacAuthUser,
pacCoreDict,
} from '@entities/schema';
import { and, asc, desc, eq, gt, isNull, like, or, sql } from 'drizzle-orm';
import cryptoPassword from '@utils/cryptoPassword';
import { GetPacAuthUserAllDto } from '@app/auth-user/dto/get-auth-user.dto';
import { isExistKey, isTrueEnum } from '@utils/boolean.enum';
import { likeQuery } from '@utils/likeQuery';
import { alias } from 'drizzle-orm/mysql-core';
import { from } from 'rxjs';
import { UpdateCoreDictDto } from '@app/core-dict/dto/update-core-dict.dto';
import { PasswordDto } from '@app/auth-user/dto/password.dto';
import { UpdateLinkDto } from '@app/auth-user/dto/updateLink.dto';
import { UsernameSignInDto } from '@app/auth-user/dto/signin.dto';
import { JwtService } from '@common/service/jwt/jwt.service';
import MD5 from '@utils/MD5';
@Injectable()
export class AuthUserService {
// 分页数据
private readonly userPageType = {
userId: pacAuthUser.userId,
username: pacAuthUser.username,
nickname: pacAuthUser.nickname,
userType: pacAuthUser.userType,
userTypeName: pacCoreDict.dictName,
userTypeKey: pacCoreDict.dictKey,
userEmail: pacAuthUser.userEmail,
pid: pacAuthUser.pid,
wxAppid: pacAuthUser.wxAppid,
avatar: pacAuthUser.avatar,
userPhone: pacAuthUser.userPhone,
userDesc: pacAuthUser.userDesc,
status: pacAuthUser.status,
haveChildren: pacAuthUser.haveChildren,
createby: pacAuthUser.createby,
createtime: pacAuthUser.createtime,
updateby: pacAuthUser.updateby,
updatetime: pacAuthUser.updatetime,
};
// 列表数据
private readonly userListType = {
userId: pacAuthUser.userId,
username: pacAuthUser.username,
nickname: pacAuthUser.nickname,
userType: pacAuthUser.userType,
userTypeName: pacCoreDict.dictName,
userTypeKey: pacCoreDict.dictKey,
};
constructor(
private readonly logger: LoggerService,
private readonly mysqlService: MysqlService,
private readonly redisService: RedisService,
private readonly snowflake: Snowflake,
private readonly config: ConfigService,
private readonly jwt: JwtService,
) {}
/** Service
* NAME: create
* DESC: 创建账户
* DATE: 2024-06-29 13:13:55 -
* */
public async create(createAuthUserDto: CreateAuthUserDto, pacInfo: PacInfoType) {
// ! 加目标锁,同级,而不是全局
const lock = await this.redisService.distributedLock('USER' + createAuthUserDto.username, createAuthUserDto.username);
// ? 存在正在进行写入的账户
if (!lock) throw new HttpException('服务繁忙,账户名称重复!', HttpStatus.CONFLICT);
try {
// ! 用户名查重
const result = await this.checkRepeatForUsername(createAuthUserDto.username);
// ? 是否存在重复的账户
if (result.length > 0) throw new HttpException('用户名重复!', HttpStatus.CONFLICT);
// ! 添加账户数据
const newPacCoreDict = await this.addUser(createAuthUserDto, pacInfo);
// ! 解锁
lock();
const insertList = [];
// ! 关联角色
if (createAuthUserDto.roleList && createAuthUserDto.roleList.length > 0) {
insertList.push(this.updateUserRole(newPacCoreDict.userId as any, createAuthUserDto.roleList, pacInfo));
}
// ! 关联部门
if (createAuthUserDto.deptList && createAuthUserDto.deptList.length > 0) {
insertList.push(this.updateUserDept(newPacCoreDict.userId as any, createAuthUserDto.deptList, pacInfo));
}
// ! 关联岗位
if (createAuthUserDto.postList && createAuthUserDto.postList.length > 0) {
insertList.push(this.updateUserPost(newPacCoreDict.userId as any, createAuthUserDto.postList, pacInfo));
}
// ! 执行关联
await Promise.all(insertList).catch((e) => {
this.logger.error('新增用户时,关联角色、部门、岗位出现错误');
this.logger.error(e);
});
// !更新父节点的子节点数量
await this.haveChildrenSelfIncreasing(createAuthUserDto.pid);
// ! 返回结果
return newPacCoreDict;
} catch (e) {
// ! 解锁
lock();
// ! 抛出错误
throw e;
}
return 'This action adds a new authUser';
}
/** Service
* NAME: findAll
* DESC: 查找用户分页/
* DATE: 2024-06-29 13:13:55 -
* */
public async findAll(getPacAuthUserAllDto: GetPacAuthUserAllDto) {
if (isTrueEnum(getPacAuthUserAllDto['isList'])) {
return await this.getList(getPacAuthUserAllDto);
} else {
return await this.getPage(getPacAuthUserAllDto);
}
}
/** Service
* NAME: findOne
* DESC: 查用户详细信息
* DATE: 2024-06-29 13:13:55 -
* */
public async findOne(id: string) {
return await this.getMore(id);
}
/** Service
* NAME: update
* DESC: 修改账户信息
* DATE: 2024-06-29 13:13:55 -
* */
public update(id: string, updateAuthUserDto: UpdateAuthUserDto, pacInfo: PacInfoType) {
return this.updateUser(id, updateAuthUserDto, pacInfo);
}
/** Service
* NAME: remove
* DESC: 移除账户
* DATE: 2024-06-29 13:13:55 -
* */
public remove(id: string, pacInfo: PacInfoType) {
return this.deleteUser(id, pacInfo.userId as any);
}
/** Service
* NAME: resetTargetUserPassword
* DESC: 充值目标账户密码
* DATE: 2024-06-29 13:13:55 -
* */
public async resetTargetUserPassword(id: string) {
return this.resetPassword(id, null);
}
/** Service
* NAME: updatePassword
* DESC: 修改自己账户密码
* DATE: 2024-06-29 13:13:55 -
* */
public async updatePassword(id: string, passwordDto: PasswordDto) {
const user = await this.mysqlService.db
.select({
password: pacAuthUser.password,
})
.from(pacAuthUser)
.where(eq(pacAuthUser.userId, id));
if (user.length == 0) throw new HttpException('未找到目标用户信息,无法修改密码!', HttpStatus.BAD_REQUEST);
const oldPassword = cryptoPassword(passwordDto.oldPassword, this.config.get<number>('system.passwordSalt'));
if (oldPassword != user[0].password) throw new HttpException('原密码错误!', HttpStatus.BAD_REQUEST);
return this.resetPassword(id, passwordDto.newPassword);
}
/** Service
* NAME: updatePassword
* DESC: 修改目标账户角色关联
* DATE: 2024-06-29 13:13:55 -
* */
public async updateLinkRole(id: string, updateLinkDto: UpdateLinkDto, pacInfo: PacInfoType) {
await this.checkUserExist(id);
if (updateLinkDto) {
return this.updateUserRole(id, updateLinkDto.list, pacInfo);
} else {
return;
}
}
/** Service
* NAME: updateLinkDept
* DESC: 修改目标账户部门关联
* DATE: 2024-06-29 13:13:55 -
* */
public async updateLinkDept(id: string, updateLinkDto: UpdateLinkDto, pacInfo: PacInfoType) {
await this.checkUserExist(id);
if (updateLinkDto) {
return this.updateUserDept(id, updateLinkDto.list, pacInfo);
} else {
return;
}
}
/** Service
* NAME: updateLinkPost
* DESC: 修改目标账户岗位关联
* DATE: 2024-06-29 13:13:55 -
* */
public async updateLinkPost(id: string, updateLinkDto: UpdateLinkDto, pacInfo: PacInfoType) {
await this.checkUserExist(id);
if (updateLinkDto) {
return this.updateUserPost(id, updateLinkDto.list, pacInfo);
} else {
return;
}
}
/** Service
* NAME: updateLinkPost
* DESC: 修改目标账户岗位关联
* DATE: 2024-06-29 13:13:55 -
* */
public async signin(usernameSignInDto: UsernameSignInDto) {
// 登陆检测
return await this.signinDetection(usernameSignInDto);
}
// DB 同级查重
private checkRepeatForUsername(username: string) {
return this.mysqlService.db
.select({
id: pacAuthUser.username,
})
.from(pacAuthUser)
.where(and(isNull(pacAuthUser.deleteby), eq(pacAuthUser.username, username)));
}
// DB 更新父级子元素数量
private async haveChildrenSelfIncreasing(id: string, isAdd = true) {
return this.mysqlService.db
.update(pacAuthUser)
.set({
haveChildren: isAdd ? sql`${pacAuthUser.haveChildren} + 1` : sql`${pacAuthUser.haveChildren} - 1`,
})
.where(eq(pacAuthUser.userId as any, id));
}
// DB 添加账户
private async addUser(createAuthUserDto: CreateAuthUserDto, pacInfo: PacInfoType) {
// ! 生成雪花id,用于账户id
const id = await this.snowflake.generate();
let password = '';
// ! 从Redis获取默认密码
const defaultPasswordForRedis = await this.redisService.get('ENV_DEFAULT_PASSWORD');
// ? 判断redis中是否存在默认密码环境变量
if (!defaultPasswordForRedis) {
password = this.config.get<string>('system.defaultPassword');
} else {
password = defaultPasswordForRedis;
}
// ! 加密密码
const pass = cryptoPassword(password, this.config.get<number>('system.passwordSalt'));
// ! 定义写入的账户数据
const newUserData: typeof pacAuthUser.$inferInsert = {
userId: id as any,
username: createAuthUserDto.username,
password: pass,
nickname: createAuthUserDto.nickname,
userType: createAuthUserDto.userType,
userEmail: createAuthUserDto.userEmail,
pid: createAuthUserDto.pid,
avatar: createAuthUserDto.avatar,
userPhone: createAuthUserDto.userPhone,
userDesc: createAuthUserDto.userDesc,
createby: pacInfo.userId as any,
createtime: sql`now()` as any,
};
return {
insert: await this.mysqlService.db.insert(pacAuthUser).values(newUserData),
userId: id.toString(),
};
}
// COMMON 去重数组
private deduplicateArray(arr: string[]) {
return Array.from(new Set(arr));
}
// COMMON 向目标表插入列
private async insertKeyValueForUserLink(table, list, key, userId, pacInfo: PacInfoType) {
// ! 清空曾经的关联
await this.mysqlService.db.delete(table).where(eq(table.userId as any, userId));
// ! 去重列表
const arr = this.deduplicateArray(list);
// ! 增加新的关联
return await this.mysqlService.db.insert(table).values(
arr.map((i) => ({
userId: userId,
[key]: i,
createby: pacInfo.userId as any,
createtime: sql`now()` as any,
})),
);
}
// DB 修改用户角色
private async updateUserRole(userId: string, roleIdList: string[], pacInfo: PacInfoType) {
return await this.insertKeyValueForUserLink(pacAuthLinkUserRole, roleIdList, 'roleId', userId, pacInfo);
}
// DB 修改账户部门
private async updateUserDept(userId: string, deptIdList: string[], pacInfo: PacInfoType) {
return await this.insertKeyValueForUserLink(pacAuthLinkUserDept, deptIdList, 'deptId', userId, pacInfo);
}
// DB修改账户岗位
private async updateUserPost(userId: string, postIdList: string[], pacInfo: PacInfoType) {
return await this.insertKeyValueForUserLink(pacAuthLinkUserPost, postIdList, 'postId', userId, pacInfo);
}
// 查询构建器
private queryBuilder(getPacAuthUserAllDto: GetPacAuthUserAllDto, selectData) {
// ! 定义基础查询函数
// 启用动态查询模式 $dynamic
const query = this.mysqlService.db
.select(selectData)
.from(pacAuthUser)
.orderBy(isTrueEnum(getPacAuthUserAllDto.isAsc) ? asc(pacAuthUser.userId) : desc(pacAuthUser.userId))
.leftJoin(pacCoreDict, eq(pacAuthUser.userType, pacCoreDict.dictId))
// 角色
.leftJoin(pacAuthLinkUserRole, eq(pacAuthUser.userId, pacAuthLinkUserRole.userId))
// 部门
.leftJoin(pacAuthLinkUserDept, eq(pacAuthUser.userId, pacAuthLinkUserDept.userId))
// 岗位
.leftJoin(pacAuthLinkUserPost, eq(pacAuthUser.userId, pacAuthLinkUserPost.userId))
.$dynamic();
// 查询条件集合
const wl = [];
// ? 未删除
wl.push(isNull(pacAuthUser.deleteby));
// ? 模糊查询
wl.push(
or(
like(pacAuthUser.username, likeQuery(getPacAuthUserAllDto.userInfo)),
like(pacAuthUser.nickname, likeQuery(getPacAuthUserAllDto.userInfo)),
like(pacAuthUser.userEmail, likeQuery(getPacAuthUserAllDto.userInfo)),
like(pacAuthUser.userPhone, likeQuery(getPacAuthUserAllDto.userInfo)),
like(pacAuthUser.userDesc, likeQuery(getPacAuthUserAllDto.userInfo)),
).if(isExistKey(getPacAuthUserAllDto, 'userInfo')),
);
// ? 按照层级查
wl.push(eq(pacAuthUser.pid, getPacAuthUserAllDto.hierarchy).if(isExistKey(getPacAuthUserAllDto, 'hierarchy')));
// ? 是否查角色类型
wl.push(eq(pacAuthUser.userType, getPacAuthUserAllDto.userType).if(isExistKey(getPacAuthUserAllDto, 'userType')));
// ? 是否查字典状态
wl.push(eq(pacAuthUser.status, getPacAuthUserAllDto.status as any).if(isExistKey(getPacAuthUserAllDto, 'status')));
// ? 是否存在子账户
wl.push(gt(pacAuthUser.haveChildren, 0).if(isExistKey(getPacAuthUserAllDto, 'haveChildren') && isTrueEnum(getPacAuthUserAllDto.haveChildren)));
// ? 角色
wl.push(
eq(pacAuthLinkUserRole.roleId, getPacAuthUserAllDto.roleId).if(
isExistKey(getPacAuthUserAllDto, 'roleId') && !isTrueEnum(getPacAuthUserAllDto.noRole),
),
);
// ? 部门
wl.push(eq(pacAuthLinkUserDept.deptId, getPacAuthUserAllDto.deptId).if(isExistKey(getPacAuthUserAllDto, 'deptId')));
// ? 岗位
wl.push(eq(pacAuthLinkUserPost.postId, getPacAuthUserAllDto.postId).if(isExistKey(getPacAuthUserAllDto, 'postId')));
// ? 没有角色
wl.push(isNull(pacAuthLinkUserRole.roleId).if(!isExistKey(getPacAuthUserAllDto, 'roleId') && isTrueEnum(getPacAuthUserAllDto.noRole)));
// ? 没有部门
wl.push(isNull(pacAuthLinkUserDept.deptId).if(!isExistKey(getPacAuthUserAllDto, 'deptId') && isTrueEnum(getPacAuthUserAllDto.noDept)));
// ? 没有岗位
wl.push(isNull(pacAuthLinkUserPost.postId).if(!isExistKey(getPacAuthUserAllDto, 'postId') && isTrueEnum(getPacAuthUserAllDto.noPost)));
query.where(and(...wl));
return query;
}
// DB 查分页
private async getPage(getPacAuthUserAllDto: GetPacAuthUserAllDto) {
const offset = (getPacAuthUserAllDto.pageNumber - 1) * getPacAuthUserAllDto.pageSize;
// ! 使用基础查询构建查询总记录数
const totalCountQuery = this.queryBuilder(getPacAuthUserAllDto, {
totalCount: sql`COUNT(DISTINCT ${pacAuthUser.userId})`,
});
// ! 使用基础查询构建分页查询
// 重命名表
const userTable1 = alias(pacAuthUser, 'userTable1');
const userTable2 = alias(pacAuthUser, 'userTable2');
const selectObject = {
...this.userPageType,
updateName: userTable1.nickname,
createName: userTable2.nickname,
};
const paginatedQuery = this.queryBuilder(getPacAuthUserAllDto, {
...selectObject,
roleList: sql`GROUP_CONCAT(DISTINCT ${pacAuthRole.roleName} ORDER BY ${pacAuthRole.orderNum} DESC SEPARATOR ',')`,
deptList: sql`GROUP_CONCAT(DISTINCT ${pacAuthDept.deptName} ORDER BY ${pacAuthRole.orderNum} DESC SEPARATOR ',')`,
postList: sql`GROUP_CONCAT(DISTINCT ${pacAuthPost.postName} ORDER BY ${pacAuthRole.orderNum} DESC SEPARATOR ',')`,
})
.leftJoin(userTable2, eq(pacAuthUser.createby, userTable2.userId))
.leftJoin(userTable1, eq(pacAuthUser.updateby, userTable1.userId))
.leftJoin(pacAuthRole, eq(pacAuthLinkUserRole.roleId, pacAuthRole.roleId))
.leftJoin(pacAuthDept, eq(pacAuthLinkUserDept.deptId, pacAuthDept.deptId))
.leftJoin(pacAuthPost, eq(pacAuthLinkUserPost.postId, pacAuthPost.postId))
.limit(getPacAuthUserAllDto.pageSize)
.groupBy(pacAuthUser.userId)
.offset(offset);
return {
total: (await totalCountQuery)[0].totalCount,
rowData: await paginatedQuery,
searchData: getPacAuthUserAllDto,
};
}
// DB 查列表
private async getList(getPacAuthUserAllDto: GetPacAuthUserAllDto) {
return this.queryBuilder(getPacAuthUserAllDto, this.userListType);
}
// DB 查详情
private async getMore(id: string) {
const userTable1 = alias(pacAuthUser, 'userTable1');
const userTable2 = alias(pacAuthUser, 'userTable2');
const user = await this.mysqlService.db
.select({
...this.userPageType,
updateName: userTable1.nickname,
createName: userTable2.nickname,
})
.from(pacAuthUser)
.leftJoin(pacCoreDict, eq(pacAuthUser.userType, pacCoreDict.dictId))
.leftJoin(userTable2, eq(pacAuthUser.createby, userTable2.userId))
.leftJoin(userTable1, eq(pacAuthUser.updateby, userTable1.userId))
.where(and(eq(pacAuthUser.userId, id), isNull(pacAuthUser.deleteby)));
if (user.length == 0) throw new HttpException('未找到目标用户信息!', HttpStatus.BAD_REQUEST);
const roleList = await this.mysqlService.db
.select({
roleId: pacAuthRole.roleId,
roleName: pacAuthRole.roleName,
roleKey: pacAuthRole.roleKey,
})
.from(pacAuthRole)
.leftJoin(pacAuthLinkUserRole, eq(pacAuthLinkUserRole.roleId, pacAuthRole.roleId))
.where(eq(pacAuthLinkUserRole.userId, id));
const deptList = await this.mysqlService.db
.select({
deptId: pacAuthDept.deptId,
deptName: pacAuthDept.deptName,
})
.from(pacAuthDept)
.leftJoin(pacAuthLinkUserDept, eq(pacAuthLinkUserDept.deptId, pacAuthDept.deptId))
.where(eq(pacAuthLinkUserDept.userId, id));
const postList = await this.mysqlService.db
.select({
postId: pacAuthPost.postId,
postName: pacAuthPost.postName,
postKey: pacAuthPost.postKey,
})
.from(pacAuthPost)
.leftJoin(pacAuthLinkUserPost, eq(pacAuthLinkUserPost.postId, pacAuthPost.postId))
.where(eq(pacAuthLinkUserPost.userId, id));
return {
...user[0],
roleList,
deptList,
postList,
};
}
// DB 删除账户
private async deleteUser(id: string, userId) {
const user = await this.mysqlService.db
.select({ id: pacAuthUser.userId, pid: pacAuthUser.pid })
.from(pacAuthUser)
.where(and(isNull(pacAuthUser.deleteby), eq(pacAuthUser.userId, id)));
if (user.length == 0) throw new HttpException('未找到目标用户信息,无法删除!', HttpStatus.BAD_REQUEST);
// ! 清理角色菜单关联表
await this.mysqlService.db.delete(pacAuthLinkUserRole).where(eq(pacAuthLinkUserRole.userId as any, id));
// ! 清理部门菜单关联表
await this.mysqlService.db.delete(pacAuthLinkUserDept).where(eq(pacAuthLinkUserDept.userId as any, id));
// ! 清理岗位菜单关联表
await this.mysqlService.db.delete(pacAuthLinkUserPost).where(eq(pacAuthLinkUserPost.userId as any, id));
// ! 判断父节点是否存在
if (user[0].pid != 0) {
// ! 减少父级子节点数量
await this.haveChildrenSelfIncreasing(user[0].pid, false);
}
// ! 删除角色数据
return await this.mysqlService.db
.update(pacAuthUser)
.set({
deletetime: sql`now()`,
deleteby: userId,
})
.where(eq(pacAuthUser.userId, id));
}
// DB 更新账户
private async updateUser(id: string, updateAuthUserDto: UpdateAuthUserDto, pacInfo: PacInfoType) {
const user = await this.mysqlService.db
.select({ id: pacAuthUser.userId, pid: pacAuthUser.pid })
.from(pacAuthUser)
.where(and(isNull(pacAuthUser.deleteby), eq(pacAuthUser.userId, id)));
if (user.length == 0) throw new HttpException('未找到目标用户信息,无法修改!', HttpStatus.BAD_REQUEST);
// 用户名不修改不需要查重
if (updateAuthUserDto.roleList) {
await this.updateUserRole(id, updateAuthUserDto.roleList, pacInfo);
}
if (updateAuthUserDto.deptList) {
await this.updateUserDept(id, updateAuthUserDto.deptList, pacInfo);
}
if (updateAuthUserDto.postList) {
await this.updateUserPost(id, updateAuthUserDto.postList, pacInfo);
}
return await this.mysqlService.db
.update(pacAuthUser)
.set({
nickname: updateAuthUserDto.nickname,
userType: updateAuthUserDto.userType,
userEmail: updateAuthUserDto.userEmail,
avatar: updateAuthUserDto.avatar,
userPhone: updateAuthUserDto.userPhone,
userDesc: updateAuthUserDto.userDesc,
status: updateAuthUserDto.status,
updateby: pacInfo.userId,
updatetime: sql`now()`,
})
.where(eq(pacAuthUser.userId, id));
}
// DB 修改密码
private async resetPassword(id: string, newPassword: string | null) {
let password = '';
if (newPassword != null) {
password = newPassword;
} else {
// ! 从Redis获取默认密码
const defaultPasswordForRedis = await this.redisService.get('ENV_DEFAULT_PASSWORD');
// ? 判断redis中是否存在默认密码环境变量
if (!defaultPasswordForRedis) {
password = this.config.get<string>('system.defaultPassword');
} else {
password = defaultPasswordForRedis;
}
}
// ! 加密密码
const pass = cryptoPassword(password, this.config.get<number>('system.passwordSalt'));
return await this.mysqlService.db
.update(pacAuthUser)
.set({
password: pass,
})
.where(eq(pacAuthUser.userId, id));
}
// DB 查看目标是否存在
private async checkUserExist(id: string) {
const user = await this.mysqlService.db
.select({ id: pacAuthUser.userId, pid: pacAuthUser.pid })
.from(pacAuthUser)
.where(and(isNull(pacAuthUser.deleteby), eq(pacAuthUser.userId, id)));
if (user.length == 0) throw new HttpException('未找到目标用户信息,无法修改!', HttpStatus.BAD_REQUEST);
return user[0];
}
// DB 登陆检测
private async signinDetection(usernameSignInDto: UsernameSignInDto) {
const user = await this.mysqlService.db
.select({ userId: pacAuthUser.userId, pid: pacAuthUser.pid, password: pacAuthUser.password })
.from(pacAuthUser)
.where(and(isNull(pacAuthUser.deleteby), eq(pacAuthUser.username, usernameSignInDto.username)));
if (user.length == 0) throw new HttpException('该用户不存在!', HttpStatus.BAD_REQUEST);
// ! 判断是否超过最大登录次数
const number = await this.redisService.get('SIGNIN_NUM' + user[0].userId);
let maxNumberFieldsigninForRedis = (await this.redisService.get('CONFIG_MAX_NUMBER_FIELD_SIGNIN')) as any;
if (!maxNumberFieldsigninForRedis) {
maxNumberFieldsigninForRedis = this.config.get<number>('system.signin.maxNumberFieldsignin');
}
if (Number(number) >= maxNumberFieldsigninForRedis) {
throw new HttpException(`登录错误次数过多:${number}次!`, HttpStatus.BAD_REQUEST);
}
// ! 验证密码
const pass = cryptoPassword(usernameSignInDto.password, this.config.get<number>('system.passwordSalt'));
if (pass != user[0].password) {
let maxTimeFieldsignin = (await this.redisService.get('CONFIG_MAX_TIME_FIELD_SIGNIN')) as any;
if (!maxTimeFieldsignin) {
maxTimeFieldsignin = this.config.get<number>('system.signin.maxTimeFieldsignin');
}
await this.redisService.redis.SET('SIGNIN_NUM' + user[0].userId, Number(number) ? Number(number) + 1 : 1, {
PX: maxTimeFieldsignin,
});
throw new HttpException(`用户名或密码错误:${Number(number) + 1}次!`, HttpStatus.BAD_REQUEST);
}
// ! 判断是否是最大登录数
const clientList = await this.redisService.redis.keys('CLIENT-' + user[0].userId + '-*');
let maxSigninClient = (await this.redisService.get('CONFIG_MAX_SIGNIN_CLIENT')) as any;
if (!maxSigninClient) {
maxSigninClient = this.config.get<number>('system.signin.maxSigninClient');
}
console.log(maxSigninClient, clientList.length >= maxSigninClient)
if (clientList.length >= maxSigninClient) {
throw new HttpException('已登陆的客户端超过限制!', HttpStatus.BAD_REQUEST);
}
// ! 签发Token
const token = this.jwt.token({ username: usernameSignInDto.username, userId: user[0].userId, timestamp: new Date().getTime() });
const refreshToken = this.jwt.refreshToken({ username: usernameSignInDto.username, userId: user[0].userId, timestamp: new Date().getTime() });
// 将登陆的账户放进Redis
await this.redisService.redis.set('CLIENT-' + user[0].userId + '-' + MD5(refreshToken), 0, {
EX: this.config.get<number>('system.signin.refreshTokenTime'),
});
await this.redisService.redis.set('CLIENT-ONLINE-' + user[0].userId + '-' + MD5(token), 0, {
EX: this.config.get<number>('system.signin.refreshTokenTime'),
});
return {
token,
refreshToken,
};
}
}

@ -1,161 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import { ArrayMaxSize, ArrayMinSize, IsOptional, IsString, Length } from 'class-validator';
import FormatUsername from "@common/decorator/formatUsername/formatUsername";
export class CreateAuthUserDto {
@ApiProperty({
description: '账户父ID',
type: String,
example: '0',
required: false,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsString({ message: '部门关联属性应为字符串格式!' })
@IsOptional()
readonly pid?: string;
@ApiProperty({
description: '头像',
type: String,
example: '0',
required: false,
minLength: 1,
maxLength: 255,
})
@Trim()
@IsString({ message: '头像应为字符串格式!' })
@Length(1, 255, { message: '请将头像长度控制在1到255位之间!' })
@IsOptional()
readonly avatar?: string;
@ApiProperty({
description: '昵称',
type: String,
example: '研发经理',
required: true,
minLength: 1,
maxLength: 32,
})
@Trim()
@IsString({ message: '昵称应为字符串格式!' })
@Length(1, 32, { message: '昵称长度控制在1到32位之间!' })
readonly nickname: string;
@ApiProperty({
description: '岗位Id列表',
type: [String],
example: ['a'],
required: true,
minItems: 0,
maxItems: 100,
})
@IsString({ each: true, message: '岗位Id格式错误' })
@ArrayMinSize(0, { message: '至少需要选择一个岗位' })
@ArrayMaxSize(100, { message: '需要绑定的岗位超过限制' })
@Length(19, 19, { each: true, message: '岗位Id格式错误' })
@IsOptional()
readonly postList: string[];
@ApiProperty({
description: '角色Id列表',
type: [String],
example: ['a'],
required: true,
minItems: 0,
maxItems: 100,
})
@IsString({ each: true, message: '角色Id格式错误' })
@ArrayMinSize(0, { message: '至少需要选择一个角色' })
@ArrayMaxSize(100, { message: '需要绑定的角色超过限制' })
@Length(19, 19, { each: true, message: '角色Id格式错误' })
@IsOptional()
readonly roleList?: string[];
@ApiProperty({
description: '部门Id列表',
type: [String],
example: ['a'],
required: true,
minItems: 0,
maxItems: 100,
})
@IsString({ each: true, message: '部门Id格式错误' })
@ArrayMinSize(0, { message: '至少需要选择一个部门' })
@ArrayMaxSize(100, { message: '需要绑定的部门超过限制' })
@Length(19, 19, { each: true, message: '部门Id格式错误' })
@IsOptional()
readonly deptList: string[];
@ApiProperty({
description: '账户描述',
type: String,
example: '0',
required: false,
minLength: 1,
maxLength: 255,
})
@Trim()
@IsString({ message: '账户描述应为字符串格式!' })
@Length(1, 255, { message: '请将账户描述长度控制在1到255位之间!' })
@IsOptional()
readonly userDesc: string;
@ApiProperty({
description: '邮箱',
type: String,
example: '0',
required: false,
minLength: 5,
maxLength: 255,
})
@Trim()
@IsString({ message: '邮箱应为字符串格式!' })
@Length(5, 255, { message: '请将邮箱长度控制在5到255位之间!' })
@IsOptional()
readonly userEmail: string;
@ApiProperty({
description: '用户名',
type: String,
example: '0',
required: false,
minLength: 4,
maxLength: 128,
})
@Trim()
@FormatUsername()
@IsString({ message: '用户名应为字符串格式!' })
@Length(4, 128, { message: '请将用户名长度控制在4到128位之间!' })
readonly username: string;
@ApiProperty({
description: '手机号',
type: String,
example: '0',
required: false,
minLength: 11,
maxLength: 11,
})
@Trim()
@IsString({ message: '手机号格式不正确!' })
@Length(11, 11, { message: '手机号格式不正确!' })
@IsOptional()
readonly userPhone: string;
@ApiProperty({
description: '账户类型,来自于字典',
type: String,
example: '0',
required: false,
minLength: 19,
maxLength: 19,
})
@Trim()
@IsString({ message: '账户类型格式不正确!' })
@Length(19, 19, { message: '账户类型格式不正确!' })
@IsOptional()
readonly userType: string;
}

@ -1,175 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: get-auth-role.dto.ts -
// | @创建时间: 2024-06-25 17:22
// | @更新时间: 2024-06-25 17:22
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { GetDto } from '@dto/get.dto';
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import { ArrayMaxSize, ArrayMinSize, IsEnum, IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';
import { BooleanEnum } from '@utils/boolean.enum';
import Int from '@common/decorator/int/int.descrator';
export class GetPacAuthUserAllDto extends GetDto {
@ApiProperty({
description: '账户信息',
type: String,
example: '管理员',
required: false,
minLength: 1,
maxLength: 128,
})
@Trim()
@IsString({ message: '账户信息应为字符串格式!' })
@Length(0, 128, { message: '请将账户信息长度控制在1到128位之间!' })
@IsOptional()
readonly userInfo?: string;
@ApiProperty({
description: '账户类型,来自于字典',
type: String,
example: '0',
required: false,
minLength: 19,
maxLength: 19,
})
@Trim()
@IsString({ message: '账户类型格式不正确!' })
@Length(19, 19, { message: '账户类型格式不正确!' })
@IsOptional()
readonly userType: string;
@ApiProperty({
description: '是否存在子账户',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'haveChildren参数格式错误' })
@IsOptional()
readonly haveChildren: BooleanEnum;
@ApiProperty({
description: '账户状态',
type: Number,
example: 0,
required: false,
minimum: -100,
maximum: 100,
})
@Trim()
@Int()
@IsInt({
message: '账户状态必须是整数!',
})
@Min(-100, {
message: '账户状态需要大于-100!',
})
@Max(100, {
message: '账户状态不能超过100',
})
@IsOptional()
readonly status?: string;
@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;
@ApiProperty({
description: '角色Id',
type: String,
example: '0',
required: false,
minLength: 19,
maxLength: 19,
})
@Trim()
@IsString({ message: '角色Id类型格式不正确!' })
@Length(19, 19, { message: '角色Id类型格式不正确!' })
@IsOptional()
readonly roleId?: string;
@ApiProperty({
description: '部门Id',
type: String,
example: '0',
required: false,
minLength: 19,
maxLength: 19,
})
@Trim()
@IsString({ message: '部门Id类型格式不正确!' })
@Length(19, 19, { message: '部门Id类型格式不正确!' })
@IsOptional()
readonly deptId?: string;
@ApiProperty({
description: '岗位Id',
type: String,
example: '0',
required: false,
minLength: 19,
maxLength: 19,
})
@Trim()
@IsString({ message: '岗位Id类型格式不正确!' })
@Length(19, 19, { message: '岗位Id类型格式不正确!' })
@IsOptional()
readonly postId?: string;
@ApiProperty({
description: '查没有角色的账户',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'noRole参数格式错误' })
@IsOptional()
readonly noRole: BooleanEnum;
@ApiProperty({
description: '查没有部门的账户',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'noDept参数格式错误' })
@IsOptional()
readonly noDept: BooleanEnum;
@ApiProperty({
description: '查没有岗位的账户',
type: BooleanEnum,
enum: BooleanEnum,
example: 0,
required: false,
})
@Trim()
@IsEnum(BooleanEnum, { message: 'noPost参数格式错误' })
@IsOptional()
readonly noPost: BooleanEnum;
}

@ -1,48 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: password.dto.ts -
// | @创建时间: 2024-06-29 22:01
// | @更新时间: 2024-06-29 22:01
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import FormatUsername from '@common/decorator/formatUsername/formatUsername';
import { IsString, Length } from 'class-validator';
export class PasswordDto {
@ApiProperty({
description: '新密码',
type: String,
example: '0',
required: false,
minLength: 8,
maxLength: 128,
})
@Trim()
@FormatUsername()
@IsString({ message: '新密码应为字符串格式!' })
@Length(8, 128, { message: '请将新密码长度控制在8到128位之间!' })
readonly newPassword: string;
@ApiProperty({
description: '旧密码',
type: String,
example: '0',
required: false,
minLength: 6,
maxLength: 128,
})
@Trim()
@FormatUsername()
@IsString({ message: '旧密码应为字符串格式!' })
@Length(6, 128, { message: '请将旧密码长度控制在6到128位之间!' })
readonly oldPassword: string;
}

@ -1,46 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: signin.dto.ts -
// | @创建时间: 2024-06-29 22:30
// | @更新时间: 2024-06-29 22:30
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { ApiProperty } from '@nestjs/swagger';
import Trim from '@common/decorator/trim/trim.decorator';
import FormatUsername from '@common/decorator/formatUsername/formatUsername';
import { IsString, Length } from 'class-validator';
export class UsernameSignInDto {
@ApiProperty({
description: '用户名',
type: String,
example: '0',
required: false,
minLength: 4,
maxLength: 128,
})
@Trim()
@FormatUsername()
@IsString({ message: '用户名应为字符串格式!' })
@Length(4, 128, { message: '请将用户名长度控制在4到128位之间!' })
readonly username: string;
@ApiProperty({
description: '新密码',
type: String,
example: '0',
required: false,
minLength: 6,
maxLength: 128,
})
@Trim()
@FormatUsername()
@IsString({ message: '新密码应为字符串格式!' })
@Length(6, 128, { message: '请将新密码长度控制在6到128位之间!' })
readonly password: string;
}

@ -1,25 +0,0 @@
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { CreateAuthUserDto } from './create-auth-user.dto';
import { IsInt, IsOptional, Max, Min } from 'class-validator';
export class UpdateAuthUserDto extends PartialType(CreateAuthUserDto) {
@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;
}

@ -1,31 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: updateLink.dto.ts -
// | @创建时间: 2024-06-29 22:07
// | @更新时间: 2024-06-29 22:07
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { ApiProperty } from '@nestjs/swagger';
import { ArrayMaxSize, ArrayMinSize, IsString, Length } from 'class-validator';
export class UpdateLinkDto {
@ApiProperty({
description: 'Id列表',
type: [String],
example: ['a'],
required: true,
minItems: 0,
maxItems: 100,
})
@IsString({ each: true, message: 'Id格式错误' })
@ArrayMinSize(0, { message: '至少需要选择一个id' })
@ArrayMaxSize(100, { message: '需要绑定的id超过限制' })
@Length(19, 19, { each: true, message: 'Id格式错误' })
readonly list: string[];
}

@ -172,7 +172,7 @@ export class CoreDictService {
const checkRepeat = await this.getDictDataForDictKey(updateCoreDictDto.dictKey);
// ? 判断是否存在重名的字典,但是不包括自己
if (checkRepeat.length > 0 && checkRepeat[0].id != dictId) {
if (checkRepeat.length > 0 && checkRepeat[0].dictId != dictId) {
throw new HttpException('服务繁忙,字典标识重复!', HttpStatus.CONFLICT);
}
@ -232,12 +232,7 @@ export class CoreDictService {
// DB 根据serviceKey查找服务数据 可用于查重
private async getDictDataForDictKey(dictKey: string) {
return await this.mysqlService.db
.select({
id: pacCoreDict.dictId,
})
.from(pacCoreDict)
.where(eq(pacCoreDict.dictKey, dictKey));
return await this.mysqlService.db.select().from(pacCoreDict).where(eq(pacCoreDict.dictKey, dictKey));
}
// DB 写入Dict数据

@ -174,7 +174,7 @@ export class CoreEnvService {
const checkRepeat = await this.getEnvForEnvKey(updateCoreEnvDto.envKey);
// ? 判断是否存在重名的环境变量,但是不包括自己
if (checkRepeat.length > 0 && checkRepeat[0].id != envId) {
if (checkRepeat.length > 0 && checkRepeat[0].envId != envId) {
throw new HttpException('环境变量标识重复!', HttpStatus.CONFLICT);
}
@ -235,12 +235,7 @@ export class CoreEnvService {
// DB 通过EnvKey获取env信息,主要用于查重
private getEnvForEnvKey(envKey: string) {
return this.mysqlService.db
.select({
id: pacCoreEnv.envId,
})
.from(pacCoreEnv)
.where(eq(pacCoreEnv.envKey, envKey));
return this.mysqlService.db.select().from(pacCoreEnv).where(eq(pacCoreEnv.envKey, envKey));
}
// DB 添加Env数据

@ -1,26 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: formatUsername.ts -
// | @创建时间: 2024-06-29 16:20
// | @更新时间: 2024-06-29 16:20
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { Transform } from 'class-transformer';
export default function FormatUsername() {
return Transform(({ value }) => {
if (typeof value === 'string') {
if (value.trim() == '') {
return undefined;
}
return value.replace(/\s/g, '').toLowerCase();
}
return value;
});
}

@ -1,28 +0,0 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { LoggerService } from '@service/logger/logger.service';
import * as jwt from 'jsonwebtoken';
@Injectable()
export class JwtService {
constructor(
private readonly configService: ConfigService,
private readonly logger: LoggerService,
) {}
public token(payload: any): Promise<any> {
return jwt.sign(payload, this.configService.get<string>('system.signin.secretKey'), {
expiresIn: this.configService.get<number>('system.signin.tokenTime'),
});
}
public refreshToken(payload: any) {
return jwt.sign(payload, this.configService.get<string>('system.signin.secretKey'), {
expiresIn: this.configService.get<number>('system.signin.refreshTokenTime'),
});
}
public verify(token: any) {
jwt.verify(token, this.configService.get<string>('system.signin.secretKey'));
}
}

@ -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,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();
});
});

@ -5,7 +5,7 @@ import { LoggerService } from '@service/logger/logger.service';
@Injectable()
export class RedisService {
public readonly redis: RedisClientType;
private readonly redis: RedisClientType;
constructor(
private readonly configService: ConfigService,
private readonly logger: LoggerService,

@ -14,8 +14,6 @@
export default () => ({
allEnv: process.env,
env: process.env['NODE_ENV'] || 'development',
// 最高管理员ID
masterId: 0,
main: {
host: '0.0.0.0' || 'localhost',
@ -25,7 +23,6 @@ export default () => ({
version: process.env.npm_package_version || '0.0.1',
},
logger: {
// 日志记录等级
level: process.env['NODE_ENV'] === undefined || 'development' ? 'trace' : 'warning',
},
swagger: {
@ -48,31 +45,4 @@ export default () => ({
password: 'Hxl1314521',
},
},
system: {
// 默认密码
defaultPassword: '123456',
// 密码盐
passwordSalt: '6dea8337e824f71ab960ea896c56f9ea',
signin: {
// 最大错误登录次数
maxNumberFieldsignin: 5,
// 登陆错误等待时间 10min
maxTimeFieldsignin: 1000 * 60 * 10,
// 最大登录客户端数
maxSigninClient: 5,
// 客户端在线时间 7天 ========jwt最小单位是s
refreshTokenTime: 60 * 60 * 24 * 7,
// 客户端连续在线时间 10min
tokenTime: 60 * 10,
// token盐
secretKey: '326dea8337e8xsxa24f71ab960ea',
},
},
});

@ -38,11 +38,12 @@ export class RoleLinkUserDto {
})
@IsString({ each: true, message: '账户Id格式错误' })
@ArrayMinSize(1, { message: '至少需要选择一个需要绑定的账户' })
@ArrayMaxSize(200, { message: '需要绑定的账户超过限制' })
@ArrayMaxSize(200, { message: '需要绑定的账户账户超过限制' })
@Length(19, 19, { each: true, message: '账户Id格式错误' })
readonly userIdList?: string[];
}
export class DeptLinkUserDto {
@ApiProperty({
description: '角色ID',

@ -3,7 +3,6 @@ import { sql } from 'drizzle-orm';
import { bigintString } from '@entities/customType';
const bigint = bigintString;
export const pacAuthDept = mysqlTable(
'pac_auth_dept',
{
@ -122,18 +121,17 @@ export const pacAuthPost = mysqlTable(
'pac_auth_post',
{
index: int('index').autoincrement().notNull(),
postId: bigint('post_id', { mode: 'number' }).notNull(),
postId: int('post_id').notNull(),
postKey: varchar('post_key', { length: 255 }).notNull(),
postName: varchar('post_name', { length: 255 }),
postType: bigint('post_type', { mode: 'number' }).notNull(),
postDesc: varchar('post_desc', { length: 255 }),
orderNum: int('order_num').default(0).notNull(),
status: int('status').default(0).notNull(),
createby: bigint('createby', { mode: 'number' }).notNull(),
createby: int('createby').notNull(),
createtime: datetime('createtime', { mode: 'string' }).notNull(),
updateby: bigint('updateby', { mode: 'number' }),
updateby: int('updateby'),
updatetime: datetime('updatetime', { mode: 'string' }),
deleteby: bigint('deleteby', { mode: 'number' }),
deleteby: int('deleteby'),
deletetime: datetime('deletetime', { mode: 'string' }),
},
(table) => {

@ -1,17 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: MD5.ts -
// | @创建时间: 2024-06-29 23:50
// | @更新时间: 2024-06-29 23:50
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as crypto from 'node:crypto';
export default function MD5(data) {
return crypto.createHash('md5').update(data).digest('hex').toUpperCase();
}

@ -1,20 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: cryptoPassword.ts -
// | @创建时间: 2024-06-29 15:44
// | @更新时间: 2024-06-29 15:44
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as crypto from 'node:crypto';
export default function cryptoPassword(str: string, salt) {
return crypto
.createHash('sha256')
.update(salt + str)
.digest('hex');
}

@ -1,16 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-hotok】
// | @E-mail: x71291@outlook.com
// | @所在项目: pac-auth
// | @文件描述: 密码随机盐.js -
// | @创建时间: 2024-06-29 15:46
// | @更新时间: 2024-06-29 15:46
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
const crypto = require('crypto');
const salt = crypto.randomBytes(16).toString('hex');
console.log('A', salt);
Loading…
Cancel
Save