main
commit
03b043e883
@ -0,0 +1,25 @@ |
|||||||
|
module.exports = { |
||||||
|
parser: '@typescript-eslint/parser', |
||||||
|
parserOptions: { |
||||||
|
project: 'tsconfig.json', |
||||||
|
tsconfigRootDir: __dirname, |
||||||
|
sourceType: 'module', |
||||||
|
}, |
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'], |
||||||
|
extends: [ |
||||||
|
'plugin:@typescript-eslint/recommended', |
||||||
|
'plugin:prettier/recommended', |
||||||
|
], |
||||||
|
root: true, |
||||||
|
env: { |
||||||
|
node: true, |
||||||
|
jest: true, |
||||||
|
}, |
||||||
|
ignorePatterns: ['.eslintrc.js'], |
||||||
|
rules: { |
||||||
|
'@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', |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,38 @@ |
|||||||
|
# compiled output |
||||||
|
/dist |
||||||
|
/node_modules |
||||||
|
/Log/* |
||||||
|
/file/* |
||||||
|
|
||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
pnpm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
lerna-debug.log* |
||||||
|
pnpm-lock.yaml |
||||||
|
|
||||||
|
# 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 |
@ -0,0 +1,5 @@ |
|||||||
|
{ |
||||||
|
"singleQuote": true, |
||||||
|
"trailingComma": "all", |
||||||
|
"tabWidth": 4 |
||||||
|
} |
@ -0,0 +1,73 @@ |
|||||||
|
<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). |
@ -0,0 +1,59 @@ |
|||||||
|
export default { |
||||||
|
// 主服务
|
||||||
|
master: { |
||||||
|
systemName: '心曲Tune', |
||||||
|
host: '0.0.0.0', |
||||||
|
port: '3000', |
||||||
|
}, |
||||||
|
swagger: { |
||||||
|
enable: true, |
||||||
|
}, |
||||||
|
dev: { |
||||||
|
// 开发模式拦截器,用以提供接口请求时间和响应大小
|
||||||
|
devInterceptor: true, |
||||||
|
}, |
||||||
|
log: { |
||||||
|
level: 'all', |
||||||
|
}, |
||||||
|
databases: { |
||||||
|
starLight: { |
||||||
|
host: 'localhost', |
||||||
|
port: 3306, |
||||||
|
username: 'root', |
||||||
|
password: 'root', |
||||||
|
database: 'rgv-sale-system', |
||||||
|
}, |
||||||
|
}, |
||||||
|
redis: { |
||||||
|
starLight: { |
||||||
|
host: 'localhost', |
||||||
|
port: 6379, |
||||||
|
username: 'default', |
||||||
|
password: 'default', |
||||||
|
dbNumber: 3, |
||||||
|
}, |
||||||
|
}, |
||||||
|
email: { |
||||||
|
host: 'smtp.qq.com', |
||||||
|
user: 'togy.gc@qq.com', |
||||||
|
pass: 'miidbsjeuqcxddbf', |
||||||
|
}, |
||||||
|
// 加密
|
||||||
|
encryption: { |
||||||
|
salt: 'время,вперёд!', // 盐
|
||||||
|
secretKey: 'быть всегда готовым!', // 密钥
|
||||||
|
}, |
||||||
|
token: { |
||||||
|
timeout: 1000 * 60 * 60 * 24 * 7, |
||||||
|
secretKey: 'Missing You!(John waite)', |
||||||
|
}, |
||||||
|
// 登录
|
||||||
|
signIn: { |
||||||
|
// @ 密码验证次数超限后的冷却时长
|
||||||
|
signInErrorTimeout: 60 * 10, |
||||||
|
// @ 允许密码输错几次
|
||||||
|
signInErrorNumber: 5, |
||||||
|
// @ 允许同时在线数
|
||||||
|
onLineNumber: 6, |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,12 @@ |
|||||||
|
import developmentConfig from './development'; |
||||||
|
import testConfig from './test'; |
||||||
|
import productionConfig from './production'; |
||||||
|
|
||||||
|
const configs = { |
||||||
|
development: developmentConfig, |
||||||
|
test: testConfig, |
||||||
|
production: productionConfig, |
||||||
|
}; |
||||||
|
const env = process.env.NODE_ENV || 'development'; |
||||||
|
|
||||||
|
export default () => configs[env]; |
@ -0,0 +1,57 @@ |
|||||||
|
export default { |
||||||
|
// 主服务
|
||||||
|
master: { |
||||||
|
systemName: '心曲Tune', |
||||||
|
host: '127.0.0.1', |
||||||
|
port: '3000', |
||||||
|
}, |
||||||
|
swagger: { |
||||||
|
enable: false, |
||||||
|
}, |
||||||
|
dev: { |
||||||
|
// 开发模式拦截器,用以提供接口请求时间和响应大小
|
||||||
|
devInterceptor: false, |
||||||
|
}, |
||||||
|
log: { |
||||||
|
level: 'info', |
||||||
|
}, |
||||||
|
databases: { |
||||||
|
starLight: { |
||||||
|
host: 'localhost', |
||||||
|
port: 3306, |
||||||
|
username: 'root', |
||||||
|
password: 'Hxl1314521', |
||||||
|
database: 'Starlight', |
||||||
|
}, |
||||||
|
}, |
||||||
|
redis: { |
||||||
|
starLight: { |
||||||
|
host: 'localhost', |
||||||
|
port: 6379, |
||||||
|
username: 'default', |
||||||
|
password: 'default', |
||||||
|
dbNumber: 3, |
||||||
|
}, |
||||||
|
}, |
||||||
|
email: { |
||||||
|
host: 'smtp.qq.com', |
||||||
|
user: 'togy.gc@qq.com', |
||||||
|
pass: 'miidbsjeuqcxddbf', |
||||||
|
}, |
||||||
|
// 加密
|
||||||
|
encryption: { |
||||||
|
salt: 'время,вперёд!', // 盐
|
||||||
|
secretKey: 'быть всегда готовым!', // 密钥
|
||||||
|
}, |
||||||
|
token: { |
||||||
|
timeout: 1000 * 60 * 60 * 24 * 7, |
||||||
|
secretKey: 'Missing You!(John waite)', |
||||||
|
}, |
||||||
|
// 登录
|
||||||
|
signIn: { |
||||||
|
// @ 密码验证次数超限后的冷却时长
|
||||||
|
signInErrorTimeout: 60 * 10, |
||||||
|
// @ 允许密码输错几次
|
||||||
|
signInErrorNumber: 5, |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,10 @@ |
|||||||
|
export default { |
||||||
|
// 主服务
|
||||||
|
master: { |
||||||
|
host: '0.0.0.0', |
||||||
|
port: '3000', |
||||||
|
}, |
||||||
|
swagger: { |
||||||
|
enable: true, |
||||||
|
}, |
||||||
|
}; |
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,8 @@ |
|||||||
|
{ |
||||||
|
"$schema": "https://json.schemastore.org/nest-cli", |
||||||
|
"collection": "@nestjs/schematics", |
||||||
|
"sourceRoot": "src", |
||||||
|
"compilerOptions": { |
||||||
|
"deleteOutDir": true |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
{ |
||||||
|
"name": "tune", |
||||||
|
"version": "0.0.1", |
||||||
|
"description": "", |
||||||
|
"author": "", |
||||||
|
"private": true, |
||||||
|
"license": "UNLICENSED", |
||||||
|
"scripts": { |
||||||
|
"build": "nest build", |
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", |
||||||
|
"start": "cross-env NODE_ENV=production nest start", |
||||||
|
"start:dev": "cross-env NODE_ENV=development nest start --watch", |
||||||
|
"start:debug": "cross-env NODE_ENV=development 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" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@fastify/multipart": "^7.4.2", |
||||||
|
"@fastify/static": "^6.9.0", |
||||||
|
"@nestjs/common": "^9.0.0", |
||||||
|
"@nestjs/config": "^2.3.1", |
||||||
|
"@nestjs/core": "^9.0.0", |
||||||
|
"@nestjs/mapped-types": "*", |
||||||
|
"@nestjs/platform-fastify": "^9.3.9", |
||||||
|
"@nestjs/swagger": "^6.2.1", |
||||||
|
"class-transformer": "^0.5.1", |
||||||
|
"class-validator": "^0.14.0", |
||||||
|
"fastify": "^4.19.2", |
||||||
|
"fastify-swagger": "^5.2.0", |
||||||
|
"jsonwebtoken": "^9.0.0", |
||||||
|
"log4js": "^6.9.1", |
||||||
|
"mysql2": "^3.2.0", |
||||||
|
"nodemailer": "^6.9.1", |
||||||
|
"redis": "^4.6.5", |
||||||
|
"reflect-metadata": "^0.1.13", |
||||||
|
"rxjs": "^7.2.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@nestjs/cli": "^9.0.0", |
||||||
|
"@nestjs/schematics": "^9.0.0", |
||||||
|
"@nestjs/testing": "^9.0.0", |
||||||
|
"@types/express": "^4.17.13", |
||||||
|
"@types/jest": "29.2.4", |
||||||
|
"@types/node": "18.11.18", |
||||||
|
"@types/supertest": "^2.0.11", |
||||||
|
"@typescript-eslint/eslint-plugin": "^5.0.0", |
||||||
|
"@typescript-eslint/parser": "^5.0.0", |
||||||
|
"cross-env": "^7.0.3", |
||||||
|
"eslint": "^8.0.1", |
||||||
|
"eslint-config-prettier": "^8.3.0", |
||||||
|
"eslint-plugin-prettier": "^4.0.0", |
||||||
|
"jest": "29.3.1", |
||||||
|
"prettier": "^2.3.2", |
||||||
|
"source-map-support": "^0.5.20", |
||||||
|
"supertest": "^6.1.3", |
||||||
|
"ts-jest": "29.0.3", |
||||||
|
"ts-loader": "^9.2.3", |
||||||
|
"ts-node": "^10.0.0", |
||||||
|
"tsconfig-paths": "4.1.1", |
||||||
|
"typescript": "^4.7.4" |
||||||
|
}, |
||||||
|
"jest": { |
||||||
|
"moduleFileExtensions": [ |
||||||
|
"js", |
||||||
|
"json", |
||||||
|
"ts" |
||||||
|
], |
||||||
|
"rootDir": "src", |
||||||
|
"testRegex": ".*\\.spec\\.ts$", |
||||||
|
"transform": { |
||||||
|
"^.+\\.(t|j)s$": "ts-jest" |
||||||
|
}, |
||||||
|
"collectCoverageFrom": [ |
||||||
|
"**/*.(t|j)s" |
||||||
|
], |
||||||
|
"coverageDirectory": "../coverage", |
||||||
|
"testEnvironment": "node" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
import { |
||||||
|
createParamDecorator, |
||||||
|
ExecutionContext, HttpException, |
||||||
|
SetMetadata, |
||||||
|
} from '@nestjs/common'; |
||||||
|
|
||||||
|
export const UserInfoDecorator = (...args: string[]) => |
||||||
|
SetMetadata('userinfoDecorator', args); |
||||||
|
|
||||||
|
// nest g d [name]
|
||||||
|
// 自定义装饰器
|
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: userinfoDto
|
||||||
|
// ? 函数描述: 限制userinfo数据格式
|
||||||
|
// ? ?
|
||||||
|
export class userinfoDto { |
||||||
|
userId: string; |
||||||
|
token: string; |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: getUserinfo
|
||||||
|
// ? 函数描述: 获取token用户信息
|
||||||
|
// ? ?
|
||||||
|
export const getUserinfo = createParamDecorator( |
||||||
|
(data: string, ctx: ExecutionContext) => { |
||||||
|
const req = ctx.switchToHttp().getRequest(); |
||||||
|
if (!req.headers.userid) { |
||||||
|
throw new HttpException('缺少Headers参数userid', 400); |
||||||
|
} else if (!req.headers.token) { |
||||||
|
throw new HttpException('缺少Headers参数token', 400); |
||||||
|
} |
||||||
|
return { |
||||||
|
userId: req.headers.userid, |
||||||
|
token: req.headers.token, |
||||||
|
} as userinfoDto; |
||||||
|
}, |
||||||
|
); |
@ -0,0 +1,32 @@ |
|||||||
|
import { |
||||||
|
ArgumentsHost, |
||||||
|
Catch, |
||||||
|
ExceptionFilter, |
||||||
|
HttpException, |
||||||
|
} from '@nestjs/common'; |
||||||
|
import a from '@nestjs/platform-fastify' |
||||||
|
import { Request, Response } from 'express'; |
||||||
|
|
||||||
|
@Catch() |
||||||
|
export class GexceptionsfilterFilter<T> implements ExceptionFilter { |
||||||
|
catch(exception: HttpException, host: ArgumentsHost) { |
||||||
|
const ctx = host.switchToHttp(); |
||||||
|
const response = ctx.getResponse<Response>(); |
||||||
|
const request = ctx.getRequest<Request>(); |
||||||
|
console.log('拦截外的报错',exception); |
||||||
|
const status = exception.getStatus(); |
||||||
|
const responseBody = exception.getResponse() |
||||||
|
if(typeof responseBody === 'object'){ |
||||||
|
response |
||||||
|
.status(status) |
||||||
|
.send(responseBody); |
||||||
|
}else |
||||||
|
response |
||||||
|
.status(status) |
||||||
|
.send({ |
||||||
|
statusCode: status, |
||||||
|
message: exception.getResponse(), |
||||||
|
error:'Bad Request' |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
import { |
||||||
|
CallHandler, |
||||||
|
ExecutionContext, |
||||||
|
Injectable, |
||||||
|
NestInterceptor, |
||||||
|
} from '@nestjs/common'; |
||||||
|
import { Observable } from 'rxjs'; |
||||||
|
import { GloggerService } from '../Gservice/GLOGGER/glogger.service'; |
||||||
|
import { tap } from 'rxjs/operators'; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class GdevinterceptorInterceptor implements NestInterceptor { |
||||||
|
logger: GloggerService; |
||||||
|
constructor() { |
||||||
|
this.logger = new GloggerService(); |
||||||
|
this.logger.setContext(`[DevlInterceptor]`); |
||||||
|
} |
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { |
||||||
|
const request = context.switchToHttp().getRequest(); |
||||||
|
this.logger.warn(request.method, request.url, '<=='); |
||||||
|
|
||||||
|
const now = Date.now(); |
||||||
|
return next.handle().pipe( |
||||||
|
tap((data) => { |
||||||
|
this.logger.warn( |
||||||
|
request.method, |
||||||
|
request.url, |
||||||
|
'==>', |
||||||
|
`${Date.now() - now}ms`, |
||||||
|
`${data ? Buffer.from(data).length : '?'}b`, |
||||||
|
); |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
import { |
||||||
|
CallHandler, |
||||||
|
ExecutionContext, |
||||||
|
Injectable, |
||||||
|
NestInterceptor, |
||||||
|
} from '@nestjs/common'; |
||||||
|
import { Observable } from 'rxjs'; |
||||||
|
import { GloggerService } from '../Gservice/GLOGGER/glogger.service'; |
||||||
|
import { map } from 'rxjs/operators'; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class GresponseinterceptorInterceptor implements NestInterceptor { |
||||||
|
logger: GloggerService; |
||||||
|
constructor() { |
||||||
|
this.logger = new GloggerService(); |
||||||
|
this.logger.setContext(`[Responseinterceptor]`); |
||||||
|
} |
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { |
||||||
|
const response = context.switchToHttp().getResponse(); |
||||||
|
return next.handle().pipe( |
||||||
|
map((data) => { |
||||||
|
if(!data){ |
||||||
|
return null |
||||||
|
} |
||||||
|
// this.logger.debug(response.statusCode);
|
||||||
|
return JSON.stringify({ |
||||||
|
data: |
||||||
|
typeof data == 'string' |
||||||
|
? data |
||||||
|
: data.data |
||||||
|
? data.data |
||||||
|
: data, |
||||||
|
statusCode: response.statusCode, |
||||||
|
success: data |
||||||
|
? data.success === false |
||||||
|
? false |
||||||
|
: true |
||||||
|
: true, |
||||||
|
message: |
||||||
|
typeof data == 'string' |
||||||
|
? 'ok' |
||||||
|
: data.message |
||||||
|
? data.message |
||||||
|
: 'ok', |
||||||
|
}); |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
import { Global, Module } from '@nestjs/common'; |
||||||
|
import { GdatabaseService } from './gdatabase.service'; |
||||||
|
@Global() |
||||||
|
@Module({ |
||||||
|
providers: [GdatabaseService], |
||||||
|
exports: [GdatabaseService], |
||||||
|
}) |
||||||
|
export class GdatabaseModule {} |
@ -0,0 +1,29 @@ |
|||||||
|
import { Injectable } from '@nestjs/common'; |
||||||
|
import * as mysql from 'mysql2/promise'; |
||||||
|
import config from '@CFG/index'; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class GdatabaseService { |
||||||
|
public DB; |
||||||
|
constructor() { |
||||||
|
this.start(); |
||||||
|
} |
||||||
|
private async start() { |
||||||
|
const DBConfig = config().databases.starLight; |
||||||
|
const DB = mysql.createPool({ |
||||||
|
host: DBConfig.host, |
||||||
|
port: DBConfig.port, |
||||||
|
user: DBConfig.username, |
||||||
|
password: DBConfig.password, |
||||||
|
database: DBConfig.database, |
||||||
|
connectionLimit: 20, // 用于指定连接池中最大的链接数,默认属性值为10.
|
||||||
|
multipleStatements: true, //是否允许执行多条sql语句,默认值为false
|
||||||
|
waitForConnections: true, // 超过最大连接时排队
|
||||||
|
queueLimit: 0, // 排队最大数量(0 代表不做限制)
|
||||||
|
maxIdle: 20, // 最大空闲连接数
|
||||||
|
idleTimeout: 60000, // 空闲连接超时,以毫秒为单位,默认值60000
|
||||||
|
}); |
||||||
|
this.DB = DB; |
||||||
|
return DB; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
import { Global, Module } from '@nestjs/common'; |
||||||
|
import { GemailService } from './gemail.service'; |
||||||
|
@Global() |
||||||
|
@Module({ |
||||||
|
providers: [GemailService], |
||||||
|
exports: [GemailService], |
||||||
|
}) |
||||||
|
export class GemailModule {} |
@ -0,0 +1,317 @@ |
|||||||
|
import { Injectable } from '@nestjs/common'; |
||||||
|
import * as nodemailer from 'nodemailer'; |
||||||
|
import config from '@CFG/index'; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class GemailService { |
||||||
|
// @ 邮件服务
|
||||||
|
private transporter; |
||||||
|
// @ 邮箱配置
|
||||||
|
private EMAILCONFIG; |
||||||
|
// @ 系统名称,作用于邮件主题。
|
||||||
|
private sysName: string; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
this.start(); |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: start
|
||||||
|
// ? 函数描述: 生成邮件服务
|
||||||
|
// ? ?
|
||||||
|
private start() { |
||||||
|
const EMAILCONFIG = config().email; |
||||||
|
const transporter = nodemailer.createTransport({ |
||||||
|
//node_modules/nodemailer/lib/well-known/services.json 查看相关的配置,如果使用qq邮箱,就查看qq邮箱的相关配置
|
||||||
|
host: EMAILCONFIG.host, |
||||||
|
// secureConnection:true,
|
||||||
|
service: 'qq', //类型qq邮箱
|
||||||
|
// port: 465,
|
||||||
|
secure: true, // true for 465, false for other ports
|
||||||
|
auth: { |
||||||
|
user: EMAILCONFIG.user, // 发送方的邮箱
|
||||||
|
pass: EMAILCONFIG.pass, // smtp 的授权码
|
||||||
|
}, |
||||||
|
//pass 不是邮箱账户的密码而是stmp的授权码(必须是相应邮箱的stmp授权码)
|
||||||
|
//邮箱---设置--账户--POP3/SMTP服务---开启---获取stmp授权码
|
||||||
|
}); |
||||||
|
this.transporter = transporter; |
||||||
|
this.EMAILCONFIG = EMAILCONFIG; |
||||||
|
this.sysName = config().master.systemName; |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: senMail(邮件内容, 收件人邮箱)
|
||||||
|
// ? 函数描述: 发送邮件主函数
|
||||||
|
// ? ?
|
||||||
|
private senMail(html, mail) { |
||||||
|
return new Promise((res, rej) => { |
||||||
|
const mailOptions = { |
||||||
|
from: '"TOGY | 心曲Tune" <togy.gc@qq.com>', // 发送方
|
||||||
|
to: mail, //接收者邮箱,多个邮箱用逗号间隔
|
||||||
|
subject: `${this.sysName}`, // 标题
|
||||||
|
text: 'Hello world?', // 文本内容
|
||||||
|
html, |
||||||
|
}; |
||||||
|
this.transporter.sendMail(mailOptions, (error, info) => { |
||||||
|
if (error) { |
||||||
|
rej([error, info]); |
||||||
|
} else { |
||||||
|
res(info); //因为是异步 所有需要回调函数通知成功结果
|
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: sendTestEmail
|
||||||
|
// ? 函数描述: 给指定邮箱发送测试验证码
|
||||||
|
// ? ?
|
||||||
|
public sendTestEmail(email) { |
||||||
|
return new Promise(async (res, rej) => { |
||||||
|
const test = ` |
||||||
|
<style> |
||||||
|
.emailBody{ |
||||||
|
position: relative; |
||||||
|
min-height: 400px; |
||||||
|
min-width: 300px; |
||||||
|
height: 100vh; |
||||||
|
width:90vw; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
margin: 0 auto; |
||||||
|
padding: 0; |
||||||
|
overflow: overlay; |
||||||
|
} |
||||||
|
.emailMain{ |
||||||
|
position:relative; |
||||||
|
|
||||||
|
} |
||||||
|
.emailTitle{ |
||||||
|
line-height: 3em; |
||||||
|
font-size: 24px; |
||||||
|
font-weight: bold; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.emailTitleLetter{ |
||||||
|
position:relative; |
||||||
|
font-size: 16px; |
||||||
|
line-height: 3em; |
||||||
|
} |
||||||
|
.emailCode{ |
||||||
|
position:relative; |
||||||
|
height: 100px; |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
.emailCode>div{ |
||||||
|
position:relative; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 1em; |
||||||
|
font-size: 30px; |
||||||
|
text-align: center; |
||||||
|
color: #fefefe; |
||||||
|
background-color: #222; |
||||||
|
} |
||||||
|
.emailDescription{ |
||||||
|
position:relative; |
||||||
|
text-align: center; |
||||||
|
padding: 20px 0 ; |
||||||
|
line-height: 1.8em; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
||||||
|
<div class="emailBody"> |
||||||
|
<div class="emailMain"> |
||||||
|
<div class="emailTitle">我们欢迎您使用${this.sysName}</div> |
||||||
|
<div class="emailTitleLetter">您在某些地方请求了邮箱的验证码,如果不是自己操作请修改账户的密码。</div> |
||||||
|
<div class="emailCode"> |
||||||
|
<div>ABCDEF</div> |
||||||
|
</div> |
||||||
|
<div class="emailDescription">此邮件作用于注册<br/>截止邮件发送时间5分钟内使用有效。</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
`;
|
||||||
|
try { |
||||||
|
const resd = await this.senMail(test, email); |
||||||
|
res(resd); |
||||||
|
} catch (e) { |
||||||
|
rej(e); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: sendRegisterCodeMail(邮箱, 验证码)
|
||||||
|
// ? 函数描述: 给制定邮箱发送注册验证码
|
||||||
|
// ? ?
|
||||||
|
public sendRegisterCodeMail(email, code) { |
||||||
|
return new Promise(async (res, rej) => { |
||||||
|
const test = ` |
||||||
|
<style> |
||||||
|
.emailBody{ |
||||||
|
position: relative; |
||||||
|
min-height: 400px; |
||||||
|
min-width: 300px; |
||||||
|
height: 100%; |
||||||
|
width:100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
margin: 0 auto; |
||||||
|
padding: 0; |
||||||
|
overflow: overlay; |
||||||
|
} |
||||||
|
.emailMain{ |
||||||
|
position:relative; |
||||||
|
|
||||||
|
} |
||||||
|
.emailTitle{ |
||||||
|
line-height: 3em; |
||||||
|
font-size: 24px; |
||||||
|
font-weight: bold; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.emailTitleLetter{ |
||||||
|
position:relative; |
||||||
|
font-size: 16px; |
||||||
|
line-height: 3em; |
||||||
|
} |
||||||
|
.emailCode{ |
||||||
|
position:relative; |
||||||
|
height: 100px; |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
.emailCode>div{ |
||||||
|
position:relative; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 1em; |
||||||
|
font-size: 30px; |
||||||
|
text-align: center; |
||||||
|
color: #fefefe; |
||||||
|
background-color: #222; |
||||||
|
} |
||||||
|
.emailDescription{ |
||||||
|
position:relative; |
||||||
|
text-align: center; |
||||||
|
padding: 20px 0 ; |
||||||
|
line-height: 1.8em; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
||||||
|
<div class="emailBody"> |
||||||
|
<div class="emailMain"> |
||||||
|
<div class="emailTitle">我们欢迎您使用${this.sysName}</div> |
||||||
|
<div class="emailTitleLetter">您在某些地方请求了邮箱的验证码,如果不是自己操作请修改账户的密码。</div> |
||||||
|
<div class="emailCode"> |
||||||
|
<div>${code}</div> |
||||||
|
</div> |
||||||
|
<div class="emailDescription">此邮件作用于账户注册<br/>截止邮件发送时间5分钟内使用有效。</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
`;
|
||||||
|
try { |
||||||
|
console.time('EMAIL'); |
||||||
|
const resd = await this.senMail(test, email); |
||||||
|
console.timeEnd('EMAIL'); |
||||||
|
res(resd); |
||||||
|
} catch (e) { |
||||||
|
rej({ |
||||||
|
data: e, |
||||||
|
message: '发送邮件失败!', |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: sendSignInCodeMail(邮箱, 验证码)
|
||||||
|
// ? 函数描述: 给制定邮箱发送注册验证码
|
||||||
|
// ? ?
|
||||||
|
public sendSignInCodeMail(email, code) { |
||||||
|
return new Promise(async (res, rej) => { |
||||||
|
const test = ` |
||||||
|
<style> |
||||||
|
.emailBody{ |
||||||
|
position: relative; |
||||||
|
min-height: 400px; |
||||||
|
min-width: 300px; |
||||||
|
height: 100%; |
||||||
|
width:100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
margin: 0 auto; |
||||||
|
padding: 0; |
||||||
|
overflow: overlay; |
||||||
|
} |
||||||
|
.emailMain{ |
||||||
|
position:relative; |
||||||
|
|
||||||
|
} |
||||||
|
.emailTitle{ |
||||||
|
line-height: 3em; |
||||||
|
font-size: 24px; |
||||||
|
font-weight: bold; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.emailTitleLetter{ |
||||||
|
position:relative; |
||||||
|
font-size: 16px; |
||||||
|
line-height: 3em; |
||||||
|
} |
||||||
|
.emailCode{ |
||||||
|
position:relative; |
||||||
|
height: 100px; |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
.emailCode>div{ |
||||||
|
position:relative; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 1em; |
||||||
|
font-size: 30px; |
||||||
|
text-align: center; |
||||||
|
color: #fefefe; |
||||||
|
background-color: #222; |
||||||
|
} |
||||||
|
.emailDescription{ |
||||||
|
position:relative; |
||||||
|
text-align: center; |
||||||
|
padding: 20px 0 ; |
||||||
|
line-height: 1.8em; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
||||||
|
<div class="emailBody"> |
||||||
|
<div class="emailMain"> |
||||||
|
<div class="emailTitle">我们欢迎您使用${this.sysName}</div> |
||||||
|
<div class="emailTitleLetter">您在某些地方请求了邮箱的验证码,如果不是自己操作请修改账户的密码。</div> |
||||||
|
<div class="emailCode"> |
||||||
|
<div>${code}</div> |
||||||
|
</div> |
||||||
|
<div class="emailDescription">此邮件作用于账户登录<br/>截止邮件发送时间5分钟内使用有效。</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
`;
|
||||||
|
try { |
||||||
|
console.time('EMAIL'); |
||||||
|
const resd = await this.senMail(test, email); |
||||||
|
console.timeEnd('EMAIL'); |
||||||
|
res(resd); |
||||||
|
} catch (e) { |
||||||
|
rej({ |
||||||
|
data: e, |
||||||
|
message: '发送邮件失败!', |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
import { Global, Module } from '@nestjs/common'; |
||||||
|
import { GloggerService } from './glogger.service'; |
||||||
|
@Global() |
||||||
|
@Module({ |
||||||
|
providers: [GloggerService], |
||||||
|
exports: [GloggerService], |
||||||
|
}) |
||||||
|
export class GloggerModule {} |
@ -0,0 +1,108 @@ |
|||||||
|
import { ConsoleLogger, Injectable, Scope } from '@nestjs/common'; |
||||||
|
import { configure, getLogger, Logger as log4jsLogger } from 'log4js'; |
||||||
|
import config from '../../../config'; |
||||||
|
|
||||||
|
@Injectable({ scope: Scope.TRANSIENT }) |
||||||
|
export class GloggerService extends ConsoleLogger { |
||||||
|
// 默认日志
|
||||||
|
defaultLog: log4jsLogger; |
||||||
|
// 文件日志
|
||||||
|
fileLog: log4jsLogger; |
||||||
|
context = ''; |
||||||
|
|
||||||
|
constructor(context = '') { |
||||||
|
super(); |
||||||
|
const logConfig = config().log; |
||||||
|
if (context) { |
||||||
|
this.context = context; |
||||||
|
} |
||||||
|
// this.configService
|
||||||
|
configure({ |
||||||
|
appenders: { |
||||||
|
default: { |
||||||
|
type: 'console', |
||||||
|
layout: { |
||||||
|
type: 'pattern', |
||||||
|
pattern: `%[%d{yyMMdd-hh:mm:ss:SSS} [%p]\t%m%]`, |
||||||
|
}, |
||||||
|
}, |
||||||
|
file: { |
||||||
|
filename: `Log/nestjs.services.log`, |
||||||
|
type: 'dateFile', |
||||||
|
// 配置 layout,此处使用自定义模式 pattern
|
||||||
|
layout: { |
||||||
|
type: 'pattern', |
||||||
|
pattern: '%d{yyMMdd-hh:mm:ss:SSS} [%p]\t%m', |
||||||
|
}, |
||||||
|
// 日志文件按日期(天)切割
|
||||||
|
pattern: 'yy-MM-dd', |
||||||
|
// 回滚旧的日志文件时,保证以 .log 结尾 (只有在 alwaysIncludePattern 为 false 生效)
|
||||||
|
keepFileExt: true, |
||||||
|
// 输出的日志文件名是都始终包含 pattern 日期结尾
|
||||||
|
alwaysIncludePattern: true, |
||||||
|
// 指定日志保留的天数
|
||||||
|
// daysToKeep: 10,
|
||||||
|
}, |
||||||
|
}, |
||||||
|
categories: { |
||||||
|
default: { |
||||||
|
appenders: ['default'], |
||||||
|
level: logConfig.level || 'all', |
||||||
|
enableCallStack: true, |
||||||
|
}, |
||||||
|
fileAll: { |
||||||
|
appenders: ['file'], |
||||||
|
level: logConfig.level || 'all', |
||||||
|
enableCallStack: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
this.defaultLog = getLogger(); |
||||||
|
this.fileLog = getLogger('fileAll'); |
||||||
|
} |
||||||
|
|
||||||
|
log(message: any, ...optionalParams: any[]) { |
||||||
|
const context = this.context; |
||||||
|
this.defaultLog.info(context, message, ...optionalParams); |
||||||
|
this.fileLog.info(context, message, ...optionalParams); |
||||||
|
} |
||||||
|
|
||||||
|
info(message: any, ...optionalParams: any[]) { |
||||||
|
const context = this.context; |
||||||
|
this.defaultLog.info(context, message, ...optionalParams); |
||||||
|
this.fileLog.info(context, message, ...optionalParams); |
||||||
|
} |
||||||
|
|
||||||
|
error(message: any, ...optionalParams: any[]) { |
||||||
|
const context = this.context; |
||||||
|
this.defaultLog.error(context, message, ...optionalParams); |
||||||
|
this.fileLog.error(context, message, ...optionalParams); |
||||||
|
} |
||||||
|
|
||||||
|
warn(message: any, ...optionalParams: any[]) { |
||||||
|
// super.warn(message, trace);
|
||||||
|
const context = this.context; |
||||||
|
this.defaultLog.warn(context, message, ...optionalParams); |
||||||
|
this.fileLog.warn(context, message, ...optionalParams); |
||||||
|
} |
||||||
|
|
||||||
|
debug(message: any, ...optionalParams: any[]) { |
||||||
|
// super.debug(message, trace);
|
||||||
|
const context = this.context; |
||||||
|
this.defaultLog.debug(context, message, ...optionalParams); |
||||||
|
this.fileLog.debug(context, message, ...optionalParams); |
||||||
|
} |
||||||
|
|
||||||
|
verbose(message: any, ...optionalParams: any[]) { |
||||||
|
// super.verbose(message, trace);
|
||||||
|
const context = this.context; |
||||||
|
this.defaultLog.info(context, message, ...optionalParams); |
||||||
|
this.fileLog.info(context, message, ...optionalParams); |
||||||
|
} |
||||||
|
|
||||||
|
setContext(context: string) { |
||||||
|
this.context = context; |
||||||
|
this.debug('加载日志'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
import { Global, Module } from '@nestjs/common'; |
||||||
|
import { GredisService } from './gredis.service'; |
||||||
|
@Global() |
||||||
|
@Module({ |
||||||
|
providers: [GredisService], |
||||||
|
exports: [GredisService], |
||||||
|
}) |
||||||
|
export class GredisModule {} |
@ -0,0 +1,360 @@ |
|||||||
|
import { Injectable } from '@nestjs/common'; |
||||||
|
import { createClient } from 'redis'; |
||||||
|
import config from '../../../config'; |
||||||
|
import { GtoolsService, HASHT } from '@/Gservice/GTOOLS/gtools.service'; |
||||||
|
|
||||||
|
export declare interface ISetRegisterEmailCode { |
||||||
|
data: any; |
||||||
|
message: string; |
||||||
|
registerCode?: string; |
||||||
|
} |
||||||
|
export declare interface ISetSignInEmailCode { |
||||||
|
data: any; |
||||||
|
message: string; |
||||||
|
signInCode?: string; |
||||||
|
} |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class GredisService { |
||||||
|
public DB; |
||||||
|
private config; |
||||||
|
// @ 专注于注册的数据库
|
||||||
|
private readonly UserRegisterPoll = 1; |
||||||
|
// @ 专注于登录的数据库
|
||||||
|
private readonly UserSignInPoll = 2; |
||||||
|
// @ 登录的相关配置文件
|
||||||
|
private readonly SignInCFG = config().signIn; |
||||||
|
// @ Token相关的配置文件
|
||||||
|
private readonly TokenCFG = config().token; |
||||||
|
|
||||||
|
constructor(private readonly gtools: GtoolsService) { |
||||||
|
this.config = config().redis.starLight; |
||||||
|
this.start(); |
||||||
|
} |
||||||
|
//#region 注册
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: start
|
||||||
|
// ? 函数描述: 连接redis
|
||||||
|
// ? ?
|
||||||
|
private async start() { |
||||||
|
const client = createClient({ |
||||||
|
url: `redis://${this.config.username}:${this.config.password}@${this.config.host}:${this.config.port}/${this.config.dbNumber}`, |
||||||
|
}); |
||||||
|
await client.connect(); |
||||||
|
this.DB = client; |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: setRegisterEmailCode(邮箱)
|
||||||
|
// ? 函数描述: 设置注册邮箱验证
|
||||||
|
// ? ?
|
||||||
|
public setRegisterEmailCode(email): Promise<ISetRegisterEmailCode> { |
||||||
|
return new Promise(async (res, rej) => { |
||||||
|
this.DB.select(this.UserRegisterPoll); |
||||||
|
try { |
||||||
|
const key = 'Register-' + email; |
||||||
|
const result = await this.DB.get(key); |
||||||
|
if (result === null) { |
||||||
|
const registerCode = this.gtools.getRandomString(6); |
||||||
|
try { |
||||||
|
const data = await this.DB.setEx( |
||||||
|
key, |
||||||
|
300, |
||||||
|
registerCode, |
||||||
|
); |
||||||
|
res({ |
||||||
|
data, |
||||||
|
registerCode, |
||||||
|
message: '生成验证码成功。', |
||||||
|
}); |
||||||
|
} catch (e) { |
||||||
|
rej({ |
||||||
|
data: e, |
||||||
|
message: '生成验证码失败。', |
||||||
|
}); |
||||||
|
} |
||||||
|
} else { |
||||||
|
rej({ |
||||||
|
data: null, |
||||||
|
message: '已发送过邮件,请稍后再重试。', |
||||||
|
}); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
rej({ |
||||||
|
data: e, |
||||||
|
message: '查找注册码失败。', |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: getRegisterEmailCode(邮箱)
|
||||||
|
// ? 函数描述: 读取注册验证码
|
||||||
|
// ? ?
|
||||||
|
public getRegisterEmailCode(email): Promise<ISetRegisterEmailCode> { |
||||||
|
return new Promise(async (res, rej) => { |
||||||
|
this.DB.select(this.UserRegisterPoll); |
||||||
|
const key = 'Register-' + email; |
||||||
|
try { |
||||||
|
const result = await this.DB.get(key); |
||||||
|
if (result === null) { |
||||||
|
rej({ |
||||||
|
data: null, |
||||||
|
message: '未找到相匹配的验证码!', |
||||||
|
}); |
||||||
|
} else { |
||||||
|
res({ |
||||||
|
data: null, |
||||||
|
message: '获取成功', |
||||||
|
registerCode: result as string, |
||||||
|
}); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
rej({ |
||||||
|
data: e, |
||||||
|
message: '从Redis获取注册码出现错误!', |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: removeRegisterEmailCode
|
||||||
|
// ? 函数描述:
|
||||||
|
// ? ?
|
||||||
|
public removeRegisterEmailCode(email): Promise<ISetRegisterEmailCode> { |
||||||
|
return new Promise(async (res, rej) => { |
||||||
|
this.DB.select(this.UserRegisterPoll); |
||||||
|
const key = 'Register-' + email; |
||||||
|
try { |
||||||
|
const result = await this.DB.del(key); |
||||||
|
res({ |
||||||
|
message: '清除注册验证码成功!', |
||||||
|
data: {}, |
||||||
|
}); |
||||||
|
} catch (e) { |
||||||
|
res({ |
||||||
|
message: '清除注册验证码失败!', |
||||||
|
data: e, |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 密码登录
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: getSignInErrorNumber
|
||||||
|
// ? 函数描述: 获取异常登录信息
|
||||||
|
// ? ?
|
||||||
|
public async getSignInErrorNumber(uuid) { |
||||||
|
const key = 'SIN' + uuid; |
||||||
|
this.DB.select(this.UserSignInPoll); |
||||||
|
try { |
||||||
|
const result = await this.DB.get(key); |
||||||
|
if ( |
||||||
|
result === null || |
||||||
|
Number(result) < this.SignInCFG.signInErrorNumber |
||||||
|
) { |
||||||
|
return { |
||||||
|
state: true, |
||||||
|
message: '获取成功', |
||||||
|
error: null, |
||||||
|
}; |
||||||
|
} else { |
||||||
|
const ttl = await this.DB.ttl(key); |
||||||
|
return { |
||||||
|
state: false, |
||||||
|
message: '登陆异常已达上限,请稍后重试!', |
||||||
|
ttl, |
||||||
|
error: null, |
||||||
|
}; |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
return { |
||||||
|
state: false, |
||||||
|
message: '查找登录异常数据出错!', |
||||||
|
error: e, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: setSignInErrorNumber
|
||||||
|
// ? 函数描述: 设置用户登陆异常数字
|
||||||
|
// ? ?
|
||||||
|
public async setSignInErrorNumber(uuid) { |
||||||
|
const key = 'SIN' + uuid; |
||||||
|
this.DB.select(this.UserSignInPoll); |
||||||
|
try { |
||||||
|
const exist = await this.DB.exists(key); |
||||||
|
if (exist == 0) { |
||||||
|
console.log(key); |
||||||
|
await this.DB.set(key, 1); |
||||||
|
await this.DB.expire(key, this.SignInCFG.signInErrorTimeout); |
||||||
|
return { |
||||||
|
error: null, |
||||||
|
number: 1, |
||||||
|
state: true, |
||||||
|
}; |
||||||
|
} else { |
||||||
|
const number = await this.DB.get(key); |
||||||
|
await this.DB.set(key, Number(number) + 1); |
||||||
|
await this.DB.expire(key, this.SignInCFG.signInErrorTimeout); |
||||||
|
return { |
||||||
|
error: null, |
||||||
|
number: Number(number) + 1, |
||||||
|
state: true, |
||||||
|
}; |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
return { |
||||||
|
error: e, |
||||||
|
message: '设置登陆异常数量出现错误!', |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: setToken
|
||||||
|
// ? 函数描述: 设置Token到Redis
|
||||||
|
// ? ?
|
||||||
|
public async setToken(uuid, token) { |
||||||
|
this.DB.select(this.UserSignInPoll); |
||||||
|
const tokenKey = |
||||||
|
'TK' + this.gtools.makeHASH(token, HASHT.MD5).slice(0, 32); |
||||||
|
const uuidKey = 'UK' + uuid; |
||||||
|
await this.DB.setEx(tokenKey, this.TokenCFG.timeout / 1000, uuid); |
||||||
|
|
||||||
|
// 获取此用户的登陆数量
|
||||||
|
let signInNumber: number; |
||||||
|
try { |
||||||
|
signInNumber = await this.DB.zCard(uuidKey); |
||||||
|
} catch (e) { |
||||||
|
return { |
||||||
|
state: false, |
||||||
|
message: '获取登陆数量出错!', |
||||||
|
error: e, |
||||||
|
}; |
||||||
|
} |
||||||
|
// 写入Token有序集合
|
||||||
|
try { |
||||||
|
const score = new Date().getTime(); |
||||||
|
await this.DB.zAdd(uuidKey, [{ score, value: tokenKey }]); |
||||||
|
} catch (e) { |
||||||
|
return { |
||||||
|
state: false, |
||||||
|
message: '写入Token有序集合出错!', |
||||||
|
error: e, |
||||||
|
}; |
||||||
|
} |
||||||
|
// 清除最早的TokenKey和uuidKey
|
||||||
|
try { |
||||||
|
if (signInNumber >= this.SignInCFG.onLineNumber) { |
||||||
|
// 超出范围内
|
||||||
|
// 删除最先的
|
||||||
|
const uuidKeyZList = await this.DB.zRange( |
||||||
|
uuidKey, |
||||||
|
0, |
||||||
|
signInNumber, |
||||||
|
); |
||||||
|
// 删除第一个
|
||||||
|
const popTokenKey = await this.DB.zRemRangeByRank( |
||||||
|
uuidKey, |
||||||
|
0, |
||||||
|
0, |
||||||
|
); |
||||||
|
// 删除token
|
||||||
|
const delUuidKey = await this.DB.del(uuidKeyZList[0]); |
||||||
|
// 加入新的
|
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
return { |
||||||
|
state: false, |
||||||
|
message: '清除最早的TokenKey和uuidKey出错!', |
||||||
|
error: e, |
||||||
|
}; |
||||||
|
} |
||||||
|
return { |
||||||
|
data: { |
||||||
|
tokenKey, |
||||||
|
uuidKey, |
||||||
|
}, |
||||||
|
state: true, |
||||||
|
message: '设置成功!', |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: setSignInEmailCode(邮箱)
|
||||||
|
// ? 函数描述: 设置邮箱登录验证
|
||||||
|
// ? ?
|
||||||
|
public setSignInEmailCode(email): Promise<ISetSignInEmailCode> { |
||||||
|
return new Promise(async (res, rej) => { |
||||||
|
this.DB.select(this.UserSignInPoll); |
||||||
|
try { |
||||||
|
const key = 'SignIn-' + email; |
||||||
|
const result = await this.DB.get(key); |
||||||
|
if (result === null) { |
||||||
|
const signInCode = this.gtools.getRandomString(6); |
||||||
|
try { |
||||||
|
const data = await this.DB.setEx(key, 300, signInCode); |
||||||
|
res({ |
||||||
|
data, |
||||||
|
signInCode, |
||||||
|
message: '生成验证码成功。', |
||||||
|
}); |
||||||
|
} catch (e) { |
||||||
|
rej({ |
||||||
|
data: e, |
||||||
|
message: '生成验证码失败。', |
||||||
|
}); |
||||||
|
} |
||||||
|
} else { |
||||||
|
rej({ |
||||||
|
data: null, |
||||||
|
message: '已发送过邮件,请稍后再重试。', |
||||||
|
}); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
rej({ |
||||||
|
data: e, |
||||||
|
message: '查找登录码失败。', |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: getSignInEmailEntryCode(邮箱)
|
||||||
|
// ? 函数描述: 读取登录验证码
|
||||||
|
// ? ?
|
||||||
|
public getSignInEmailEntryCode(email): Promise<ISetSignInEmailCode> { |
||||||
|
return new Promise(async (res, rej) => { |
||||||
|
this.DB.select(this.UserSignInPoll); |
||||||
|
const key = 'SignIn-' + email; |
||||||
|
try { |
||||||
|
const result = await this.DB.get(key); |
||||||
|
if (result === null) { |
||||||
|
rej({ |
||||||
|
data: null, |
||||||
|
message: '未找到相匹配的验证码!', |
||||||
|
}); |
||||||
|
} else { |
||||||
|
res({ |
||||||
|
data: null, |
||||||
|
message: '获取成功', |
||||||
|
signInCode: result as string, |
||||||
|
}); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
rej({ |
||||||
|
data: e, |
||||||
|
message: '从Redis获取登录码出现错误!', |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
//#endregion
|
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
import { Global, Module } from '@nestjs/common'; |
||||||
|
import { GtoolsService } from './gtools.service'; |
||||||
|
@Global() |
||||||
|
@Module({ |
||||||
|
providers: [GtoolsService], |
||||||
|
exports: [GtoolsService], |
||||||
|
}) |
||||||
|
export class GtoolsModule {} |
@ -0,0 +1,137 @@ |
|||||||
|
import { Injectable } from '@nestjs/common'; |
||||||
|
import * as crypto from 'crypto'; |
||||||
|
import config from '@CFG/index'; |
||||||
|
import * as jwt from 'jsonwebtoken'; |
||||||
|
|
||||||
|
// @ 不可逆加密
|
||||||
|
export enum HASHT { |
||||||
|
MD5 = 'md5', // 32位
|
||||||
|
SHA256 = 'sha256', // 64位
|
||||||
|
SHA512 = 'sha512', // 128位
|
||||||
|
} |
||||||
|
// @ 可逆加密
|
||||||
|
export enum AEST { |
||||||
|
AES128 = 'aes-128-cbc', |
||||||
|
AES256 = 'aes-256-gcm', |
||||||
|
} |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class GtoolsService { |
||||||
|
// @ 加解密配置文件
|
||||||
|
private readonly encryptionCFG = config().encryption; |
||||||
|
private readonly tokenCFG = config().token; |
||||||
|
|
||||||
|
// @ AES-128-cec加解密 iv和key
|
||||||
|
private AES128CEC_key; |
||||||
|
private AES128CEC_iv; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
this.AES128CEC_key = Buffer.from( |
||||||
|
this.makeHASH(this.encryptionCFG.secretKey, HASHT.MD5).slice(-16), |
||||||
|
'utf8', |
||||||
|
); |
||||||
|
|
||||||
|
this.AES128CEC_iv = Buffer.from( |
||||||
|
this.makeHASH(this.encryptionCFG.salt, HASHT.MD5).slice(-16), |
||||||
|
'utf8', |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: getRandomString(长度,大小写默认big)
|
||||||
|
// ? 函数描述: 获取制定长度的随机字符串
|
||||||
|
// ? ?
|
||||||
|
public getRandomString(len: number, size = 'big'): string { |
||||||
|
if (size == 'big') { |
||||||
|
return Math.random() |
||||||
|
.toString(36) |
||||||
|
.slice(-len) |
||||||
|
.toString() |
||||||
|
.toUpperCase(); |
||||||
|
} else { |
||||||
|
return Math.random() |
||||||
|
.toString(36) |
||||||
|
.slice(-len) |
||||||
|
.toString() |
||||||
|
.toLowerCase(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: makeUUID
|
||||||
|
// ? 函数描述: 生成UUID
|
||||||
|
// ? ?
|
||||||
|
public makeUUID() { |
||||||
|
return crypto.randomUUID().split('-').join('').toUpperCase(); |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: makeHASH(加密内容, 加密编码默认为SHA512)
|
||||||
|
// ? 函数描述: =加密字符串
|
||||||
|
// ? ?
|
||||||
|
public makeHASH(plaintext, algorithm = HASHT.SHA512) { |
||||||
|
const sha512 = crypto.createHash(algorithm); |
||||||
|
const sha512Sum = sha512.update(plaintext + this.encryptionCFG.salt); |
||||||
|
const ciphertext = sha512Sum.digest('hex'); |
||||||
|
return ciphertext; |
||||||
|
} |
||||||
|
|
||||||
|
//#region 可逆加密
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: encrypt
|
||||||
|
// ? 函数描述: 加密字符串
|
||||||
|
// ? ?
|
||||||
|
encrypt(plaintext, algorithm = AEST.AES128) { |
||||||
|
const cipher = crypto.createCipheriv( |
||||||
|
algorithm, |
||||||
|
this.AES128CEC_key, |
||||||
|
this.AES128CEC_iv, |
||||||
|
); // 初始化加密算法
|
||||||
|
let ciphertext = cipher.update(plaintext, 'utf8', 'hex'); |
||||||
|
ciphertext += cipher.final('hex'); |
||||||
|
return ciphertext; |
||||||
|
} |
||||||
|
|
||||||
|
// ? ?
|
||||||
|
// ? 函数名称: decrypt
|
||||||
|
// ? 函数描述: 解密字符串
|
||||||
|
// ? ?
|
||||||
|
decrypt(ciphertext, algorithm = AEST.AES128) { |
||||||
|
let plaintext = ''; |
||||||
|
const cipher = crypto.createDecipheriv( |
||||||
|
algorithm, |
||||||
|
this.AES128CEC_key, |
||||||
|
this.AES128CEC_iv, |
||||||
|
); |
||||||
|
plaintext += cipher.update(ciphertext, 'hex', 'utf8'); |
||||||
|
plaintext += cipher.final('utf8'); |
||||||
|
return plaintext; |
||||||
|
} |
||||||
|
//#endredion
|
||||||
|
|
||||||
|
//#region jwt
|
||||||
|
createToken(data) { |
||||||
|
const plaintextToken = jwt.sign(data, this.tokenCFG.secretKey, { |
||||||
|
expiresIn: this.tokenCFG.timeout / 1000, |
||||||
|
}); |
||||||
|
const ciphertextToken = this.encrypt(plaintextToken); |
||||||
|
return ciphertextToken; |
||||||
|
} |
||||||
|
resolveToken(ciphertextToken) { |
||||||
|
try { |
||||||
|
// 解析密文
|
||||||
|
const plaintextToken = this.decrypt(ciphertextToken); |
||||||
|
const data = jwt.verify(plaintextToken, this.tokenCFG.secretKey); |
||||||
|
return { |
||||||
|
token: true, |
||||||
|
data, |
||||||
|
}; |
||||||
|
} catch (e) { |
||||||
|
return { |
||||||
|
token: false, |
||||||
|
data: e, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
//#endredion
|
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
import GtoolsService from './gtools.service' |
||||||
|
|
||||||
|
|
||||||
|
const g = new GtoolsService() |
@ -0,0 +1,29 @@ |
|||||||
|
import { Module } from '@nestjs/common'; |
||||||
|
import { ConfigModule } from '@nestjs/config'; |
||||||
|
import { GredisModule } from '@/Gservice/GREDIS/gredis.module'; |
||||||
|
import { GloggerModule } from '@/Gservice/GLOGGER/glogger.module'; |
||||||
|
import { GdatabaseModule } from '@/Gservice/GDATABASE/gdatabase.module'; |
||||||
|
import { GemailModule } from '@/Gservice/GEMAIL/gemail.module'; |
||||||
|
import { GtoolsModule } from '@/Gservice/GTOOLS/gtools.module'; |
||||||
|
import config from '@CFG/index'; |
||||||
|
// import { StarlightModule } from '@/starlight/starlight.module';
|
||||||
|
import { RgvsaleModule } from './rgvsale/rgvsale.module'; |
||||||
|
|
||||||
|
@Module({ |
||||||
|
imports: [ |
||||||
|
ConfigModule.forRoot({ |
||||||
|
isGlobal: true, // 作用于全局
|
||||||
|
load: [config], // 加载自定义配置项
|
||||||
|
}), |
||||||
|
GloggerModule, |
||||||
|
// StarlightModule,
|
||||||
|
GdatabaseModule, |
||||||
|
// GredisModule,
|
||||||
|
GemailModule, |
||||||
|
GtoolsModule, |
||||||
|
RgvsaleModule, |
||||||
|
], |
||||||
|
controllers: [], |
||||||
|
providers: [], |
||||||
|
}) |
||||||
|
export class AppModule {} |
@ -0,0 +1,97 @@ |
|||||||
|
import { NestFactory } from '@nestjs/core'; |
||||||
|
import { AppModule } from './app.module'; |
||||||
|
import { |
||||||
|
FastifyAdapter, |
||||||
|
NestFastifyApplication, |
||||||
|
} from '@nestjs/platform-fastify'; |
||||||
|
// 参数验证
|
||||||
|
import { ValidationPipe } from '@nestjs/common'; |
||||||
|
// Fastify文件上传插件
|
||||||
|
import fastifyMultipart from '@fastify/multipart'; |
||||||
|
// 日志服务
|
||||||
|
import { GloggerService } from '@/Gservice/GLOGGER/glogger.service'; |
||||||
|
// API文档
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; |
||||||
|
// 配置文件
|
||||||
|
import { ConfigService } from '@nestjs/config'; |
||||||
|
// 异常捕获
|
||||||
|
import { GexceptionsfilterFilter } from '@/Gexceptions/gexceptionsfilter.filter'; |
||||||
|
// 拦截器
|
||||||
|
// --全局开发拦截
|
||||||
|
import { GdevinterceptorInterceptor } from '@/Ginterceptor/gdevinterceptor.interceptor'; |
||||||
|
// --全局响应拦截
|
||||||
|
import { GresponseinterceptorInterceptor } from '@/Ginterceptor/gresponseinterceptor.interceptor'; |
||||||
|
|
||||||
|
async function bootstrap() { |
||||||
|
const app = await NestFactory.create<NestFastifyApplication>( |
||||||
|
// 加载主程序模块
|
||||||
|
AppModule, |
||||||
|
// 使用Fastify作为底层框架
|
||||||
|
new FastifyAdapter({ |
||||||
|
logger: false, |
||||||
|
}), |
||||||
|
); |
||||||
|
// 配置服务
|
||||||
|
const appConfig = app.get(ConfigService); |
||||||
|
// 日志 Log为文件路径
|
||||||
|
const logger = new GloggerService('[BOOTSTRAP]'); |
||||||
|
app.useLogger(logger); |
||||||
|
// 全局路由前缀
|
||||||
|
app.setGlobalPrefix('api'); |
||||||
|
|
||||||
|
// 全局开启参数验证--需要安装class-validator 又依赖 class-transformer
|
||||||
|
// app.useGlobalPipes(new ValidationPipe());
|
||||||
|
app.useGlobalPipes(new ValidationPipe({ |
||||||
|
transform: true, |
||||||
|
}));// 开启参数转换,就不用一个一个写了
|
||||||
|
// fastify文件上传中间件
|
||||||
|
await app.register(fastifyMultipart, { |
||||||
|
addToBody: true, |
||||||
|
throwFileSizeLimit: true, |
||||||
|
limits:{ |
||||||
|
fileSize: 1024 * 1024 * 500 |
||||||
|
} |
||||||
|
}); |
||||||
|
// 开发者工具
|
||||||
|
const swaggerState = appConfig.get('swagger').enable; |
||||||
|
Swagger(app, swaggerState, logger); |
||||||
|
const devInterceptorState = appConfig.get('dev').devInterceptor; |
||||||
|
DevInterceptor(app, devInterceptorState, logger); |
||||||
|
|
||||||
|
//全局响应拦截器
|
||||||
|
app.useGlobalInterceptors(new GresponseinterceptorInterceptor()); |
||||||
|
// 全局异常捕获
|
||||||
|
app.useGlobalFilters(new GexceptionsfilterFilter()); |
||||||
|
|
||||||
|
// 从配置文件的信息中启动服务
|
||||||
|
const listenConfig = appConfig.get('master'); |
||||||
|
await app.listen(listenConfig.port, listenConfig.host); |
||||||
|
|
||||||
|
logger.log( |
||||||
|
'🚀 服务应用已经成功启动!', |
||||||
|
`http://${listenConfig.host}:${listenConfig.port}`, |
||||||
|
); |
||||||
|
} |
||||||
|
// Swagger接口测试和说明文档
|
||||||
|
function Swagger(app, state, logger) { |
||||||
|
if (!state) return; |
||||||
|
const config = new DocumentBuilder() |
||||||
|
.setTitle('系统接口文档') |
||||||
|
.setDescription('这是一份接口文档') |
||||||
|
.setVersion('1.0.0') |
||||||
|
.addBearerAuth() |
||||||
|
.build(); |
||||||
|
const document = SwaggerModule.createDocument(app, config); |
||||||
|
SwaggerModule.setup('docs', app, document); |
||||||
|
logger.warn('Swagger外接程序已加载!'); |
||||||
|
logger.warn('应用程序文档地址: http://localhost:3000/docs'); |
||||||
|
} |
||||||
|
// DevInterceptorService 输出请求处理时间和响应数据大小的拦截器
|
||||||
|
function DevInterceptor(app, state, logger) { |
||||||
|
// 全局拦截器
|
||||||
|
if (!state) return; |
||||||
|
app.useGlobalInterceptors(new GdevinterceptorInterceptor()); |
||||||
|
logger.warn('DevInterceptorService请求响应拦截器已开启!'); |
||||||
|
} |
||||||
|
|
||||||
|
bootstrap(); |
@ -0,0 +1,126 @@ |
|||||||
|
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; |
||||||
|
import { Param, ValidationPipe, Query, HttpException, HttpStatus } from '@nestjs/common'; |
||||||
|
import { Transform } from 'class-transformer'; |
||||||
|
|
||||||
|
// 获取产品账户分页
|
||||||
|
export class GetAccountPageDto { |
||||||
|
// 产品ID
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
// 页码
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageNum必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageNum: number; |
||||||
|
|
||||||
|
// 页大小
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageSize必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageSize: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 添加产品账户
|
||||||
|
export class CreateAccountDto{ |
||||||
|
// 产品ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '产品账户角色必须为字符串', |
||||||
|
}) |
||||||
|
accountRoleName: string |
||||||
|
|
||||||
|
@IsString({ |
||||||
|
message: '产品账户用户名必须为字符串', |
||||||
|
}) |
||||||
|
accountUsername: string |
||||||
|
|
||||||
|
@IsString({ |
||||||
|
message: '产品账户密码必须为字符串', |
||||||
|
}) |
||||||
|
accountPassword: string |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑产品账户
|
||||||
|
export class EditAccountDto{ |
||||||
|
// 账户ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品账户ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
accountId: number; |
||||||
|
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '产品账户角色必须为字符串', |
||||||
|
}) |
||||||
|
accountRoleName: string |
||||||
|
|
||||||
|
@IsString({ |
||||||
|
message: '产品账户用户名必须为字符串', |
||||||
|
}) |
||||||
|
accountUsername: string |
||||||
|
|
||||||
|
@IsString({ |
||||||
|
message: '产品账户密码必须为字符串', |
||||||
|
}) |
||||||
|
accountPassword: string |
||||||
|
} |
||||||
|
|
||||||
|
// 删除产品账户
|
||||||
|
export class DeleteAccountDto{ |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品账户ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
accountId: Number; |
||||||
|
} |
@ -0,0 +1,156 @@ |
|||||||
|
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; |
||||||
|
import { Param, ValidationPipe, Query, HttpException, HttpStatus } from '@nestjs/common'; |
||||||
|
import { Transform } from 'class-transformer'; |
||||||
|
|
||||||
|
// 获取演示项分页
|
||||||
|
export class GetDemoItemPageDto { |
||||||
|
// 产品ID
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
// 页码
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageNum必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageNum: number; |
||||||
|
|
||||||
|
// 页大小
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageSize必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageSize: number; |
||||||
|
} |
||||||
|
|
||||||
|
export class GetDemoItemListDto { |
||||||
|
// 产品ID
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 添加演示项
|
||||||
|
export class CreateDemoItemDto{ |
||||||
|
// 产品ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '演示项名称必须为字符串', |
||||||
|
}) |
||||||
|
itemName: string |
||||||
|
|
||||||
|
@Transform((val) => { |
||||||
|
const filepathList = val.value.split('/rgvsale/staticFile/') |
||||||
|
if(filepathList.length == 2){ |
||||||
|
let fileId = filepathList[1] |
||||||
|
if (Number(fileId)) { |
||||||
|
return Number(fileId); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项图解地址格式不正确', 400); |
||||||
|
} |
||||||
|
}else{ |
||||||
|
throw new HttpException('演示项图解地址格式不正确', 400); |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
itemUrl: Number |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑演示项
|
||||||
|
export class EditDemoItemDto{ |
||||||
|
// 产品ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示项Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
// 产品ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
itemId: number; |
||||||
|
@IsString({ |
||||||
|
message: '演示项名称必须为字符串', |
||||||
|
}) |
||||||
|
itemName: string |
||||||
|
|
||||||
|
@Transform((val) => { |
||||||
|
const filepathList = val.value.split('/rgvsale/staticFile/') |
||||||
|
if(filepathList.length == 2){ |
||||||
|
let fileId = filepathList[1] |
||||||
|
if (Number(fileId)) { |
||||||
|
return Number(fileId); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项图解地址格式不正确', 400); |
||||||
|
} |
||||||
|
}else{ |
||||||
|
throw new HttpException('演示项图解地址格式不正确', 400); |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
itemUrl: Number |
||||||
|
} |
||||||
|
|
||||||
|
// 删除演示项
|
||||||
|
export class DeleteDemoItemDto{ |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示项Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
itemId: Number; |
||||||
|
} |
@ -0,0 +1,177 @@ |
|||||||
|
import { |
||||||
|
ArrayMinSize, |
||||||
|
IsArray, IsDate, |
||||||
|
IsEnum, |
||||||
|
IsMilitaryTime, |
||||||
|
IsNotEmpty, |
||||||
|
IsNumber, |
||||||
|
IsOptional, |
||||||
|
IsString, |
||||||
|
} from 'class-validator'; |
||||||
|
import { Param, ValidationPipe, Query, HttpException, HttpStatus } from '@nestjs/common'; |
||||||
|
import { Transform, Type } from 'class-transformer'; |
||||||
|
|
||||||
|
|
||||||
|
export enum sex { |
||||||
|
'上' = 0, |
||||||
|
'下' = 1, |
||||||
|
} |
||||||
|
|
||||||
|
// 添加演示方案
|
||||||
|
export class CreateProgrammeDto{ |
||||||
|
@IsString({ |
||||||
|
message: '方案名称必须为字符串', |
||||||
|
}) |
||||||
|
programmeName: string |
||||||
|
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '产品ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: Number; |
||||||
|
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '行业ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
industryId: Number; |
||||||
|
|
||||||
|
@ArrayMinSize(1, { message: '演示项至少有一个' }) |
||||||
|
// 文档建议如果是字符串的数组,使用字符串约束更好,因为js其实不存在数字数组,字符串数组等
|
||||||
|
@Type(() => Number) |
||||||
|
// 上面虽然解决了不是字符串的数组的问题,但是如果传进来的是一个字符串呢?这就太tm难了,所以再在编译时检查一下算了吧,运行时不管了
|
||||||
|
@IsArray({ message: '演示项列表必须是一个数组' }) |
||||||
|
@Transform((val) => { |
||||||
|
const newList = [] |
||||||
|
val.value.map(i => { |
||||||
|
const newNum = Number(i) |
||||||
|
if (newNum) { |
||||||
|
if(isNaN(newNum)) throw new HttpException('演示项列表必须为数字', 400) |
||||||
|
newList.push(newNum) |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项列表必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
console.log(newList); |
||||||
|
return newList |
||||||
|
}) |
||||||
|
demoItemsList: Number[] |
||||||
|
} |
||||||
|
|
||||||
|
// 创建
|
||||||
|
export class CreateScenesDto{ |
||||||
|
// 方案ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '方案ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('方案ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
programmeId: Number; |
||||||
|
|
||||||
|
// 预计开始时间
|
||||||
|
@Type(() => Date) |
||||||
|
@IsDate({ |
||||||
|
message: '预计开始时间格式不正确' |
||||||
|
}) |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '预计开始时间不能为空', |
||||||
|
}) |
||||||
|
expectedStarttime: string; |
||||||
|
|
||||||
|
// 目标客户
|
||||||
|
@IsString() |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '目标客户不能为空', |
||||||
|
}) |
||||||
|
targetCustomers: string; |
||||||
|
} |
||||||
|
|
||||||
|
// 删除现场
|
||||||
|
export class DeleteSceneDto{ |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示现场Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示现场ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
sceneId: Number; |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑场景信息
|
||||||
|
export class EditSceneDto{ |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示现场Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示现场ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
sceneId: Number; |
||||||
|
|
||||||
|
|
||||||
|
// 预计开始时间
|
||||||
|
@IsMilitaryTime() |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '预计开始时间不能为空', |
||||||
|
}) |
||||||
|
expectedStarttime: string; |
||||||
|
|
||||||
|
// 目标客户
|
||||||
|
@IsString() |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '目标客户不能为空', |
||||||
|
}) |
||||||
|
targetCustomers: string; |
||||||
|
} |
||||||
|
|
||||||
|
// 增加演示项点击次数
|
||||||
|
export class AddDemoItemAccumulateForSceneDto{ |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示项Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
demoItemId: Number; |
||||||
|
|
||||||
|
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示现场Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示现场ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
sceneId: Number; |
||||||
|
} |
@ -0,0 +1,259 @@ |
|||||||
|
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; |
||||||
|
import { Param, ValidationPipe, Query, HttpException, HttpStatus } from '@nestjs/common'; |
||||||
|
import { Transform } from 'class-transformer'; |
||||||
|
|
||||||
|
// 获取演示问题分页
|
||||||
|
export class GetScenesProblemPageDto{ |
||||||
|
// 产品ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
|
||||||
|
// 行业ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
industryId: number; |
||||||
|
|
||||||
|
|
||||||
|
// 方案ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('方案ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
programmeId: number; |
||||||
|
|
||||||
|
|
||||||
|
// 现场ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('现场ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
sceneId: number; |
||||||
|
|
||||||
|
// 模糊查询
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '客户角色必须为字符串', |
||||||
|
}) |
||||||
|
search: string; |
||||||
|
|
||||||
|
// 利用状态
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value) || Number(val.value) === 0) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else if(Number(val.value) !== 0){ |
||||||
|
throw new HttpException('利用状态必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
isUse: number; |
||||||
|
|
||||||
|
|
||||||
|
// 页码
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageNum必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageNum: number; |
||||||
|
|
||||||
|
// 页大小
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageSize必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageSize: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 新增演示问题
|
||||||
|
export class CreateScenesProblemDto{ |
||||||
|
// 问题内容
|
||||||
|
@IsString({ |
||||||
|
message: '问题内容必须为字符串', |
||||||
|
}) |
||||||
|
problemContent: string; |
||||||
|
|
||||||
|
// 演示现场ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示现场ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('演示现场ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
sceneId: number; |
||||||
|
|
||||||
|
// 演示项ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示现项ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('演示项ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
demoItemId: number; |
||||||
|
|
||||||
|
// 客户角色
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '客户角色必须为字符串', |
||||||
|
}) |
||||||
|
targetCustomersRole: string; |
||||||
|
|
||||||
|
// 客户名称
|
||||||
|
@IsString({ |
||||||
|
message: '客户名称必须为字符串', |
||||||
|
}) |
||||||
|
targetCustomers: string; |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑演示问题
|
||||||
|
export class EditScenesProblemDto{ |
||||||
|
// 问题内容
|
||||||
|
@IsString({ |
||||||
|
message: '问题内容必须为字符串', |
||||||
|
}) |
||||||
|
problemContent: string; |
||||||
|
|
||||||
|
// 问题ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示现场ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('演示现场ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
problemId: number; |
||||||
|
|
||||||
|
// 客户角色
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '客户角色必须为字符串', |
||||||
|
}) |
||||||
|
targetCustomersRole: string; |
||||||
|
|
||||||
|
// 客户名称
|
||||||
|
@IsString({ |
||||||
|
message: '客户名称必须为字符串', |
||||||
|
}) |
||||||
|
targetCustomers: string; |
||||||
|
|
||||||
|
// 演示现场ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示现场ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('演示现场ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
sceneId: number; |
||||||
|
|
||||||
|
// 演示项ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示现项ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('演示项ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
demoItemId: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 删除演示问题
|
||||||
|
export class DeleteScenesProblemDto{ |
||||||
|
// 问题ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '问题ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('问题ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
problemId: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 修改问题利用状态
|
||||||
|
export class ChangeUseStateDto{ |
||||||
|
// 问题ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '问题ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('问题ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
problemId: number; |
||||||
|
} |
@ -0,0 +1,171 @@ |
|||||||
|
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; |
||||||
|
import { Param, ValidationPipe, Query, HttpException, HttpStatus } from '@nestjs/common'; |
||||||
|
import { Transform } from 'class-transformer'; |
||||||
|
|
||||||
|
// 获取行业分页
|
||||||
|
export class GetIndustryPageDto { |
||||||
|
// 产品ID
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
// 页码
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageNum必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageNum: number; |
||||||
|
|
||||||
|
// 页大小
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageSize必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageSize: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 获取行业下拉列表
|
||||||
|
export class GetIndustryListDto{ |
||||||
|
// 产品ID
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 添加行业
|
||||||
|
export class CreateIndustryDto{ |
||||||
|
// 行业ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
// 行业名称
|
||||||
|
@IsString({ |
||||||
|
message: '行业名称必须为字符串', |
||||||
|
}) |
||||||
|
industryName: string |
||||||
|
|
||||||
|
// 行业描述
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '行业描述必须为字符串', |
||||||
|
}) |
||||||
|
industryDescription: string |
||||||
|
|
||||||
|
// 数据库文件
|
||||||
|
@Transform((val) => { |
||||||
|
const filepathList = val.value.split('/rgvsale/staticFile/') |
||||||
|
if(filepathList.length == 2){ |
||||||
|
let fileId = filepathList[1] |
||||||
|
if (Number(fileId)) { |
||||||
|
return Number(fileId); |
||||||
|
} else { |
||||||
|
throw new HttpException('数据库文件地址格式不正确', 400); |
||||||
|
} |
||||||
|
}else{ |
||||||
|
throw new HttpException('数据库文件地址格式不正确', 400); |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
sqlId: Number |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑行业
|
||||||
|
export class EditIndustryDto{ |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
industryId: Number; |
||||||
|
|
||||||
|
// 行业ID
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
// 行业名称
|
||||||
|
@IsString({ |
||||||
|
message: '行业名称必须为字符串', |
||||||
|
}) |
||||||
|
industryName: string |
||||||
|
|
||||||
|
// 行业描述
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '行业描述必须为字符串', |
||||||
|
}) |
||||||
|
industryDescription: string |
||||||
|
|
||||||
|
// 数据库文件
|
||||||
|
@Transform((val) => { |
||||||
|
const filepathList = val.value.split('/rgvsale/staticFile/') |
||||||
|
if(filepathList.length == 2){ |
||||||
|
let fileId = filepathList[1] |
||||||
|
if (Number(fileId)) { |
||||||
|
return Number(fileId); |
||||||
|
} else { |
||||||
|
throw new HttpException('数据库文件地址格式不正确', 400); |
||||||
|
} |
||||||
|
}else{ |
||||||
|
throw new HttpException('数据库文件地址格式不正确', 400); |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
sqlId: Number |
||||||
|
} |
||||||
|
|
||||||
|
// 删除行业
|
||||||
|
export class DeleteIndustryDto{ |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
industryId: Number; |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; |
||||||
|
import { Param, ValidationPipe, Query, HttpException, HttpStatus } from '@nestjs/common'; |
||||||
|
import { Transform } from 'class-transformer'; |
||||||
|
|
||||||
|
// 获取产品分页
|
||||||
|
export class GetProductPageDto { |
||||||
|
// 页码
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageNum必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageNum: number; |
||||||
|
|
||||||
|
// 页大小
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageSize必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageSize: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 添加产品
|
||||||
|
export class CreateProductDto{ |
||||||
|
@IsString({ |
||||||
|
message: '产品名称必须为字符串', |
||||||
|
}) |
||||||
|
productName: string |
||||||
|
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '产品描述必须为字符串', |
||||||
|
}) |
||||||
|
productDescription: string |
||||||
|
|
||||||
|
@IsString({ |
||||||
|
message: '产品链接必须为字符串', |
||||||
|
}) |
||||||
|
productsUrl: string |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑产品
|
||||||
|
export class EditProductDto{ |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: Number; |
||||||
|
|
||||||
|
@IsString({ |
||||||
|
message: '产品名称必须为字符串', |
||||||
|
}) |
||||||
|
productName: string |
||||||
|
|
||||||
|
@IsOptional() |
||||||
|
@IsString({ |
||||||
|
message: '产品描述必须为字符串', |
||||||
|
}) |
||||||
|
productDescription: string |
||||||
|
|
||||||
|
@IsString({ |
||||||
|
message: '产品链接必须为字符串', |
||||||
|
}) |
||||||
|
productUrl: string |
||||||
|
} |
||||||
|
|
||||||
|
// 删除产品
|
||||||
|
export class DeleteProductDto{ |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: Number; |
||||||
|
} |
@ -0,0 +1,300 @@ |
|||||||
|
import { ArrayMinSize, IsArray, IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; |
||||||
|
import { Param, ValidationPipe, Query, HttpException, HttpStatus } from '@nestjs/common'; |
||||||
|
import { Transform, Type } from 'class-transformer'; |
||||||
|
|
||||||
|
|
||||||
|
export enum sex { |
||||||
|
'上' = 0, |
||||||
|
'下' = 1, |
||||||
|
} |
||||||
|
|
||||||
|
// 添加演示方案
|
||||||
|
export class CreateProgrammeDto{ |
||||||
|
@IsString({ |
||||||
|
message: '方案名称必须为字符串', |
||||||
|
}) |
||||||
|
programmeName: string |
||||||
|
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '产品ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: Number; |
||||||
|
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '行业ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
industryId: Number; |
||||||
|
|
||||||
|
@ArrayMinSize(1, { message: '演示项至少有一个' }) |
||||||
|
// 文档建议如果是字符串的数组,使用字符串约束更好,因为js其实不存在数字数组,字符串数组等
|
||||||
|
@Type(() => Number) |
||||||
|
// 上面虽然解决了不是字符串的数组的问题,但是如果传进来的是一个字符串呢?这就太tm难了,所以再在编译时检查一下算了吧,运行时不管了
|
||||||
|
@IsArray({ message: '演示项列表必须是一个数组' }) |
||||||
|
@Transform((val) => { |
||||||
|
const newList = [] |
||||||
|
val.value.map(i => { |
||||||
|
const newNum = Number(i) |
||||||
|
if (newNum) { |
||||||
|
if(isNaN(newNum)) throw new HttpException('演示项列表必须为数字', 400) |
||||||
|
newList.push(newNum) |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项列表必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
console.log(newList); |
||||||
|
return newList |
||||||
|
}) |
||||||
|
demoItemsList: Number[] |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑演示方案
|
||||||
|
export class EditProgrammeDto{ |
||||||
|
@IsNotEmpty({ |
||||||
|
message: '产品ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: Number; |
||||||
|
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '方案ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('方案ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
programmeId: Number; |
||||||
|
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '行业ID不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
industryId: Number; |
||||||
|
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '产品名称不能为空', |
||||||
|
}) |
||||||
|
@IsString({ |
||||||
|
message: '产品名称必须为字符串', |
||||||
|
}) |
||||||
|
programmeName: string |
||||||
|
|
||||||
|
@ArrayMinSize(1, { message: '演示项至少有一个' }) |
||||||
|
// 文档建议如果是字符串的数组,使用字符串约束更好,因为js其实不存在数字数组,字符串数组等
|
||||||
|
@Type(() => Number) |
||||||
|
// 上面虽然解决了不是字符串的数组的问题,但是如果传进来的是一个字符串呢?这就太tm难了,所以再在编译时检查一下算了吧,运行时不管了
|
||||||
|
@IsArray({ message: '演示项列表必须是一个数组' }) |
||||||
|
@Transform((val) => { |
||||||
|
const newList = [] |
||||||
|
val.value.map(i => { |
||||||
|
const newNum = Number(i) |
||||||
|
if (newNum) { |
||||||
|
if(isNaN(newNum)) throw new HttpException('演示项列表必须为数字', 400) |
||||||
|
newList.push(newNum) |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项列表必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
return newList |
||||||
|
}) |
||||||
|
demoItemsList: Number[] |
||||||
|
} |
||||||
|
|
||||||
|
// 删除产品
|
||||||
|
export class DeleteProgrammeDto{ |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('方案ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
programmeId: Number; |
||||||
|
} |
||||||
|
|
||||||
|
// 排序演示项
|
||||||
|
export class EditDemoItemsListRankDto{ |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('方案ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
programmeId: Number; |
||||||
|
|
||||||
|
|
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else { |
||||||
|
throw new HttpException('演示项ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
demoItemId: Number; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@IsEnum(sex, { message: '顺序参数必须为:' + Object.keys(sex) }) |
||||||
|
offset: Number; |
||||||
|
} |
||||||
|
|
||||||
|
// 获取我的演示方案分页
|
||||||
|
export class GetProgrammePageDto{ |
||||||
|
// 产品ID
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('产品ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
productId: number; |
||||||
|
|
||||||
|
// 行业Id
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('行业ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
industryId: number; |
||||||
|
|
||||||
|
// 页码
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageNum必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageNum: number; |
||||||
|
|
||||||
|
// 页大小
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageSize必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageSize: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 获取方案的演示项分页
|
||||||
|
export class GetDemoItemsPageForProgrammeDto{ |
||||||
|
// 页码
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageNum必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageNum: number; |
||||||
|
|
||||||
|
// 页大小
|
||||||
|
@IsOptional() |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
} else { |
||||||
|
throw new HttpException('pageSize必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
pageSize: number; |
||||||
|
|
||||||
|
// 方案Id
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '方案Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('方案ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
programmeId: number; |
||||||
|
} |
||||||
|
|
||||||
|
// 删除方案演示项
|
||||||
|
export class DeleteDemoItemForProgrammeDto{ |
||||||
|
// 方案Id
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '方案Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('方案ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
programmeId: number; |
||||||
|
|
||||||
|
// 演示项ID
|
||||||
|
@IsNotEmpty({ |
||||||
|
message: '演示项Id不能为空', |
||||||
|
}) |
||||||
|
@Transform((val) => { |
||||||
|
if (Number(val.value)) { |
||||||
|
return Number(val.value); |
||||||
|
} else if(!val.value){ |
||||||
|
return undefined |
||||||
|
}else { |
||||||
|
throw new HttpException('演示项ID必须为数字', 400); |
||||||
|
} |
||||||
|
}) |
||||||
|
demoItemId: number; |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; |
||||||
|
|
||||||
|
export class UserInfoDto { |
||||||
|
// 用户ID
|
||||||
|
@IsString({ |
||||||
|
message: 'headers:userId必须为字符串', |
||||||
|
}) |
||||||
|
userId: string; |
||||||
|
|
||||||
|
// 用户ID
|
||||||
|
@IsString({ |
||||||
|
message: 'headers:token必须为字符串', |
||||||
|
}) |
||||||
|
token: string; |
||||||
|
} |
@ -0,0 +1,320 @@ |
|||||||
|
import { |
||||||
|
Controller, |
||||||
|
Get, |
||||||
|
Post, |
||||||
|
Body, |
||||||
|
Patch, |
||||||
|
Param, |
||||||
|
Delete, |
||||||
|
Query, |
||||||
|
ValidationPipe, |
||||||
|
Req, |
||||||
|
Res, |
||||||
|
Response, |
||||||
|
HttpStatus, HttpException, |
||||||
|
} from '@nestjs/common'; |
||||||
|
import { FastifyRequest } from 'fastify'; |
||||||
|
import { RgvsaleService } from './rgvsale.service'; |
||||||
|
import { GetProductPageDto, CreateProductDto, EditProductDto, DeleteProductDto } from './dto/product.dto'; |
||||||
|
import { getUserinfo, userinfoDto } from '@/Gdecorator/userinfoDecorator/userinfoDecorator.decorator'; |
||||||
|
import { UserInfoDto } from './dto/userInfo.dto'; |
||||||
|
import { CreateAccountDto, EditAccountDto, DeleteAccountDto, GetAccountPageDto } from '@/rgvsale/dto/account.dto'; |
||||||
|
import { |
||||||
|
CreateDemoItemDto, |
||||||
|
DeleteDemoItemDto, |
||||||
|
EditDemoItemDto, |
||||||
|
GetDemoItemListDto, |
||||||
|
GetDemoItemPageDto, |
||||||
|
} from '@/rgvsale/dto/demoItem.dto'; |
||||||
|
import { |
||||||
|
CreateIndustryDto, |
||||||
|
DeleteIndustryDto, |
||||||
|
EditIndustryDto, |
||||||
|
GetIndustryListDto, |
||||||
|
GetIndustryPageDto, |
||||||
|
} from '@/rgvsale/dto/industry.dto'; |
||||||
|
import { |
||||||
|
CreateProgrammeDto, DeleteDemoItemForProgrammeDto, |
||||||
|
DeleteProgrammeDto, |
||||||
|
EditDemoItemsListRankDto, |
||||||
|
EditProgrammeDto, GetDemoItemsPageForProgrammeDto, GetProgrammePageDto, |
||||||
|
} from '@/rgvsale/dto/productsProgramme.dto'; |
||||||
|
import { AddDemoItemAccumulateForSceneDto, CreateScenesDto, DeleteSceneDto } from '@/rgvsale/dto/demoScenes.dto'; |
||||||
|
import { |
||||||
|
ChangeUseStateDto, |
||||||
|
CreateScenesProblemDto, |
||||||
|
DeleteScenesProblemDto, EditScenesProblemDto, |
||||||
|
GetScenesProblemPageDto, |
||||||
|
} from '@/rgvsale/dto/demoScenesProblem.dto'; |
||||||
|
|
||||||
|
@Controller('rgvsale') |
||||||
|
export class RgvsaleController { |
||||||
|
constructor(private readonly rgvsaleService: RgvsaleService) { |
||||||
|
} |
||||||
|
|
||||||
|
//#region 产品
|
||||||
|
|
||||||
|
// 分页获取产品列表
|
||||||
|
// , @getUserinfo() userInfo: UserInfoDto
|
||||||
|
@Get('/products/getPage') |
||||||
|
getProductPage(@Query(new ValidationPipe({ transform: true })) query: GetProductPageDto, @getUserinfo() userInfo: UserInfoDto): Promise<object> { |
||||||
|
return this.rgvsaleService.getProductPage(query); |
||||||
|
} |
||||||
|
|
||||||
|
// 获取产品下拉菜单
|
||||||
|
@Get('/products/getList') |
||||||
|
getProductList(): Promise<object> { |
||||||
|
return this.rgvsaleService.getProductList(); |
||||||
|
} |
||||||
|
|
||||||
|
// 新增产品
|
||||||
|
@Post('/products/createProduct') |
||||||
|
createProduct(@Body() body: CreateProductDto, @getUserinfo() userInfo: UserInfoDto): Promise<object> { |
||||||
|
return this.rgvsaleService.createProduct(body, userInfo); |
||||||
|
} |
||||||
|
|
||||||
|
// 删除产品
|
||||||
|
@Delete('/products/deleteProduct') |
||||||
|
deleteProduct(@Body() body: DeleteProductDto): Promise<object> { |
||||||
|
return this.rgvsaleService.deleteProduct(body); |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑产品
|
||||||
|
@Patch('/products/editProduct') |
||||||
|
editProduct(@Body() body: EditProductDto): Promise<object> { |
||||||
|
return this.rgvsaleService.editProduct(body); |
||||||
|
} |
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 账户
|
||||||
|
|
||||||
|
//分页获取账户列表
|
||||||
|
@Get('/productsAccount/getPage') |
||||||
|
getAccountPage(@Query(new ValidationPipe({ transform: true })) query: GetAccountPageDto): Promise<object> { |
||||||
|
return this.rgvsaleService.getAccountPage(query); |
||||||
|
} |
||||||
|
|
||||||
|
// 创建账户
|
||||||
|
@Post('/productsAccount/createAccount') |
||||||
|
createAccount(@Body() body: CreateAccountDto, @getUserinfo() userInfo: UserInfoDto): Promise<object> { |
||||||
|
return this.rgvsaleService.createAccount(body, userInfo); |
||||||
|
} |
||||||
|
|
||||||
|
// 删除账户
|
||||||
|
@Delete('/productsAccount/deleteAccount') |
||||||
|
deleteAccount(@Body() body: DeleteAccountDto): Promise<object> { |
||||||
|
return this.rgvsaleService.deleteAccount(body); |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑账户
|
||||||
|
@Patch('/productsAccount/editAccount') |
||||||
|
editAccount(@Body() body: EditAccountDto): Promise<object> { |
||||||
|
return this.rgvsaleService.editAccount(body); |
||||||
|
} |
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 演示项
|
||||||
|
|
||||||
|
// 分页查询演示项列表
|
||||||
|
@Get('/demoItems/getPage') |
||||||
|
getDemoPage(@Query(new ValidationPipe({ transform: true })) query: GetDemoItemPageDto, @getUserinfo() userInfo: UserInfoDto): Promise<object> { |
||||||
|
return this.rgvsaleService.getDemoPage(query); |
||||||
|
} |
||||||
|
|
||||||
|
// 获取产品演示项列表
|
||||||
|
@Get('/demoItems/getList') |
||||||
|
getDemoList(@Query() params: GetDemoItemListDto){ |
||||||
|
return this.rgvsaleService.getDemoList(params) |
||||||
|
} |
||||||
|
|
||||||
|
// 添加演示项
|
||||||
|
@Post('/demoItems/createItem') |
||||||
|
createDemo(@Body() body: CreateDemoItemDto, @getUserinfo() userInfo: UserInfoDto) { |
||||||
|
return this.rgvsaleService.createDemo(body, userInfo); |
||||||
|
} |
||||||
|
|
||||||
|
// 删除演示项
|
||||||
|
@Delete('/demoItems/deleteItem') |
||||||
|
deleteDemo(@Body() body: DeleteDemoItemDto) { |
||||||
|
return this.rgvsaleService.deleteDemo(body); |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑演示项
|
||||||
|
@Patch('/demoItems/editItem') |
||||||
|
editDemo(@Body() body: EditDemoItemDto) { |
||||||
|
return this.rgvsaleService.editDemo(body); |
||||||
|
} |
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 文件
|
||||||
|
|
||||||
|
// 文件上传
|
||||||
|
@Post('/upload') |
||||||
|
upload(@Body() body, @getUserinfo() userInfo: UserInfoDto): Promise<object> { |
||||||
|
return this.rgvsaleService.upload(body, userInfo); |
||||||
|
} |
||||||
|
|
||||||
|
// 文件下载
|
||||||
|
@Get('download/:id') |
||||||
|
async download(@Param('id') id: string, @Res() res) { |
||||||
|
return this.rgvsaleService.download(id, res) |
||||||
|
} |
||||||
|
|
||||||
|
// 静态文件
|
||||||
|
@Get('staticFile/:id') |
||||||
|
async staticFile(@Param('id') id: string, @Res() res) { |
||||||
|
return this.rgvsaleService.download(id, res) |
||||||
|
} |
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 行业
|
||||||
|
|
||||||
|
//分页获取行业列表
|
||||||
|
@Get('/productsIndustry/getPage') |
||||||
|
getIndustryPage(@Query() params: GetIndustryPageDto, @getUserinfo() userInfo: UserInfoDto) { |
||||||
|
return this.rgvsaleService.getIndustryPage(params, userInfo) |
||||||
|
} |
||||||
|
|
||||||
|
// 获取行业下拉列表
|
||||||
|
@Get('/productsIndustry/getList') |
||||||
|
getIndustryList(@Query() params: GetIndustryListDto){ |
||||||
|
return this.rgvsaleService.getIndustryList(params) |
||||||
|
} |
||||||
|
|
||||||
|
// 创建行业
|
||||||
|
@Post('/productsIndustry/createIndustry') |
||||||
|
createIndustry(@Body() body: CreateIndustryDto, @getUserinfo() userInfo: UserInfoDto) { |
||||||
|
return this.rgvsaleService.createIndustry(body, userInfo) |
||||||
|
} |
||||||
|
|
||||||
|
// 删除行业
|
||||||
|
@Delete('/productsIndustry/deleteIndustry') |
||||||
|
deleteIndustry(@Body() body: DeleteIndustryDto) { |
||||||
|
return this.rgvsaleService.deleteIndustry(body) |
||||||
|
} |
||||||
|
|
||||||
|
// 编辑行业
|
||||||
|
@Patch('/productsIndustry/editIndustry') |
||||||
|
editIndustry(@Body() body: EditIndustryDto) { |
||||||
|
return this.rgvsaleService.editIndustry(body) |
||||||
|
} |
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 演示项
|
||||||
|
// 获取演示方案树
|
||||||
|
@Get('/productsProgramme/getTree') |
||||||
|
getProductsProgrammeTree(@getUserinfo() userInfo: UserInfoDto){ |
||||||
|
return this.rgvsaleService.getProductsProgrammeTree(userInfo) |
||||||
|
} |
||||||
|
// 新增演示方案
|
||||||
|
@Post('/productsProgramme/createProgramme') |
||||||
|
createProductsProgramme(@Body() body: CreateProgrammeDto, @getUserinfo() userInfo: UserInfoDto){ |
||||||
|
return this.rgvsaleService.createProductsProgramme(body, userInfo) |
||||||
|
} |
||||||
|
// 删除演示方案
|
||||||
|
@Delete('/productsProgramme/deleteProgramme') |
||||||
|
deleteProductsProgramme(@Body() body: DeleteProgrammeDto){ |
||||||
|
return this.rgvsaleService.deleteProductsProgramme(body) |
||||||
|
} |
||||||
|
// 编辑演示方案
|
||||||
|
@Patch('/productsProgramme/editProgramme') |
||||||
|
editProductsProgramme(@Body() body: EditProgrammeDto, @getUserinfo() userInfo: UserInfoDto){ |
||||||
|
return this.rgvsaleService.editProductsProgramme(body, userInfo) |
||||||
|
} |
||||||
|
// 排序方案演示项
|
||||||
|
@Patch('/productsProgramme/editDemoItemsListRank') |
||||||
|
editProductsProgrammeForDemoItemsListRank(@Body() body: EditDemoItemsListRankDto){ |
||||||
|
return this.rgvsaleService.editProductsProgrammeForDemoItemsListRank(body) |
||||||
|
} |
||||||
|
// 获取个人方案分页
|
||||||
|
@Get('/productsProgramme/getPage') |
||||||
|
getProductsProgrammePage(@Query() params: GetProgrammePageDto, @getUserinfo() userInfo: UserInfoDto){ |
||||||
|
return this.rgvsaleService.getProductsProgrammePage(params, userInfo) |
||||||
|
} |
||||||
|
// 获取方案演示项分页
|
||||||
|
@Get('/productsProgramme/getDemoItemPageForProgramme') |
||||||
|
getDemoItemPageForProgramme(@Query() params: GetDemoItemsPageForProgrammeDto){ |
||||||
|
return this.rgvsaleService.getDemoItemPageForProgramme(params) |
||||||
|
} |
||||||
|
// 获取方案演示项列表
|
||||||
|
@Get('/productsProgramme/getDemoItemListForProgramme') |
||||||
|
getDemoItemListForProgramme(@Query() params: GetDemoItemsPageForProgrammeDto){ |
||||||
|
return this.rgvsaleService.getDemoItemListForProgramme(params) |
||||||
|
} |
||||||
|
// 获取我的方案列表
|
||||||
|
@Get('/productsProgramme/getList') |
||||||
|
getProductsProgrammeList(@getUserinfo() userInfo: UserInfoDto){ |
||||||
|
return this.rgvsaleService.getProductsProgrammeList(userInfo) |
||||||
|
} |
||||||
|
// 删除方案演示项
|
||||||
|
@Delete('/productsProgramme/deleteDemoItemForProgramme') |
||||||
|
deleteDemoItemForProgramme(@Body() body: DeleteDemoItemForProgrammeDto){ |
||||||
|
return this.rgvsaleService.deleteDemoItemForProgramme(body) |
||||||
|
} |
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 演示现场 场景
|
||||||
|
|
||||||
|
// 获取我的演示现场
|
||||||
|
@Get('/demoScenes/getList') |
||||||
|
getSceneListAndTree(@getUserinfo() userInfo: UserInfoDto){ |
||||||
|
return this.rgvsaleService.getSceneListAndTree(userInfo) |
||||||
|
} |
||||||
|
|
||||||
|
// 根据ID获取演示现场
|
||||||
|
@Get('/demoScenes/getListById') |
||||||
|
getListById(@Query() params: DeleteSceneDto){ |
||||||
|
return this.rgvsaleService.getListById(params) |
||||||
|
} |
||||||
|
|
||||||
|
// 创建演示现场
|
||||||
|
@Post('/demoScenes/createScene') |
||||||
|
createScene(@Body() body: CreateScenesDto, @getUserinfo() userInfo: UserInfoDto){ |
||||||
|
return this.rgvsaleService.createScene(body, userInfo) |
||||||
|
} |
||||||
|
|
||||||
|
// 删除演示现场
|
||||||
|
@Delete('/demoScenes/deleteScene') |
||||||
|
deleteScene(@Body() body:DeleteSceneDto){ |
||||||
|
return this.rgvsaleService.deleteScene(body) |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
@Patch('/demoScenes/addSceneRecordDemoItem') |
||||||
|
addDemoItemAccumulateForScene(@Body() body: AddDemoItemAccumulateForSceneDto){ |
||||||
|
return this.rgvsaleService.addDemoItemAccumulateForScene(body) |
||||||
|
} |
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region 问题
|
||||||
|
获取问题分页 |
||||||
|
@Get('/demoScenesProblem/getScenesProblemList') |
||||||
|
getScenesProblemList(@Query() params: GetScenesProblemPageDto){ |
||||||
|
return this.rgvsaleService.getScenesProblemList(params) |
||||||
|
} |
||||||
|
创建问题 |
||||||
|
@Post('/demoScenesProblem/createScenesProblem') |
||||||
|
createScenesProblem(@Body() body: CreateScenesProblemDto, @getUserinfo() userInfo: UserInfoDto){ |
||||||
|
return this.rgvsaleService.createScenesProblem(body, userInfo) |
||||||
|
} |
||||||
|
删除问题 |
||||||
|
@Delete('/demoScenesProblem/deleteScenesProblem') |
||||||
|
deleteScenesProblem(@Body() body: DeleteScenesProblemDto){ |
||||||
|
return this.rgvsaleService.deleteScenesProblem(body) |
||||||
|
} |
||||||
|
编辑问题 |
||||||
|
@Patch('/demoScenesProblem/editScenesProblem') |
||||||
|
editScenesProblem(@Body() body: EditScenesProblemDto){ |
||||||
|
return this.rgvsaleService.editScenesProblem(body) |
||||||
|
} |
||||||
|
修改问题利用状态 |
||||||
|
@Patch('/demoScenesProblem/changeUseState') |
||||||
|
changeScenesProblemUseState(@Body() body: ChangeUseStateDto){ |
||||||
|
return this.rgvsaleService.changeScenesProblemUseState(body) |
||||||
|
} |
||||||
|
//#endregion
|
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
import { Module } from '@nestjs/common'; |
||||||
|
import { RgvsaleService } from './rgvsale.service'; |
||||||
|
import { RgvsaleController } from './rgvsale.controller'; |
||||||
|
|
||||||
|
@Module({ |
||||||
|
controllers: [RgvsaleController], |
||||||
|
providers: [RgvsaleService] |
||||||
|
}) |
||||||
|
export class RgvsaleModule {} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@ |
|||||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||||
|
import { INestApplication } from '@nestjs/common'; |
||||||
|
import * as request from 'supertest'; |
||||||
|
import { AppModule } from './../src/app.module'; |
||||||
|
|
||||||
|
describe('AppController (e2e)', () => { |
||||||
|
let app: INestApplication; |
||||||
|
|
||||||
|
beforeEach(async () => { |
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({ |
||||||
|
imports: [AppModule], |
||||||
|
}).compile(); |
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication(); |
||||||
|
await app.init(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('/ (GET)', () => { |
||||||
|
return request(app.getHttpServer()) |
||||||
|
.get('/') |
||||||
|
.expect(200) |
||||||
|
.expect('Hello World!'); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"moduleFileExtensions": ["js", "json", "ts"], |
||||||
|
"rootDir": ".", |
||||||
|
"testEnvironment": "node", |
||||||
|
"testRegex": ".e2e-spec.ts$", |
||||||
|
"transform": { |
||||||
|
"^.+\\.(t|j)s$": "ts-jest" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
import jwt from 'jsonwebtoken' |
||||||
|
|
||||||
|
// const token = 'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjVjMDdlNzRjLTFiYzctNDAzYy05NjUxLTI5YjgzZGM5ZGE4MSJ9.bC2MO_7dzBeuMH33z7l3Wlovl489aja1B1D3SB4ukJmGye_RDZCy5rmDw0tPGsNpbeYryxBG9OgWZSxEAKX5Kg'
|
||||||
|
//
|
||||||
|
const secretkey = 'abcdefghijklmnopqrstuvwxyz' |
||||||
|
console.log(jwt); |
||||||
|
const a = jwt.sign({ secretkey },secretkey, { expiresIn: '30m' }) |
||||||
|
// console.log(a);
|
||||||
|
|
||||||
|
const token = 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjVlOWQ1ZTMzLTI0MjEtNDJkYy1iZmU5LTI5Y2M1YjIxOWU4ZCJ9.VmZzLm2si5d51oe_ZPq9sB7ZF7GLuttjSjxQHdxQg49W4QU6PnLFzhAIeKYx0l4mfKj4uL4_IKyqPsm4t6znHg' |
||||||
|
jwt.verify(token, secretkey, (error, data)=>{ |
||||||
|
console.log(error); |
||||||
|
console.log(data); |
||||||
|
}) |
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"module": "commonjs", |
||||||
|
"declaration": true, |
||||||
|
"removeComments": true, |
||||||
|
"emitDecoratorMetadata": true, |
||||||
|
"experimentalDecorators": true, |
||||||
|
"allowSyntheticDefaultImports": true, |
||||||
|
"target": "es2017", |
||||||
|
"sourceMap": true, |
||||||
|
"outDir": "./dist", |
||||||
|
"baseUrl": "./", |
||||||
|
"incremental": true, |
||||||
|
"skipLibCheck": true, |
||||||
|
"strictNullChecks": false, |
||||||
|
"noImplicitAny": false, |
||||||
|
"strictBindCallApply": false, |
||||||
|
"forceConsistentCasingInFileNames": false, |
||||||
|
"noFallthroughCasesInSwitch": false, |
||||||
|
"paths": { |
||||||
|
"@/*": [ |
||||||
|
"src/*" |
||||||
|
], |
||||||
|
"@CFG/*": [ |
||||||
|
"config/*" |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
Loading…
Reference in new issue