commit 03b043e883cad49df3299a7171b3d63abf5ad9de Author: expressgy Date: Sat Jul 15 18:39:32 2023 +0800 完成 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..259de13 --- /dev/null +++ b/.eslintrc.js @@ -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', + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e706083 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..bb05c7f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 4 +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5aa86c --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Coverage +Discord +Backers on Open Collective +Sponsors on Open Collective + + Support us + +

+ + +## 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). diff --git a/config/development.ts b/config/development.ts new file mode 100644 index 0000000..db60bca --- /dev/null +++ b/config/development.ts @@ -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, + }, +}; diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 0000000..09a2c8b --- /dev/null +++ b/config/index.ts @@ -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]; diff --git a/config/production.ts b/config/production.ts new file mode 100644 index 0000000..63b74e3 --- /dev/null +++ b/config/production.ts @@ -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, + }, +}; diff --git a/config/test.ts b/config/test.ts new file mode 100644 index 0000000..2233785 --- /dev/null +++ b/config/test.ts @@ -0,0 +1,10 @@ +export default { + // 主服务 + master: { + host: '0.0.0.0', + port: '3000', + }, + swagger: { + enable: true, + }, +}; diff --git a/file/11 - 副本.jpg b/file/11 - 副本.jpg new file mode 100644 index 0000000..16b333b Binary files /dev/null and b/file/11 - 副本.jpg differ diff --git a/file/121.mp4 b/file/121.mp4 new file mode 100644 index 0000000..d1f4fb0 Binary files /dev/null and b/file/121.mp4 differ diff --git a/file/docx测试.docx b/file/docx测试.docx new file mode 100644 index 0000000..f00f343 Binary files /dev/null and b/file/docx测试.docx differ diff --git a/file/pdf测试.pdf b/file/pdf测试.pdf new file mode 100644 index 0000000..a650271 Binary files /dev/null and b/file/pdf测试.pdf differ diff --git a/nest-cli.json b/nest-cli.json new file mode 100644 index 0000000..f9aa683 --- /dev/null +++ b/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2457639 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/src/Gdecorator/userinfoDecorator/userinfoDecorator.decorator.ts b/src/Gdecorator/userinfoDecorator/userinfoDecorator.decorator.ts new file mode 100644 index 0000000..387ada9 --- /dev/null +++ b/src/Gdecorator/userinfoDecorator/userinfoDecorator.decorator.ts @@ -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; + }, +); diff --git a/src/Gexceptions/gexceptionsfilter.filter.ts b/src/Gexceptions/gexceptionsfilter.filter.ts new file mode 100644 index 0000000..078210b --- /dev/null +++ b/src/Gexceptions/gexceptionsfilter.filter.ts @@ -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 implements ExceptionFilter { + catch(exception: HttpException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + 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' + }); + } +} diff --git a/src/Ginterceptor/gdevinterceptor.interceptor.ts b/src/Ginterceptor/gdevinterceptor.interceptor.ts new file mode 100644 index 0000000..7742bc7 --- /dev/null +++ b/src/Ginterceptor/gdevinterceptor.interceptor.ts @@ -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 { + 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`, + ); + }), + ); + } +} diff --git a/src/Ginterceptor/gresponseinterceptor.interceptor.ts b/src/Ginterceptor/gresponseinterceptor.interceptor.ts new file mode 100644 index 0000000..6075db8 --- /dev/null +++ b/src/Ginterceptor/gresponseinterceptor.interceptor.ts @@ -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 { + 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', + }); + }), + ); + } +} diff --git a/src/Gservice/GDATABASE/gdatabase.module.ts b/src/Gservice/GDATABASE/gdatabase.module.ts new file mode 100644 index 0000000..cfe03f4 --- /dev/null +++ b/src/Gservice/GDATABASE/gdatabase.module.ts @@ -0,0 +1,8 @@ +import { Global, Module } from '@nestjs/common'; +import { GdatabaseService } from './gdatabase.service'; +@Global() +@Module({ + providers: [GdatabaseService], + exports: [GdatabaseService], +}) +export class GdatabaseModule {} diff --git a/src/Gservice/GDATABASE/gdatabase.service.ts b/src/Gservice/GDATABASE/gdatabase.service.ts new file mode 100644 index 0000000..84ad687 --- /dev/null +++ b/src/Gservice/GDATABASE/gdatabase.service.ts @@ -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; + } +} diff --git a/src/Gservice/GEMAIL/gemail.module.ts b/src/Gservice/GEMAIL/gemail.module.ts new file mode 100644 index 0000000..d3689a8 --- /dev/null +++ b/src/Gservice/GEMAIL/gemail.module.ts @@ -0,0 +1,8 @@ +import { Global, Module } from '@nestjs/common'; +import { GemailService } from './gemail.service'; +@Global() +@Module({ + providers: [GemailService], + exports: [GemailService], +}) +export class GemailModule {} diff --git a/src/Gservice/GEMAIL/gemail.service.ts b/src/Gservice/GEMAIL/gemail.service.ts new file mode 100644 index 0000000..5171a8e --- /dev/null +++ b/src/Gservice/GEMAIL/gemail.service.ts @@ -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" ', // 发送方 + 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 = ` + +
+
+
我们欢迎您使用${this.sysName}
+
您在某些地方请求了邮箱的验证码,如果不是自己操作请修改账户的密码。
+
+
ABCDEF
+
+
此邮件作用于注册
截止邮件发送时间5分钟内使用有效。
+
+
+ `; + 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 = ` + +
+
+
我们欢迎您使用${this.sysName}
+
您在某些地方请求了邮箱的验证码,如果不是自己操作请修改账户的密码。
+
+
${code}
+
+
此邮件作用于账户注册
截止邮件发送时间5分钟内使用有效。
+
+
+ `; + 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 = ` + +
+
+
我们欢迎您使用${this.sysName}
+
您在某些地方请求了邮箱的验证码,如果不是自己操作请修改账户的密码。
+
+
${code}
+
+
此邮件作用于账户登录
截止邮件发送时间5分钟内使用有效。
+
+
+ `; + try { + console.time('EMAIL'); + const resd = await this.senMail(test, email); + console.timeEnd('EMAIL'); + res(resd); + } catch (e) { + rej({ + data: e, + message: '发送邮件失败!', + }); + } + }); + } +} diff --git a/src/Gservice/GLOGGER/glogger.module.ts b/src/Gservice/GLOGGER/glogger.module.ts new file mode 100644 index 0000000..d2ce4ff --- /dev/null +++ b/src/Gservice/GLOGGER/glogger.module.ts @@ -0,0 +1,8 @@ +import { Global, Module } from '@nestjs/common'; +import { GloggerService } from './glogger.service'; +@Global() +@Module({ + providers: [GloggerService], + exports: [GloggerService], +}) +export class GloggerModule {} diff --git a/src/Gservice/GLOGGER/glogger.service.ts b/src/Gservice/GLOGGER/glogger.service.ts new file mode 100644 index 0000000..208b171 --- /dev/null +++ b/src/Gservice/GLOGGER/glogger.service.ts @@ -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('加载日志'); + } +} diff --git a/src/Gservice/GREDIS/gredis.module.ts b/src/Gservice/GREDIS/gredis.module.ts new file mode 100644 index 0000000..3b7e8de --- /dev/null +++ b/src/Gservice/GREDIS/gredis.module.ts @@ -0,0 +1,8 @@ +import { Global, Module } from '@nestjs/common'; +import { GredisService } from './gredis.service'; +@Global() +@Module({ + providers: [GredisService], + exports: [GredisService], +}) +export class GredisModule {} diff --git a/src/Gservice/GREDIS/gredis.service.ts b/src/Gservice/GREDIS/gredis.service.ts new file mode 100644 index 0000000..09d4fc2 --- /dev/null +++ b/src/Gservice/GREDIS/gredis.service.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 +} diff --git a/src/Gservice/GREDIS/testJson.js b/src/Gservice/GREDIS/testJson.js new file mode 100644 index 0000000..e69de29 diff --git a/src/Gservice/GTOOLS/gtools.module.ts b/src/Gservice/GTOOLS/gtools.module.ts new file mode 100644 index 0000000..52736de --- /dev/null +++ b/src/Gservice/GTOOLS/gtools.module.ts @@ -0,0 +1,8 @@ +import { Global, Module } from '@nestjs/common'; +import { GtoolsService } from './gtools.service'; +@Global() +@Module({ + providers: [GtoolsService], + exports: [GtoolsService], +}) +export class GtoolsModule {} diff --git a/src/Gservice/GTOOLS/gtools.service.ts b/src/Gservice/GTOOLS/gtools.service.ts new file mode 100644 index 0000000..9a12df5 --- /dev/null +++ b/src/Gservice/GTOOLS/gtools.service.ts @@ -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 +} diff --git a/src/Gservice/GTOOLS/testToken.js b/src/Gservice/GTOOLS/testToken.js new file mode 100644 index 0000000..31359cc --- /dev/null +++ b/src/Gservice/GTOOLS/testToken.js @@ -0,0 +1,4 @@ +import GtoolsService from './gtools.service' + + +const g = new GtoolsService() \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..ef2262e --- /dev/null +++ b/src/app.module.ts @@ -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 {} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..767b560 --- /dev/null +++ b/src/main.ts @@ -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( + // 加载主程序模块 + 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(); diff --git a/src/rgvsale/dto/account.dto.ts b/src/rgvsale/dto/account.dto.ts new file mode 100644 index 0000000..12dcc42 --- /dev/null +++ b/src/rgvsale/dto/account.dto.ts @@ -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; +} diff --git a/src/rgvsale/dto/demoItem.dto.ts b/src/rgvsale/dto/demoItem.dto.ts new file mode 100644 index 0000000..7474888 --- /dev/null +++ b/src/rgvsale/dto/demoItem.dto.ts @@ -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; +} diff --git a/src/rgvsale/dto/demoScenes.dto.ts b/src/rgvsale/dto/demoScenes.dto.ts new file mode 100644 index 0000000..348d244 --- /dev/null +++ b/src/rgvsale/dto/demoScenes.dto.ts @@ -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; +} \ No newline at end of file diff --git a/src/rgvsale/dto/demoScenesProblem.dto.ts b/src/rgvsale/dto/demoScenesProblem.dto.ts new file mode 100644 index 0000000..15221f4 --- /dev/null +++ b/src/rgvsale/dto/demoScenesProblem.dto.ts @@ -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; +} \ No newline at end of file diff --git a/src/rgvsale/dto/industry.dto.ts b/src/rgvsale/dto/industry.dto.ts new file mode 100644 index 0000000..4d3cd9f --- /dev/null +++ b/src/rgvsale/dto/industry.dto.ts @@ -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; +} diff --git a/src/rgvsale/dto/product.dto.ts b/src/rgvsale/dto/product.dto.ts new file mode 100644 index 0000000..9b1b813 --- /dev/null +++ b/src/rgvsale/dto/product.dto.ts @@ -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; +} diff --git a/src/rgvsale/dto/productsProgramme.dto.ts b/src/rgvsale/dto/productsProgramme.dto.ts new file mode 100644 index 0000000..a07d80e --- /dev/null +++ b/src/rgvsale/dto/productsProgramme.dto.ts @@ -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; +} diff --git a/src/rgvsale/dto/userInfo.dto.ts b/src/rgvsale/dto/userInfo.dto.ts new file mode 100644 index 0000000..75f74de --- /dev/null +++ b/src/rgvsale/dto/userInfo.dto.ts @@ -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; +} diff --git a/src/rgvsale/rgvsale.controller.ts b/src/rgvsale/rgvsale.controller.ts new file mode 100644 index 0000000..4b64dd9 --- /dev/null +++ b/src/rgvsale/rgvsale.controller.ts @@ -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 { + return this.rgvsaleService.getProductPage(query); + } + + // 获取产品下拉菜单 + @Get('/products/getList') + getProductList(): Promise { + return this.rgvsaleService.getProductList(); + } + + // 新增产品 + @Post('/products/createProduct') + createProduct(@Body() body: CreateProductDto, @getUserinfo() userInfo: UserInfoDto): Promise { + return this.rgvsaleService.createProduct(body, userInfo); + } + + // 删除产品 + @Delete('/products/deleteProduct') + deleteProduct(@Body() body: DeleteProductDto): Promise { + return this.rgvsaleService.deleteProduct(body); + } + + // 编辑产品 + @Patch('/products/editProduct') + editProduct(@Body() body: EditProductDto): Promise { + return this.rgvsaleService.editProduct(body); + } + //#endregion + + //#region 账户 + + //分页获取账户列表 + @Get('/productsAccount/getPage') + getAccountPage(@Query(new ValidationPipe({ transform: true })) query: GetAccountPageDto): Promise { + return this.rgvsaleService.getAccountPage(query); + } + + // 创建账户 + @Post('/productsAccount/createAccount') + createAccount(@Body() body: CreateAccountDto, @getUserinfo() userInfo: UserInfoDto): Promise { + return this.rgvsaleService.createAccount(body, userInfo); + } + + // 删除账户 + @Delete('/productsAccount/deleteAccount') + deleteAccount(@Body() body: DeleteAccountDto): Promise { + return this.rgvsaleService.deleteAccount(body); + } + + // 编辑账户 + @Patch('/productsAccount/editAccount') + editAccount(@Body() body: EditAccountDto): Promise { + return this.rgvsaleService.editAccount(body); + } + //#endregion + + //#region 演示项 + + // 分页查询演示项列表 + @Get('/demoItems/getPage') + getDemoPage(@Query(new ValidationPipe({ transform: true })) query: GetDemoItemPageDto, @getUserinfo() userInfo: UserInfoDto): Promise { + 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 { + 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 +} diff --git a/src/rgvsale/rgvsale.module.ts b/src/rgvsale/rgvsale.module.ts new file mode 100644 index 0000000..8b37c16 --- /dev/null +++ b/src/rgvsale/rgvsale.module.ts @@ -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 {} diff --git a/src/rgvsale/rgvsale.service.ts b/src/rgvsale/rgvsale.service.ts new file mode 100644 index 0000000..16e48ad --- /dev/null +++ b/src/rgvsale/rgvsale.service.ts @@ -0,0 +1,2285 @@ +import { HttpException, HttpStatus, Injectable, Response } from '@nestjs/common'; +import { GdatabaseService } from '@/Gservice/GDATABASE/gdatabase.service'; +import { ConfigService } from '@nestjs/config'; +import { GloggerService } from '@/Gservice/GLOGGER/glogger.service'; +import { GetProductPageDto, CreateProductDto, EditProductDto, DeleteProductDto } from './dto/product.dto'; +import { UserInfoDto } from './dto/userInfo.dto'; +import { CreateAccountDto, EditAccountDto, DeleteAccountDto, GetAccountPageDto } from '@/rgvsale/dto/account.dto'; +import { + GetDemoItemPageDto, + CreateDemoItemDto, + EditDemoItemDto, + DeleteDemoItemDto, + GetDemoItemListDto, +} from '@/rgvsale/dto/demoItem.dto'; +import { createWriteStream, createReadStream } from 'fs'; +import {join} from 'path' +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 { of } from 'rxjs'; +import { FastifyRequest } from 'fastify/types/request'; +import { + AddDemoItemAccumulateForSceneDto, + CreateScenesDto, + DeleteSceneDto, + EditSceneDto, +} from '@/rgvsale/dto/demoScenes.dto'; + +import {GetScenesProblemPageDto, + CreateScenesProblemDto, + EditScenesProblemDto, + DeleteScenesProblemDto, + ChangeUseStateDto} from '@/rgvsale/dto/demoScenesProblem.dto' + +@Injectable() +export class RgvsaleService { + + constructor( + // @ 配置文件服务 + private readonly config: ConfigService, + // @ 日志服务 + private readonly logger: GloggerService, + // @ 数据库服务 + private readonly database: GdatabaseService, + ) { + } + + //#region 产品 + + // 分页获取产品列表 + public async getProductPage(params: GetProductPageDto) { + let total = 0; + let rowData = []; + let { pageNum, pageSize } = params; + if (pageNum == 0 || !pageNum) pageNum = 1; + if (pageSize == 0 || !pageSize) pageSize = 10; + try { + const TotalSQL = `SELECT COUNT(*) FROM products_info WHERE isDelete = 0`; + const [rows, results] = await this.database.DB.execute( + TotalSQL, + [], + ); + total = rows[0]['COUNT(*)']; + // const DataSQL = `SELECT * FROM products_info WHERE isDelete = 0 ORDER BY id DESC limit ?,?`; 倒序排列 limit需要在最后 + const DataSQL = `SELECT * FROM products_info WHERE isDelete = 0 ORDER BY id DESC limit ?,?`; + const [datarows, dataresults] = await this.database.DB.execute( + DataSQL, + [(pageNum - 1) * pageSize, pageSize], + ); + rowData = datarows; + + } catch (e) { + return { + data: e, + message: '获取产品出现错误!', + success: false, + }; + } + return { + data: { + total, + pageNum, + pageSize, + rowData, + }, + success: true, + message: '获取产品页面成功!', + }; + } + + // 获取产品下拉菜单 + public async getProductList() { + const SQL = `SELECT * FROM products_info WHERE isDelete = 0 ORDER BY id DESC`; + try { + const [rows, results] = await this.database.DB.execute( + SQL, + [], + ); + return { + data: rows, + success: true, + message: '获取产品列表成功!', + }; + } catch (e) { + return { + data: e, + message: '获取产品出现错误!', + success: false, + }; + } + } + + // 新增产品 + public async createProduct(body: CreateProductDto, userInfo: UserInfoDto) { + try{ + // 重名校验 + const SQL = `SELECT id FROM products_info WHERE productsName = ? AND isDelete = 0;` + const [row] = await this.database.DB.execute(SQL, [body.productName]) + console.log(row); + if(row.length != 0){ + return { + data: {}, + message: '新增产品失败,产品重名!', + success: false, + } + } + }catch (e) { + return { + data: e, + message: '新增产品失败,产品名称查重失败!', + success: false, + } + } + try { + const createTime = new Date(); + const [rows] = await this.database.DB.execute( + `INSERT INTO products_info (productsName, productsDescription, productsUrl, createtime, createuser) VALUES (?, ?, ?, ?, ?);`, + [body.productName, body.productDescription, body.productsUrl, createTime, userInfo.userId], + ); + } catch (e) { + return { + data: e, + message: '新增产品出现错误!', + success: false, + }; + } + return { + data: {}, + message: '新增产品成功!', + success: true, + }; + } + + // 删除产品 + public async deleteProduct(body: DeleteProductDto) { + try { + const [rows] = await this.database.DB.execute( + `UPDATE products_info SET isDelete = 1 WHERE id = ?;`, + [body.productId], + ); + } catch (e) { + return { + data: e, + message: '删除产品出现错误!', + success: false, + }; + } + return { + data: {}, + message: '删除产品成功!', + success: true, + }; + } + + // 编辑产品 + public async editProduct(body: EditProductDto) { + try{ + // 重名校验 + const SQL = `SELECT id FROM products_info WHERE productsName = ? AND isDelete = 0 AND id != ?;` + const [row] = await this.database.DB.execute(SQL, [body.productName, body.productId]) + if(row.length != 0){ + return { + data: {}, + message: '编辑产品失败,产品重名!', + success: false, + } + } + }catch (e) { + return { + data: e, + message: '编辑产品失败,产品名称查重失败!', + success: false, + } + } + try { + const [rows] = await this.database.DB.execute( + `UPDATE products_info SET productsName = ?, productsDescription = ?, productsUrl = ? WHERE id = ?;`, + [body.productName, body.productDescription, body.productUrl, body.productId], + ); + } catch (e) { + return { + data: e, + message: '编辑产品信息出现错误!', + success: false, + }; + } + return { + data: {}, + message: '编辑产品信息成功!', + success: true, + }; + } + + //#endregion + + //#region 账户 + + //分页获取账户列表 + public async getAccountPage(params: GetAccountPageDto) { + let total = 0; + let rowData = []; + let { pageNum, pageSize, productId } = params; + if (pageNum == 0 || !pageNum) pageNum = 1; + if (pageSize == 0 || !pageSize) pageSize = 10; + try { + const TotalSQL = productId + ? `SELECT COUNT(*) FROM products_account WHERE isDelete = 0 AND productsInfoId = ?` + : `SELECT COUNT(*) FROM products_account WHERE isDelete = 0`; + const [rows, results] = productId + ? await this.database.DB.execute(TotalSQL, [params.productId]) + : await this.database.DB.execute(TotalSQL, []) + total = rows[0]['COUNT(*)']; + const DataSQL = productId + ? `SELECT * FROM products_account WHERE isDelete = 0 AND productsInfoId = ? ORDER BY id DESC limit ?,?` + : `SELECT * FROM products_account WHERE isDelete = 0 ORDER BY id DESC limit ?,?` + const [datarows, dataresults] =productId + ? await this.database.DB.execute(DataSQL, [productId, (pageNum - 1) * pageSize, pageSize]) + : await this.database.DB.execute(DataSQL, [(pageNum - 1) * pageSize, pageSize]) + rowData = datarows; + } catch (e) { + return { + data: e, + message: '获取产品账户出现错误!', + success: false, + }; + } + // 获取产品 + const productIdList = Array.from(new Set(rowData.map(i => i.productsInfoId))) + let allProduct = [] + try{ + const setl = productIdList.join(',') + const SQL = `SELECT id, productsName FROM products_info WHERE id IN (${setl})`; + const [row, result] = await this.database.DB.execute(SQL, []); + allProduct = row + }catch (e) { + return { + data: e, + message: '获取产品账户出现错误!', + success: false, + }; + } + // 展开产品 + rowData = rowData.map(i => { + return { + ...i, + productsName: allProduct.filter(j => j.id == i.productsInfoId)[0]?.productsName + } + }) + return { + data: { + total, + pageNum, + pageSize, + rowData, + }, + success: true, + message: '获取产品账户页面成功!', + }; + } + + // 创建账户 + public async createAccount(body: CreateAccountDto, userInfo: UserInfoDto) { + const {productId,accountPassword,accountUsername,accountRoleName,} = body + try{ + // 重名校验 + const SQL = `SELECT id FROM products_account WHERE accountName = ? AND productsInfoId = ? AND isDelete = 0;` + const [row] = await this.database.DB.execute(SQL, [accountUsername, productId]) + if(row.length != 0){ + return { + data: {}, + message: '新增产品账户失败,账户重名!', + success: false, + } + } + }catch (e) { + return { + data: e, + message: '新增产品账户失败,账户名称查重失败!', + success: false, + } + } + try { + const createTime = new Date(); + const [rows] = await this.database.DB.execute( + `INSERT INTO products_account (productsInfoId, accountName, accountPass, accountRole, createtime, createuser) VALUES (?, ?, ?, ?, ?, ?);`, + [body.productId, body.accountUsername, body.accountPassword, body.accountRoleName, createTime, userInfo.userId], + ); + } catch (e) { + return { + data: e, + message: '新增产品账户出现错误!', + success: false, + }; + } + return { + data: {}, + message: '新增产品账户成功!', + success: true, + }; + } + + // 删除账户 + public async deleteAccount(body: DeleteAccountDto) { + try { + const [rows] = await this.database.DB.execute( + `UPDATE products_account SET isDelete = 1 WHERE id = ?;`, + [body.accountId], + ); + } catch (e) { + return { + data: e, + message: '删除产品账户出现错误!', + success: false, + }; + } + return { + data: {}, + message: '删除产品账户成功!', + success: true, + }; + } + + // 编辑账户 + public async editAccount(body: EditAccountDto) { + try{ + // 重名校验 + const SQL = `SELECT id FROM products_account WHERE accountName = ? AND isDelete = 0 AND id != ?;` + const [row] = await this.database.DB.execute(SQL, [body.accountUsername, body.accountId]) + if(row.length != 0){ + return { + data: {}, + message: '编辑产品账户失败,账户重名!', + success: false, + } + } + }catch (e) { + return { + data: {}, + message: '编辑产品账户失败,账户名称查重失败!', + success: false, + } + } + try { + const [rows] = await this.database.DB.execute( + `UPDATE products_account SET accountName = ?, productsInfoId =?, accountPass = ?, accountRole = ? WHERE id = ?;`, + [body.accountUsername,body.productId, body.accountPassword, body.accountRoleName, body.accountId], + ); + } catch (e) { + return { + data: e, + message: '编辑产品账户信息出现错误!', + success: false, + }; + } + return { + data: {}, + message: '编辑产品账户信息成功!', + success: true, + }; + } + + //#endregion + + //#region 演示项 + + // 分页查询演示项列表 + public async getDemoPage(params: GetDemoItemPageDto) { + let total = 0; + let rowData = []; + let { pageNum, pageSize, productId } = params; + if (pageNum == 0 || !pageNum) pageNum = 1; + if (pageSize == 0 || !pageSize) pageSize = 10; + try { + const TotalSQL = productId + ? `SELECT COUNT(*) FROM demo_items WHERE isDelete = 0 AND productsInfoId = ?` + : `SELECT COUNT(*) FROM demo_items WHERE isDelete = 0`; + const [rows, results] = productId + ? await this.database.DB.execute(TotalSQL, [productId]) + : await this.database.DB.execute(TotalSQL, []) + total = rows[0]['COUNT(*)']; + const DataSQL = productId + ? `SELECT * FROM demo_items WHERE isDelete = 0 AND productsInfoId = ? ORDER BY id DESC limit ?,?` + : `SELECT * FROM demo_items WHERE isDelete = 0 ORDER BY id DESC limit ?,?`; + const [datarows, dataresults] = productId + ? await this.database.DB.execute(DataSQL, [productId, (pageNum - 1) * pageSize, pageSize]) + : await this.database.DB.execute(DataSQL, [(pageNum - 1) * pageSize, pageSize]); + rowData = datarows; + } catch (e) { + return { + data: e, + message: '获取演示项列表出现错误!', + success: false, + }; + } + // 获取产品 + const productIdList = Array.from(new Set(rowData.map(i => i.productsInfoId))) + let allProduct = [] + try{ + const setl = productIdList.join(',') + const SQL = `SELECT id, productsName FROM products_info WHERE id IN (${setl})`; + const [row, result] = await this.database.DB.execute(SQL, []); + allProduct = row + }catch (e) { + return { + data: e, + message: '获取产品账户出现错误!', + success: false, + }; + } + // 展开产品 + rowData = rowData.map(i => { + return { + ...i, + productsName: allProduct.filter(j => j.id == i.productsInfoId)[0]?.productsName + } + }) + return { + data: { + total, + pageNum, + pageSize, + rowData: rowData.map(i => { + const obj = {...i}; + obj.itemUrl = '/api/rgvsale/staticFile/' + obj.itemUrl + return obj + }), + }, + success: true, + message: '获取演示项列表成功!', + }; + } + + // 获取演示项下拉列表 + public async getDemoList(params: GetDemoItemListDto){ + const {productId} = params + let SQL, SQLData + if(productId){ + SQL = "SELECT id, itemName FROM demo_items WHERE isDelete = 0 AND productsInfoId = ? ORDER BY id DESC" + SQLData = [productId] + }else{ + SQL = "SELECT id, itemName FROM demo_items WHERE isDelete = 0 ORDER BY id DESC" + SQLData = [] + } + try{ + const [row] = await this.database.DB.execute(SQL, SQLData) + return { + data: row, + message: '获取演示项列表成功!', + success: true, + }; + }catch (e) { + return { + data: e, + message: '获取演示项列表出现错误!', + success: false, + }; + } + } + + // 添加演示项 + public async createDemo(body: CreateDemoItemDto, userInfo: UserInfoDto) { + try{ + // 重名校验 + const SQL = `SELECT id FROM demo_items WHERE itemName = ? AND isDelete = 0;` + const [row] = await this.database.DB.execute(SQL, [body.itemName]) + if(row.length != 0){ + return { + data: {}, + message: '新增产品失败,演示项重名!', + success: false, + } + } + }catch (e) { + return { + data: {}, + message: '新增产品失败,演示项名称查重失败!', + success: false, + } + } + try { + const createTime = new Date(); + const [rows] = await this.database.DB.execute( + `INSERT INTO demo_items (productsInfoId, itemName, itemUrl, createtime, createuser) VALUES (?, ?, ?, ?, ?);`, + [body.productId, body.itemName, body.itemUrl, createTime, userInfo.userId], + ); + } catch (e) { + return { + data: e, + message: '新增演示项出现错误!', + success: false, + }; + } + return { + data: {}, + message: '新增演示项成功!', + success: true, + }; + } + + // 删除演示项 + public async deleteDemo(body: DeleteDemoItemDto) { + try { + const [rows] = await this.database.DB.execute( + `UPDATE demo_items SET isDelete = 1 WHERE id = ?;`, + [body.itemId], + ); + } catch (e) { + return { + data: e, + message: '删除演示项出现错误!', + success: false, + }; + } + return { + data: {}, + message: '删除演示项成功!', + success: true, + }; + } + + // 编辑演示项 + public async editDemo(body: EditDemoItemDto) { + try{ + // 重名校验 + const SQL = `SELECT id FROM demo_items WHERE itemName = ? AND isDelete = 0 AND id != ?;` + const [row] = await this.database.DB.execute(SQL, [body.itemName, body.itemId]) + if(row.length != 0){ + return { + data: {}, + message: '编辑产品失败,演示项重名!', + success: false, + } + } + }catch (e) { + return { + data: {}, + message: '编辑产品失败,演示项名称查重失败!', + success: false, + } + } + try { + const [rows] = await this.database.DB.execute( + `UPDATE demo_items SET productsInfoId =?, itemName = ?, itemUrl = ? WHERE id = ?;`, + [body.productId, body.itemName, body.itemUrl, body.itemId], + ); + } catch (e) { + return { + data: e, + message: '编辑演示项信息出现错误!', + success: false, + }; + } + return { + data: {}, + message: '编辑演示项信息成功!', + success: true, + }; + } + + //#endregion + + //#region 文件 + + // 上传文件 + public async upload(body, userInfo){ + const { file } = body + if(file.length == 0){ + return { + data: {}, + message: '未找到文件!', + success: false, + } + } + try{ + await this.uploadCertificate(file) + console.log(file); + const createtime = new Date() + const SQL = `INSERT INTO file (filename, filesize, createtime, createuser) VALUES (?, ?, ?, ?);` + const [row, results] = await this.database.DB.execute(SQL, [file[0].filename, file[0].data.length, createtime, userInfo.userId]) + return { + data: { + filename: file[0].filename, + filepath: '/api/rgvsale/staticFile/' + row.insertId, + filesize: file[0].data.length + }, + message: '文件上传成功!', + success: true, + } + }catch (e) { + return { + data: e, + message: '文件上传失败!', + success: false, + } + } + } + + // 保存文件 + //重要的是先写入流 然后再读流,把读到的数据append到formData的对象中 + private async uploadCertificate(file) { + const writerStream = createWriteStream(join('./file', `${file[0].filename}`)); + writerStream.write(file[0].data); + await writerStream.end(); + // const readerStream = createReadStream(join(__dirname, `${file[0].filename}`)); + // const fileFormData = new FormData(); + } + + // 文件单位转换 + private formatBytes(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } + + // 下载文件 + public async download(id, res){ + let filename + try{ + filename = await this.getFilePath(id) + }catch (e) { + throw new HttpException( + e, + HttpStatus.SERVICE_UNAVAILABLE, + ); + } + const readerStream = createReadStream(join('./file', filename)) + res.header('Content-Type', 'application/octet-stream') + res.header('Content-Disposition', 'attachment; filename=' + encodeURIComponent(filename)) + // readerStream.pipe(res) + readerStream.on('error', () => { + throw new HttpException( + '下载失败,请重试', + HttpStatus.SERVICE_UNAVAILABLE, + ); + }); + res.send(readerStream) + } + + // 静态文件 + public async staticFile(id, res){ + let filename + try{ + filename = await this.getFilePath(id) + }catch (e) { + throw new HttpException( + e, + HttpStatus.SERVICE_UNAVAILABLE, + ); + } + res.sendFile(join('./file', filename)) + } + + // 获取文件地址 + private async getFilePath(id){ + const SQL = `SELECT filename FROM file WHERE uuid = ? AND isDelete = 0;` + const [row] = await this.database.DB.execute(SQL, [Number(id)]) + if(row.length == 0){ + throw new HttpException( + '下载失败,不存在该文件', + HttpStatus.SERVICE_UNAVAILABLE, + ); + } + return row[0].filename + } + + //#endregion + + //#region行业 + + // 分页获取行业列表 + public async getIndustryPage(params: GetIndustryPageDto, userInfo) { + let total = 0; + let rowData = []; + let { pageNum, pageSize, productId } = params; + if (pageNum == 0 || !pageNum) pageNum = 1; + if (pageSize == 0 || !pageSize) pageSize = 10; + try { + const TotalSQL = productId + ? `SELECT COUNT(*) FROM products_industry WHERE isDelete = 0 AND productsInfoId = ?` + : `SELECT COUNT(*) FROM products_industry WHERE isDelete = 0`; + const [rows, results] = productId + ? await this.database.DB.execute(TotalSQL, [params.productId]) + : await this.database.DB.execute(TotalSQL, []) + total = rows[0]['COUNT(*)']; + const DataSQL = productId + ? `SELECT * FROM products_industry WHERE isDelete = 0 AND productsInfoId = ? ORDER BY id DESC limit ?,?` + : `SELECT * FROM products_industry WHERE isDelete = 0 ORDER BY id DESC limit ?,?` + const [datarows, dataresults] =productId + ? await this.database.DB.execute(DataSQL, [productId, (pageNum - 1) * pageSize, pageSize]) + : await this.database.DB.execute(DataSQL, [(pageNum - 1) * pageSize, pageSize]) + console.log(DataSQL, (pageNum - 1) * pageSize, pageSize); + rowData = datarows; + } catch (e) { + return { + data: e, + message: '获取行业页面出现错误!', + success: false, + }; + } + const productIdList = Array.from(new Set(rowData.map(i => i.productsInfoId))) + let allProduct = [] + try{ + const setl = productIdList.join(',') + const SQL = `SELECT id, productsName FROM products_info WHERE id IN (${setl})`; + const [row, result] = await this.database.DB.execute(SQL, []); + allProduct = row + }catch (e) { + return { + data: e, + message: '获取产品账户出现错误!', + success: false, + }; + } + // 展开产品 + rowData = rowData.map(i => { + return { + ...i, + productsName: allProduct.filter(j => j.id == i.productsInfoId)[0]?.productsName + } + }) + return { + data: { + total, + pageNum, + pageSize, + rowData: rowData.map(i => { + const obj = {...i}; + obj.sqlId = '/api/rgvsale/staticFile/' + obj.sqlId + return obj + }), + }, + success: true, + message: '获取行业页面成功!', + }; + } + + // 获取行业下拉列表 + public async getIndustryList(params: GetIndustryListDto) { + const {productId} = params + if(productId){ + const SQL = `SELECT * FROM products_industry WHERE productsInfoId = ? AND isDelete = 0 ORDER BY id DESC`; + try { + const [rows, results] = await this.database.DB.execute( + SQL, + [productId], + ); + return { + data: rows, + success: true, + message: '获取行业列表成功!', + }; + } catch (e) { + return { + data: e, + message: '获取行业列表出现错误!', + success: false, + }; + } + }else{ + const SQL = `SELECT * FROM products_industry WHERE isDelete = 0 ORDER BY id DESC`; + try { + const [rows, results] = await this.database.DB.execute( + SQL, + [], + ); + return { + data: rows, + success: true, + message: '获取行业列表成功!', + }; + } catch (e) { + return { + data: e, + message: '获取行业列表出现错误!', + success: false, + }; + } + } + } + + // 创建行业 + public async createIndustry(body: CreateIndustryDto, userInfo: UserInfoDto) { + try{ + // 重名校验 + const SQL = `SELECT id FROM products_industry WHERE industryName = ? AND isDelete = 0;` + const [row] = await this.database.DB.execute(SQL, [body.industryName]) + if(row.length != 0){ + return { + data: {}, + message: '新增行业失败,行业重名!', + success: false, + } + } + }catch (e) { + return { + data: e, + message: '新增行业失败,行业名称查重失败!', + success: false, + } + } + try { + const createTime = new Date(); + const [rows] = await this.database.DB.execute( + `INSERT INTO products_industry (productsInfoId, industryName, industryDescription, sqlId, createtime, createuser) VALUES (?, ?, ?, ?, ?, ?);`, + [body.productId, body.industryName, body.industryDescription, body.sqlId, createTime, userInfo.userId], + ); + } catch (e) { + return { + data: e, + message: '新增行业出现错误!', + success: false, + }; + } + return { + data: {}, + message: '新增行业成功!', + success: true, + }; + } + + // 删除行业 + public async deleteIndustry(body: DeleteIndustryDto) { + try { + const [rows] = await this.database.DB.execute( + `UPDATE products_industry SET isDelete = 1 WHERE id = ?;`, + [body.industryId], + ); + } catch (e) { + return { + data: e, + message: '删除行业出现错误!', + success: false, + }; + } + return { + data: {}, + message: '删除行业成功!', + success: true, + }; + } + + // 编辑行业 + public async editIndustry(body: EditIndustryDto) { + try{ + // 重名校验 + const SQL = `SELECT id FROM products_industry WHERE industryName = ? AND isDelete = 0 AND id != ?;` + const [row] = await this.database.DB.execute(SQL, [body.industryName, body.industryId]) + if(row.length != 0){ + return { + data: {}, + message: '编辑行业失败,行业重名!', + success: false, + } + } + }catch (e) { + return { + data: {}, + message: '编辑行业失败,行业名称查重失败!', + success: false, + } + } + try { + const [rows] = await this.database.DB.execute( + `UPDATE products_industry SET industryName = ?, productsInfoId =?, industryDescription = ?, sqlId = ? WHERE id = ?;`, + [body.industryName, body.productId, body.industryDescription, body.productId, body.industryId], + ); + } catch (e) { + return { + data: e, + message: '编辑行业信息出现错误!', + success: false, + }; + } + return { + data: {}, + message: '编辑行业信息成功!', + success: true, + }; + } + + //#endregion + + //#region 演示方案 + // 获取演示方案树 + public async getProductsProgrammeTree(userInfo: UserInfoDto){ + const {userId} = userInfo + const Errmessage = { + data: {}, + message: '获取演示方案出现错误!', + success: false, + } + // 查个人所有方案 + let allProgramme = [] + try{ + const SQL = `SELECT * FROM demo_programmes WHERE createuser = ? AND isDelete = 0 ORDER BY id DESC` + const [row] = await this.database.DB.execute(SQL, [userId]); + allProgramme = row + allProgramme = allProgramme.map(i => { + return{ + AXID: (new Date().getTime().toString() + Math.random() * 100000).toString(), + label: i.programmeName, + ...i, + } + }) + if(row.length == 0){ + return{ + data: [], + message: '获取演示方案成功!', + success: true, + } + } + }catch (e) { + Errmessage.data = e + return Errmessage + } + // 查方案所有产品 + let allProductId = Array.from(new Set(allProgramme.map(i => i.productsInfoId))) + let allProduct = [] + try{ + const setl = allProductId.join(',') + const SQL = `SELECT id, productsName FROM products_info WHERE id IN (${setl})`; + const [row, result] = await this.database.DB.execute(SQL, []); + allProduct = row + }catch (e) { + Errmessage.data = e + return Errmessage + } + // 查方案所有行业 + let allIndustryId = Array.from(new Set(allProgramme.map(i => i.industryId))) + let allIndustry = [] + try{ + const setl = allIndustryId.join(',') + const SQL = `SELECT id, industryName FROM products_industry WHERE id IN (${setl})`; + const [row] = await this.database.DB.execute(SQL, []); + allIndustry = row + }catch (e) { + Errmessage.data = e + return Errmessage + } + // 查所有演示项 + let allDemoItemId = Array.from(new Set(allProgramme.map(i => JSON.parse(i.demoItemsList)).flat())) + let allDemoItem = [] + try{ + const setl = allDemoItemId.join(',') + const SQL = `SELECT id, itemName, itemUrl FROM demo_items WHERE id IN (${setl})`; + const [row] = await this.database.DB.execute(SQL, []); + allDemoItem = row + allDemoItem = allDemoItem.map(i => { + return{ + AZID: (new Date().getTime().toString() + Math.random() * 100000).toString(), + label: i.itemName, + ...i, + } + }) + }catch (e) { + Errmessage.data = e + return Errmessage + } + let newDemoList = [] + allDemoItemId.map(i => { + newDemoList.push(allDemoItem[allDemoItem.findIndex(j => { + return i == j.id + })]) + }) + allDemoItem = newDemoList + // 查账户 + let allAccount = [] + try{ + const setl = allProductId.join(',') + const SQL = `SELECT id, productsInfoId, accountName, accountPass, accountRole FROM products_account WHERE productsInfoId IN (${setl}) AND isDelete = 0;`; + const [row, result] = await this.database.DB.execute(SQL, []); + allAccount = row + }catch (e) { + Errmessage.data = e + return Errmessage + } + allProgramme = allProgramme.map(i => { + // 展开产品信息 + const productName = allProduct.filter(j => j.id == i.productsInfoId) + i.productName = productName[0] ? productName[0].productsName : null; + // 展开行业信息 + const industryName = allIndustry.filter(j => j.id == i.industryId) + i.industryName = industryName[0] ? industryName[0].industryName : null; + // 展开账户信息 + const accountList = allAccount.filter(j => i.productsInfoId == j.productsInfoId) + i.accountList = accountList; + // 展开演示项 + i.children = JSON.parse(i.demoItemsList).map(j => { + const demoData = allDemoItem.filter(k => { + return k.id == j + }) + const obj = { + baba:{...i}, + ...demoData[0] + } + obj.itemUrl = '/api/rgvsale/staticFile/' + obj.itemUrl + obj.AXID = (new Date().getTime().toString() + Math.random() * 100000).toString() + return obj + }) + return i + }) + let programmeTree = [] + programmeTree = allProduct.map(i => { + const programmeItem = allProgramme.filter(j => i.id == j.productsInfoId) + return { + productId: i.id, + productName: i.productsName, + AXID:i.id, + label: i.productsName, + children: programmeItem, + } + }) + return{ + data: programmeTree, + message: '获取演示方案成功!', + success: true, + } + } + + // 查找我的方案列表 + public async getProductsProgrammeList(userInfo:UserInfoDto){ + const {userId} = userInfo + // 查个人所有方案 + try{ + const SQL = `SELECT id, programmeName FROM demo_programmes WHERE createuser = ? AND isDelete = 0 ORDER BY id DESC` + const [row] = await this.database.DB.execute(SQL, [userId]); + return { + data: row, + message: '查找我的演示方案列表成功!', + success: true + } + }catch (e) { + return { + data: e, + message: '获取演示方案出现错误!', + success: false, + } + } + } + + // 获取个人方案分页 + public async getProductsProgrammePage(params: GetProgrammePageDto, userInfo:UserInfoDto){ + function ErrReturn(e){ + return { + data: e, + message: '获取演示方案出现错误!', + success: false, + } + } + let total = 0; + let allProgramme = []; + let { pageNum, pageSize, productId, industryId } = params; + let { userId } = userInfo + if (pageNum == 0 || !pageNum) pageNum = 1; + if (pageSize == 0 || !pageSize) pageSize = 10; + try { + let TotalSQL, TotalSQLParams, DataSQL, DataSQLParams; + if(productId != undefined && industryId == undefined){ + TotalSQL = "SELECT COUNT(*) FROM demo_programmes WHERE isDelete = 0 AND productsInfoId = ? AND createuser = ?" + DataSQL = "SELECT * FROM demo_programmes WHERE createuser = ? AND isDelete = 0 AND productsInfoId = ? ORDER BY id DESC limit ?,?" + TotalSQLParams = [productId, userId] + DataSQLParams = [userId, productId, (pageNum - 1) * pageSize, pageSize] + }else if(productId == undefined && industryId != undefined){ + TotalSQL = "SELECT COUNT(*) FROM demo_programmes WHERE isDelete = 0 AND industryId = ? AND createuser = ?" + DataSQL = "SELECT * FROM demo_programmes WHERE createuser = ? AND isDelete = 0 AND industryId = ? ORDER BY id DESC limit ?,?" + TotalSQLParams = [industryId, userId] + DataSQLParams = [userId, industryId, (pageNum - 1) * pageSize, pageSize] + }else if(productId != undefined && industryId != undefined) { + TotalSQL = "SELECT COUNT(*) FROM demo_programmes WHERE isDelete = 0 AND industryId = ? AND productsInfoId = ? AND createuser = ?" + DataSQL = "SELECT * FROM demo_programmes WHERE createuser = ? AND isDelete = 0 AND industryId = ? AND productsInfoId = ? ORDER BY id DESC limit ?,?" + TotalSQLParams = [industryId, productId, userId] + DataSQLParams = [userId, industryId, productId, (pageNum - 1) * pageSize, pageSize] + }else if(productId == undefined && industryId == undefined){ + TotalSQL = "SELECT COUNT(*) FROM demo_programmes WHERE isDelete = 0 AND createuser = ?" + DataSQL = "SELECT * FROM demo_programmes WHERE createuser = ? AND isDelete = 0 ORDER BY id DESC limit ?,?" + TotalSQLParams = [userId] + DataSQLParams = [userId, (pageNum - 1) * pageSize, pageSize] + } + const [rows, results] = await this.database.DB.execute(TotalSQL, TotalSQLParams) + total = rows[0]['COUNT(*)']; + const [datarows, dataresults] = await this.database.DB.execute(DataSQL, DataSQLParams) + allProgramme = datarows; + if(allProgramme.length == 0){ + return { + data: { + total, + pageNum, + pageSize, + rowData: [], + }, + success: true, + message: '获取演示方案页面成功!', + } + } + } catch (e) { + return ErrReturn(e) + } + // 查方案所有产品 + let allProductId = Array.from(new Set(allProgramme.map(i => i.productsInfoId))) + let allProduct = [] + try{ + const setl = allProductId.join(',') + const SQL = `SELECT id, productsName FROM products_info WHERE id IN (${setl})`; + const [row, result] = await this.database.DB.execute(SQL, []); + allProduct = row + }catch (e) { + return ErrReturn(e) + } + // 查方案所有行业 + let allIndustryId = Array.from(new Set(allProgramme.map(i => i.industryId))) + let allIndustry = [] + try{ + const setl = allIndustryId.join(',') + const SQL = `SELECT id, industryName FROM products_industry WHERE id IN (${setl})`; + const [row] = await this.database.DB.execute(SQL, []); + allIndustry = row + }catch (e) { + return ErrReturn(e) + } + allProgramme = allProgramme.map(i => { + // 展开产品信息 + const productName = allProduct.filter(j => j.id == i.productsInfoId) + i.productName = productName[0] ? productName[0].productsName : null; + // 展开行业信息 + const industryName = allIndustry.filter(j => j.id == i.industryId) + i.industryName = industryName[0] ? industryName[0].industryName : null; + return i + }) + allProgramme = allProgramme.map(i => { + i.demoItemsList = JSON.parse(i.demoItemsList) + return i + }) + return { + data: { + total, + pageNum, + pageSize, + rowData: allProgramme, + }, + success: true, + message: '获取演示方案页面成功!', + }; + } + + // 获取方案演示项分页 + public async getDemoItemPageForProgramme(params: GetDemoItemsPageForProgrammeDto){ + const { programmeId, pageNum, pageSize} = params; + // 查方案演示项 + let demoItemsIdList + let total + try{ + const SQL = "SELECT demoItemsList FROM demo_programmes WHERE id = ? AND isDelete = 0;" + const [rows, results] = await this.database.DB.execute(SQL, [programmeId]) + if(rows.length == 0){ + return { + data: {}, + message: '未找到演示方案!', + success: false + } + } + total = JSON.parse(rows[0].demoItemsList).length + demoItemsIdList = JSON.parse(rows[0].demoItemsList).splice((pageNum - 1) * pageSize, pageSize) + if(demoItemsIdList.length == 0){ + return { + data: { + total, + pageNum, + pageSize, + rowData: [], + }, + message: '查找演示方案演示项页面成功!', + success: true + } + } + }catch (e) { + return { + data: e, + message: '查找演示方案演示项页面出现错误!', + success: false + } + } + let demoList + try{ + const setl = demoItemsIdList.join(',') + const SQL = `SELECT * FROM demo_items WHERE id IN (${setl})`; + const [row] = await this.database.DB.execute(SQL, []); + demoList = row + }catch (e) { + return { + data: e, + message: '查找演示方案演示项页面出现错误!', + success: false + } + } + demoList = demoList.map(i => { + i.itemUrl = '/api/rgvsale/staticFile/' + i.itemUrl + return i + }) + let newDemoList = [] + demoItemsIdList.map(i => { + newDemoList.push(demoList[demoList.findIndex(j => { + return i == j.id + })]) + }) + return { + data: { + total, + pageNum, + pageSize, + rowData: newDemoList, + }, + message: '查找演示方案演示项页面成功!', + success: true + } + } + + // 获取方案演示项列表 + public async getDemoItemListForProgramme(params: GetDemoItemsPageForProgrammeDto){ + const { programmeId } = params; + // 查方案演示项 + let demoItemsIdList + let total + try{ + const SQL = "SELECT demoItemsList FROM demo_programmes WHERE id = ? AND isDelete = 0;" + const [rows, results] = await this.database.DB.execute(SQL, [programmeId]) + if(rows.length == 0){ + return { + data: {}, + message: '未找到演示方案!', + success: false + } + } + total = JSON.parse(rows[0].demoItemsList).length + demoItemsIdList = JSON.parse(rows[0].demoItemsList) + if(demoItemsIdList.length == 0){ + return { + data: [], + message: '查找演示方案演示项列表成功!', + success: true + } + } + }catch (e) { + return { + data: e, + message: '查找演示方案演示项列表出现错误!', + success: false + } + } + let demoList + try{ + const setl = demoItemsIdList.join(',') + const SQL = `SELECT * FROM demo_items WHERE id IN (${setl})`; + const [row] = await this.database.DB.execute(SQL, []); + demoList = row + }catch (e) { + return { + data: e, + message: '查找演示方案演示项列表出现错误!', + success: false + } + } + demoList = demoList.map(i => { + i.itemUrl = '/api/rgvsale/staticFile/' + i.itemUrl + return i + }) + let newDemoList = [] + demoItemsIdList.map(i => { + newDemoList.push(demoList[demoList.findIndex(j => { + return i == j.id + })]) + }) + return { + data: newDemoList, + message: '查找演示方案演示项列表成功!', + success: true + } + } + + + // 新增演示方案 + public async createProductsProgramme(body: CreateProgrammeDto, userInfo){ + const {industryId, productId, programmeName, demoItemsList} = body + const {userId} = userInfo + try{ + // 重名校验 + const SQL = `SELECT id FROM demo_programmes WHERE industryId = ? AND productsInfoId = ? AND programmeName = ? AND createuser = ? AND isDelete = 0;` + const [row] = await this.database.DB.execute(SQL, [industryId, productId, programmeName, userId]) + if(row.length != 0){ + return { + data: {}, + message: '新增方案失败,方案重名!', + success: false, + } + } + }catch (e) { + return { + data: e, + message: '新增方案失败,方案名称查重失败!', + success: false, + } + } + try{ + const createtime = new Date() + const SQL = `INSERT INTO demo_programmes (productsInfoId, industryId, programmeName, demoItemsList, createtime, createuser) VALUES (?, ?, ?, ?, ?, ?)`; + const [row] = await this.database.DB.execute(SQL,[productId, industryId, programmeName, JSON.stringify(demoItemsList), createtime, userId]) + }catch (e) { + return { + data: e, + message: '新增方案出现错误!', + success: false, + } + } + return{ + data:{}, + message: '新增方案成功!', + success: true, + } + } + + // 删除演示方案 + public async deleteProductsProgramme(body: DeleteProgrammeDto){ + try { + const [rows] = await this.database.DB.execute( + `UPDATE demo_programmes SET isDelete = 1 WHERE id = ?;`, + [body.programmeId], + ); + } catch (e) { + return { + data: e, + message: '删除方案出现错误!', + success: false, + }; + } + return { + data: {}, + message: '删除方案成功!', + success: true, + }; + } + + // 编辑演示方案 + public async editProductsProgramme(body: EditProgrammeDto, userInfo:UserInfoDto){ + const {industryId, productId, programmeName, demoItemsList, programmeId} = body + const {userId} = userInfo + try{ + // 重名校验 + const SQL = `SELECT id FROM demo_programmes WHERE industryId = ? AND productsInfoId = ? AND programmeName = ? AND createuser = ? AND id != ? AND isDelete = 0;` + const [row] = await this.database.DB.execute(SQL, [industryId, productId, programmeName, userId, programmeId]) + if(row.length != 0){ + return { + data: {}, + message: '编辑方案失败,方案重名!', + success: false, + } + } + }catch (e) { + return { + data: e, + message: '编辑方案失败,方案名称查重失败!', + success: false, + } + } + try{ + const SQL = `UPDATE demo_programmes SET productsInfoId = ?, industryId =?, programmeName = ?, demoItemsList = ? WHERE id = ?;` + const [row] = await this.database.DB.execute(SQL, [productId, industryId, programmeName, JSON.stringify(demoItemsList), programmeId]) + }catch (e) { + return { + data: e, + message: '编辑方案出现错误!', + success: false, + } + } + return{ + data:{}, + message: '编辑方案成功!', + success: true, + } + } + + // 排序方案演示项 + public async editProductsProgrammeForDemoItemsListRank(body: EditDemoItemsListRankDto){ + const { programmeId, demoItemId, offset } = body + let demoItemsList + try{ + const SQL = `SELECT demoItemsList FROM demo_programmes WHERE id = ? AND isDelete = 0;` + const [row] = await this.database.DB.execute(SQL, [programmeId]) + if(row.length != 1){ + return{ + data: {}, + message: '未找到演示方案!', + success: false, + } + }else{ + demoItemsList = JSON.parse(row[0].demoItemsList) + } + }catch (e) { + return{ + data:e, + message: '编辑演示方案演示项顺序出现错误!', + success: false, + } + } + if(!demoItemsList.includes(demoItemId)){ + return{ + data: {}, + message: '编辑演示方案演示项顺序失败,未找到关联的演示项!', + success: false, + } + } + const index = demoItemsList.indexOf(demoItemId) + if(offset == 0){ + if(index == demoItemsList.length - 1){ + return{ + data: {}, + message: '该演示项已在最后,无法向后排序!', + success: false, + } + } + let afterItem = demoItemsList[index + 1] + demoItemsList.splice(index, 1, afterItem) + demoItemsList.splice(index + 1, 1, demoItemId) + }else if(offset == 1){ + if(index == 0){ + return{ + data: {}, + message: '该演示项已在最前,无法向前排序!', + success: false, + } + } + let beforeItem = demoItemsList[index - 1] + demoItemsList.splice(index - 1, 1, demoItemId) + demoItemsList.splice(index, 1, beforeItem) + }else{ + return{ + data: {}, + message: '编辑演示方案演示项顺序失败,无法识别的偏移量!', + success: false, + } + } + try{ + const SQL = `UPDATE demo_programmes SET demoItemsList = ? WHERE id = ?;` + const [row] = await this.database.DB.execute(SQL, [JSON.stringify(demoItemsList), programmeId]) + }catch (e) { + return { + data: e, + message: '编辑演示方案演示项顺序出现错误!', + success: false, + } + } + return{ + data:{}, + message: '编辑演示方案演示项顺序成功!', + success: true, + } + } + + // 删除方案演示项 + public async deleteDemoItemForProgramme(body: DeleteDemoItemForProgrammeDto){ + const {demoItemId, programmeId} = body + // 获取演示项列表 + let demoItemIdList + try{ + const SQL = "SELECT demoItemsList FROM demo_programmes WHERE id =?;" + const [row] = await this.database.DB.execute(SQL, [programmeId]); + if(row.length == 0){ + return { + data: {}, + message: '未找到该演示方案!', + success: false + } + } + demoItemIdList = JSON.parse(row[0].demoItemsList) + if(demoItemIdList.length == 0){ + return { + data: {}, + message: '该方案不存在演示项!', + success: false + } + } + }catch (e) { + return { + data: e, + message: '删除方案演示项出现错误!', + success: false, + } + } + const index = demoItemIdList.indexOf(demoItemId) + if(index > -1){ + demoItemIdList.splice(index, 1) + } + try{ + const SQL = `UPDATE demo_programmes SET demoItemsList = ? WHERE id = ?;` + const [row] = await this.database.DB.execute(SQL, [JSON.stringify(demoItemIdList), programmeId]) + }catch (e) { + return { + data: e, + message: '删除方案演示项出现错误!', + success: false, + } + } + return { + data: {}, + message: '删除方案演示项成功!', + success: true + } + } + //#endregion + + //#region 演示现场 场景 + + // 获取我的演示现场 + public async getSceneListAndTree(userInfo: UserInfoDto){ + const {userId} = userInfo + function ErrReturn(e, message = '获取演示现场出现错误!') { + return{ + data:e, + message, + success: false, + } + } + // 查演示现场 + let allDemoScenes + try{ + const SQL = "SELECT * FROM demo_scenes WHERE createuser = ? AND isDelete = 0 ORDER BY id DESC;" + const [rows] = await this.database.DB.execute(SQL, [userId]) + if(rows.length == 0){ + return { + data:{ + productTree: null, + industryTree: null, + programmeTree: null, + demoSceneList: null + }, + success: true, + message: '获取演示现场成功!' + } + } + allDemoScenes = rows + }catch (e) { + return ErrReturn(e) + } + // 数据中的方案ID列表 + const programmeIdList = Array.from(new Set(allDemoScenes.map(i => i.programmeId))) + let programmeList + // 查演示方案 + try{ + const setl = programmeIdList.join(',') + const SQL = `SELECT id, programmeName, productsInfoId, industryId, demoItemsList FROM demo_programmes WHERE id IN (${setl});` + const [rows] = await this.database.DB.execute(SQL, []) + programmeList = rows + if(rows.length == 0) return ErrReturn({}, `未找到相应的演示方案, 方案ID:${programmeIdList}!`) + }catch (e) { + return ErrReturn(e) + } + // 产品中的产品和行业 + const productIdList = Array.from(new Set(programmeList.map(i => i.productsInfoId))) + const industryIdList = Array.from(new Set(programmeList.map(i => i.industryId))) + let productList, industryList + // 查产品 + try{ + const setl = productIdList.join(',') + const SQL = `SELECT id, productsName FROM products_info WHERE id IN (${setl});` + const [rows] = await this.database.DB.execute(SQL, []) + productList = rows + if(rows.length == 0) return ErrReturn({}, `未找到相应的产品, 产品ID:${productIdList}!`) + }catch (e) { + return ErrReturn(e) + } + // 查行业 + try{ + const setl = industryIdList.join(',') + const SQL = `SELECT id, industryName, productsInfoId FROM products_industry WHERE id IN (${setl});` + const [rows] = await this.database.DB.execute(SQL, []) + industryList = rows + if(rows.length == 0) return ErrReturn({}, `未找到相应的行业,行业ID:${industryIdList}!`) + }catch (e) { + return ErrReturn(e) + } + // 查账户 + let allAccount = [] + try{ + const setl = productIdList.join(',') + const SQL = `SELECT id, productsInfoId, accountName, accountPass, accountRole FROM products_account WHERE productsInfoId IN (${setl}) AND isDelete = 0;`; + const [row, result] = await this.database.DB.execute(SQL, []); + allAccount = row + }catch (e) { + return ErrReturn(e) + } + // 为方案展开产品和行业 + programmeList = programmeList.map(i => { + const accountList = allAccount.filter(j => i.productsInfoId == j.productsInfoId) + const productData = productList.filter(j => j.id == i.productsInfoId) + const industryData = industryList.filter(j => j.id == i.industryId) + return{ + ...i, + accountList, + productName: productData[0]?.productsName, + industryName: industryData[0]?.industryName, + AXID: (new Date().getTime().toString() + Math.random() * 100000).toString(), + label: i.programmeName + } + }) + // 为现场展开方案 + allDemoScenes = allDemoScenes.map(i => { + const {productName,industryName,programmeName,demoItemsList, productsInfoId, industryId} = programmeList.filter(j => j.id == i.programmeId)[0] + return { + ...i, + productName, + industryName, + programmeName, + demoItemsList:JSON.parse(demoItemsList), + industryId, + productId: productsInfoId, + AXID:(new Date().getTime().toString() + Math.random() * 100000).toString(), + label: i.sceneName + } + }) + // 获取第一个演示项 + const demoItemsIdListForFirst = Array.from(new Set(allDemoScenes.map(i => i.demoItemsList[0]))).filter(i => i!== undefined) + let demoItemList + // 查演示项内容 + try{ + const setl = demoItemsIdListForFirst.join(',') + const SQL = `SELECT id, itemName, itemUrl, accumulate FROM demo_items WHERE id IN (${setl});` + const [rows] = await this.database.DB.execute(SQL, []) + demoItemList = rows + }catch (e) { + return ErrReturn(e) + } + // 展开演示项 + allDemoScenes = allDemoScenes.map(i => { + const demoItemData = demoItemList.filter(j => j.id == i.demoItemsList[0]) + if(demoItemData.length == 0){ + return { + ...i, + demoItemData:{ + id: '', itemName: '', itemUrl:'' + } + } + }else{ + return { + ...i, + demoItemData: { + id: demoItemData[0].id, + itemName: demoItemData[0].itemName, + itemUrl: '/api/rgvsale/staticFile/' + demoItemData[0].itemUrl, + accumulate: demoItemData[0].accumulate + } + } + } + }) + // 按方案成树 + let programmeTree = programmeList.map(i => { + return{ + ...i, + children: allDemoScenes.filter(j => i.id == j.programmeId) + } + }) + // 按行业成树 + const industryTree = industryList.map(i => { + return { + ...i, + children: programmeTree.filter(j => i.id == j.industryId), + AXID:(new Date().getTime().toString() + Math.random() * 100000).toString(), + label: i.industryName + } + }) + // 按产品分 + const productTree = productList.map(i => { + return{ + ...i, + children: industryTree.filter(j => i.id == j.productsInfoId), + AXID:(new Date().getTime().toString() + Math.random() * 100000).toString(), + label: i.productsName + } + }) + + return { + data:{ + productTree, + industryTree, + programmeTree, + demoSceneList:allDemoScenes + }, + message:'获取演示现场成功!', + success: true, + } + } + + // 根据ID获取演示现场 + public async getListById(params: DeleteSceneDto){ + const {sceneId} = params + function ErrReturn(e, message = '获取演示现场出现错误!') { + return{ + data:e, + message, + success: false, + } + } + // 查演示现场 + let allDemoScenes + try{ + const SQL = "SELECT * FROM demo_scenes WHERE id = ? AND isDelete = 0 ORDER BY id DESC;" + const [rows] = await this.database.DB.execute(SQL, [sceneId]) + if(rows.length == 0){ + return { + data:{}, + success: true, + message: '获取演示现场成功!' + } + } + allDemoScenes = rows + }catch (e) { + return ErrReturn(e) + } + // 获取方案 + // 数据中的方案ID列表 + let programme + // 查演示方案 + try{ + const SQL = `SELECT id, programmeName, productsInfoId, industryId, demoItemsList FROM demo_programmes WHERE id = ?;` + const [rows] = await this.database.DB.execute(SQL, [allDemoScenes[0].programmeId]) + programme = rows + if(rows.length == 0) return ErrReturn({}, `未找到相应的演示方案, 方案ID:${allDemoScenes[0].programmeId}!`) + }catch (e) { + return ErrReturn(e) + } + // 查账户 + let allAccount = [] + try{ + const SQL = `SELECT id, productsInfoId, accountName, accountPass, accountRole FROM products_account WHERE productsInfoId = ? AND isDelete = 0;`; + const [row, result] = await this.database.DB.execute(SQL, [allDemoScenes[0].programmeId]); + allAccount = row + }catch (e) { + return ErrReturn(e) + } + // 产品中的产品和行业 + let product, industry + // 查产品 + try{ + const SQL = `SELECT id, productsName FROM products_info WHERE id = ?;` + const [rows] = await this.database.DB.execute(SQL, [programme[0].productsInfoId]) + product = rows + if(rows.length == 0) return ErrReturn({}, `未找到相应的产品, 产品ID:${programme[0].productsInfoId}!`) + }catch (e) { + return ErrReturn(e) + } + // 查行业 + try{ + const SQL = `SELECT id, industryName, productsInfoId FROM products_industry WHERE id = ?;` + const [rows] = await this.database.DB.execute(SQL, [programme[0].industryId]) + industry = rows + if(rows.length == 0) return ErrReturn({}, `未找到相应的行业,行业ID:${programme[0].industryId}!`) + }catch (e) { + return ErrReturn(e) + } + // 获取演示项 + const demoItemsIdList = JSON.parse(programme[0].demoItemsList) + let demoItemList + if(demoItemsIdList.length == 0){ + demoItemList = [] + }else{ + try{ + const setl = demoItemsIdList.join(',') + const SQL = `SELECT id, itemName, itemUrl, accumulate FROM demo_items WHERE id IN (${setl});` + const [rows] = await this.database.DB.execute(SQL, []) + demoItemList = rows.map(i => { + return{ + AXID : (new Date().getTime().toString() + Math.random() * 100000).toString(), + label: i.itemName, + id: i.id, + itemName: i.itemName, + itemUrl: '/api/rgvsale/staticFile/' + i.itemUrl, + accumulate: i.accumulate, + isComplete: JSON.parse(allDemoScenes[0].completeDemo).includes(i.id) + } + }) + let newDemoList = [] + demoItemsIdList.map(i => { + newDemoList.push(demoItemList[demoItemList.findIndex(j => { + return i == j.id + })]) + }) + demoItemList = newDemoList + }catch (e) { + console.log(e); + return ErrReturn(e) + } + } + allDemoScenes = allDemoScenes[0] + allDemoScenes.AXID = (new Date().getTime().toString() + Math.random() * 100000).toString(); + allDemoScenes.label = programme[0].programmeName; + allDemoScenes.industryName = industry[0].industryName + allDemoScenes.industryId = industry[0].id + allDemoScenes.programmeName = programme[0].programmeName + allDemoScenes.productName = product[0].productsName + allDemoScenes.productId = product[0].id + allDemoScenes.children = demoItemList + allDemoScenes.completeDemo = JSON.parse(allDemoScenes.completeDemo) + allDemoScenes.accountList = allAccount + return { + data:[allDemoScenes], + message:'获取演示现场成功!', + success: true, + } + } + + // 创建演示现场 + public async createScene(body: CreateScenesDto, userInfo: UserInfoDto){ + const {programmeId, expectedStarttime, targetCustomers} = body + const {userId} = userInfo + // 查找方案名称 + let programmeName, sceneName + try{ + const SQL = `SELECT programmeName FROM demo_programmes WHERE id = ? AND isDelete = 0;` + const [rows] = await this.database.DB.execute(SQL, [programmeId]) + if(rows.length == 0){ + return { + data: {}, + message: '创建现场失败,不存在该演示方案!', + success: false, + } + } + programmeName = rows[0].programmeName + }catch (e) { + return { data: e, message: '创建现场出现错误!', success: false,} + } + try{ + const SQL = `SELECT id FROM demo_scenes WHERE targetCustomers = ? AND programmeId = ? AND createuser = ?;` + const [rows] = await this.database.DB.execute(SQL, [targetCustomers, programmeId, userId]) + sceneName = programmeName + '-' + targetCustomers + '-' + (rows.length + 1) + }catch (e) { + return { data: e, message: '创建现场出现错误!', success: false,} + } + try { + const createTime = new Date(); + const [rows] = await this.database.DB.execute( + `INSERT INTO demo_scenes (sceneName, expectedStartTime, programmeId, targetCustomers, completeDemo, createtime, createuser) VALUES (?, ?, ?, ?, ?, ?, ?);`, + [sceneName , expectedStarttime, programmeId, targetCustomers, '[]', createTime, userInfo.userId], + ); + return { + data: rows, + message: '创建现场成功!', + success: true, + } + } catch (e) { + return { + data: e, + message: '创建现场出现错误!', + success: false, + }; + } + } + + // 删除演示现场 + public async deleteScene(body: DeleteSceneDto){ + const {sceneId} = body + try { + const [rows] = await this.database.DB.execute( + `UPDATE demo_scenes SET isDelete = 1 WHERE id = ?;`, + [sceneId], + ); + } catch (e) { + return { + data: e, + message: '删除演示现场出现错误!', + success: false, + }; + } + return { + data: {}, + message: '删除演示现场成功!', + success: true, + }; + } + + // 增加演示项点击次数 + public async addDemoItemAccumulateForScene(body: AddDemoItemAccumulateForSceneDto){ + const {demoItemId,sceneId} = body + // 获取已经演示的演示项 + let completeDemo + try{ + const SQL = `SELECT * FROM demo_scenes WHERE id = ?` + const [rows] = await this.database.DB.execute(SQL, [sceneId]); + if(rows.length == 0){ + return{ + data:{}, + message:"未找到演示现场!", + success: false + } + } + completeDemo = JSON.parse(rows[0].completeDemo) + }catch (e) { + return{ + data: e, + message:"修改演示项演示状态出现错误!", + success: false + } + } + if(completeDemo.includes(demoItemId)){ + return{ + data: {}, + message:"改演示项已经为演示过状态!", + success: false + } + } + completeDemo.push(demoItemId) + try{ + const SQL = `UPDATE demo_scenes SET completeDemo = ? WHERE id = ?;` + const [rows] = await this.database.DB.execute(SQL, [JSON.stringify(completeDemo), sceneId]); + const SQL2 = `UPDATE demo_items SET accumulate = accumulate + 1 WHERE id = ?` + const [rows2] = await this.database.DB.execute(SQL2, [demoItemId]); + }catch (e) { + return{ + data: e, + message:"修改演示项演示状态出现错误!", + success: false + } + } + return{ + data: {}, + message:"修改演示项演示状态成功!", + success: true + } + } + //#endregion + + + //#region 问题 + // 获取问题分页 + public async getScenesProblemList(params: GetScenesProblemPageDto){ + let {productId, programmeId, sceneId, search, isUse, pageNum, pageSize} = params + function ErrReturn(e, message = '获取问题页面出现错误!') { + console.log(e); + return{ + data:e.toString(), + message, + success: false, + } + } + // 根据产品查方案 + let programmeIdList = null; + if(productId){ + try{ + const SQL = `SELECT id FROM demo_programmes WHERE productsInfoId = ?` + const [rows] = await this.database.DB.execute(SQL, [productId]) + programmeIdList = rows + }catch (e) { + return ErrReturn(e) + } + } + if(programmeIdList != null && programmeIdList.length == 0){ + return { + data: { total:0, pageNum, pageSize, rowData:[],}, + success: true, + message: '获取问题页面成功!', + }; + } + // 根据行业查方案 + // 根据方案查现场 + let sceneIdList = null; + try{ + if(programmeId){ + // 使用单个方案ID + const SQL = `SELECT id FROM demo_scenes WHERE programmeId = ?` + const [rows] = await this.database.DB.execute(SQL, [programmeId]) + sceneIdList = rows + }else if(programmeIdList != null){ + // 使用方案ID列表 + const setl = programmeIdList.map( i => i.id).join(',') + const SQL = `SELECT id FROM demo_scenes WHERE programmeId IN (${setl})` + const [rows] = await this.database.DB.execute(SQL, []) + sceneIdList = rows + } + }catch (e) { + return ErrReturn(e); + } + if(sceneIdList != null && sceneIdList.length == 0){ + return { + data: { total:0, pageNum, pageSize, rowData:[],}, + success: true, + message: '获取问题页面成功!', + }; + } + // 根据现场查问题 + let problemIdList = null + try{ + if(sceneId){ + const SQL = `SELECT id FROM demo_scenes_problem WHERE scenesId = ?` + const [rows] = await this.database.DB.execute(SQL, [sceneId]) + problemIdList = rows + }else if(sceneIdList != null){ + const setl = sceneIdList.map( i => i.id).join(',') + const SQL = `SELECT id FROM demo_scenes_problem WHERE scenesId IN (${setl})` + const [rows] = await this.database.DB.execute(SQL, []) + problemIdList = rows + } + }catch (e) { + return ErrReturn(e); + } + if(problemIdList != null && problemIdList.length == 0){ + return { + data: { total:0, pageNum, pageSize, rowData:[] }, + success: true, + message: '获取问题页面成功!', + }; + } + if (pageNum == 0 || !pageNum) pageNum = 1; + if (pageSize == 0 || !pageSize) pageSize = 10; + let pageSQL, pageSQLParams, total, SQL, SQLParams, rowData + let problemIdSQL = problemIdList != null ? `AND id IN (${problemIdList.map(i => i.id)})` : `` + if(search && isUse != undefined){ + pageSQL = `SELECT COUNT(*) FROM demo_scenes_problem WHERE isDelete = 0 ${problemIdSQL} AND isUse = ? AND (targetCustomers LIKE '%${search}%' OR targetCustomersRole LIKE '%${search}%' OR problemContent LIKE '%${search}%')` + pageSQLParams = [isUse] + }else if(search && isUse == undefined){ + pageSQL = `SELECT COUNT(*) FROM demo_scenes_problem WHERE isDelete = 0 ${problemIdSQL} AND (targetCustomers LIKE '%${search}%' OR targetCustomersRole LIKE '%${search}%' OR problemContent LIKE '%${search}%')` + pageSQLParams = [] + }else if(!search && isUse != undefined){ + pageSQL = `SELECT COUNT(*) FROM demo_scenes_problem WHERE isDelete = 0 ${problemIdSQL} AND isUse = ?` + pageSQLParams = [isUse] + }else if(!search && isUse == undefined){ + pageSQL = `SELECT COUNT(*) FROM demo_scenes_problem WHERE isDelete = 0 ${problemIdSQL}` + pageSQLParams = [] + } + SQL = pageSQL.replace('COUNT(*)', '*') + ' ORDER BY id DESC limit ?,?' + SQLParams = [...pageSQLParams, (pageNum - 1) * pageSize, pageSize] + try{ + const [rows, results] = await this.database.DB.execute(pageSQL, pageSQLParams) + total = rows[0]['COUNT(*)']; + const [datarows, dataresults] = await this.database.DB.execute(SQL, SQLParams) + rowData = datarows; + if(rowData.length == 0){ + return { + data: { + total, + pageNum, + pageSize, + rowData: [], + }, + success: true, + message: '获取问题页面成功!', + } + } + }catch (e) { + return ErrReturn(e) + } + // 查演示项 + let demoItemIdList, demoItemDataList + demoItemIdList = rowData.map(i => i.demoItemId) + try{ + const setl = demoItemIdList.join(',') + const aSQL = `SELECT id, itemName FROM demo_items WHERE id IN (${setl})` + const [rows] = await this.database.DB.execute(aSQL, []) + demoItemDataList = rows + if(rows.length == 0){ + return ErrReturn({}, "数据错误,未找到相应演示项,演示项ID" + demoItemIdList) + } + }catch (e) { + return ErrReturn(e) + } + // 查现场 + let newScenesIdIdList, scenesDataList + newScenesIdIdList = rowData.map(i => i.scenesId) + try{ + const setl = newScenesIdIdList.join(',') + const aSQL = `SELECT id, sceneName, programmeId FROM demo_scenes WHERE id IN (${setl})` + const [rows] = await this.database.DB.execute(aSQL, []) + scenesDataList = rows + if(rows.length == 0){ + return ErrReturn({}, "数据错误,未找到相应演示现场,现场ID" + newScenesIdIdList) + } + }catch (e) { + return ErrReturn(e) + } + // 查方案 + let newProgrammeIdList, programmeDataList + newProgrammeIdList = scenesDataList.map(i => i.programmeId) + try{ + const setl = newProgrammeIdList.join(',') + const aSQL = `SELECT id, programmeName, productsInfoId, industryId FROM demo_programmes WHERE id IN (${setl})` + const [rows] = await this.database.DB.execute(aSQL, []) + programmeDataList = rows + if(rows.length == 0){ + return ErrReturn({}, "数据错误,未找到相应方案,方案ID" + newProgrammeIdList) + } + }catch (e) { + return ErrReturn(e) + } + // 查产品 + let newProductIdList, productDataList + newProductIdList = programmeDataList.map(i => i.productsInfoId) + try{ + const setl = newProductIdList.join(',') + const aSQL = `SELECT id, productsName FROM products_info WHERE id IN (${setl})` + const [rows] = await this.database.DB.execute(aSQL, []) + productDataList = rows + if(rows.length == 0){ + return ErrReturn({}, "数据错误,未找到相应产品,产品ID" + newProductIdList) + } + }catch (e) { + return ErrReturn(e) + } + // 为方案展开产品 + programmeDataList = programmeDataList.map(i => { + const aData = productDataList.filter(j => j.id == i.productsInfoId) + if(aData.length == 0){ + return ErrReturn({}, "数据错误,未找到相应产品,产品ID" + i.productsInfoId) + } + return{ + ...i, + productName:aData[0].productsName + } + }) + // 为现场展开方案 + scenesDataList = scenesDataList.map(i => { + const aData = programmeDataList.filter(j => j.id == i.programmeId) + if(aData.length == 0){ + return ErrReturn({}, "数据错误,未找到相应方案,方案ID" + i.programmeId) + } + return{ + ...i, + programmeData:aData[0] + } + }) + // 为问题展开现场 + rowData = rowData.map(i => { + const aData = scenesDataList.filter(j => j.id == i.scenesId) + if(aData.length == 0){ + return ErrReturn({}, "数据错误,未找到相应现场,现场ID" + i.scenesId) + } + return{ + ...i, + sceneId: aData[0].id, + sceneName: aData[0].sceneName, + programmeId: aData[0].programmeId, + programmeName:aData[0].programmeData.programmeName, + productId:aData[0].programmeData.productsInfoId, + productName:aData[0].programmeData.productName, + industryId:aData[0].programmeData.industryId, + } + }) + // 为问题展开演示项 + rowData = rowData.map(i => { + const aData = demoItemDataList.filter(j => j.id == i.demoItemId) + if(aData.length == 0){ + return ErrReturn({}, "数据错误,未找到相应演示项,演示项ID" + i.demoItemId) + } + return{ + ...i, + demoItemName:aData[0].itemName + } + }) + const returnData = { + data: { + total, + pageNum, + pageSize, + rowData: [], + }, + success: true, + message: '获取问题页面成功!', + } + if(rowData.length != 0){ + returnData.data.rowData = rowData + } + return returnData + } + + // 创建问题 + public async createScenesProblem(body: CreateScenesProblemDto, userInfo: UserInfoDto){ + const {targetCustomers, targetCustomersRole, problemContent, sceneId, demoItemId} = body + const {userId} = userInfo + try{ + const createtime = new Date() + const SQL = `INSERT INTO demo_scenes_problem (problemContent, scenesId, targetCustomers, targetCustomersRole, createuser, createtime, demoItemId) VALUES (?, ?, ?, ?, ?, ?, ?);` + this.database.DB.execute(SQL, [problemContent, sceneId, targetCustomers,targetCustomersRole,userId, createtime, demoItemId]) + }catch (e) { + return { + data:e, + message:'创建问题出现错误!', + success:false + } + } + return{ + data:{}, + message:'创建问题成功!', + success: true + } + } + + // 删除问题 + public async deleteScenesProblem(body: DeleteScenesProblemDto){ + const {problemId} = body + try { + const [rows] = await this.database.DB.execute( + `UPDATE demo_scenes_problem SET isDelete = 1 WHERE id = ?;`, + [problemId], + ); + } catch (e) { + return { + data: e, + message: '删除演示现场出现错误!', + success: false, + }; + } + return{data: {}, + message: '删除问题成功!', + success: true,} + } + + // 编辑问题 + public async editScenesProblem(body: EditScenesProblemDto){ + const {problemContent, targetCustomers, targetCustomersRole, problemId, sceneId, demoItemId} = body + try{ + const SQL = `UPDATE demo_scenes_problem SET problemContent = ?, targetCustomers =?, targetCustomersRole = ?, scenesId = ?, demoItemId = ? WHERE id = ?;` + const [row] = await this.database.DB.execute(SQL, [problemContent, targetCustomers, targetCustomersRole, sceneId, demoItemId, problemId]) + }catch (e) { + return { + data: e, + message: '编辑问题出现错误!', + success: false, + } + } + return{ + data:{}, + message: '编辑问题成功!', + success: true, + } + } + + // 修改问题利用状态 + public async changeScenesProblemUseState(body: ChangeUseStateDto){ + const {problemId} = body + try{ + const SQL = `UPDATE demo_scenes_problem SET isUse = 1 WHERE id = ?;` + const [row] = await this.database.DB.execute(SQL, [problemId]) + }catch (e) { + return { + data: e, + message: '修改问题利用状态出现错误!', + success: false, + } + } + return{ + data:{}, + message: '修改问题利用状态成功!', + success: true, + } + } + + //#endregion +} diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts new file mode 100644 index 0000000..ce86fb8 --- /dev/null +++ b/test/app.e2e-spec.ts @@ -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!'); + }); +}); diff --git a/test/jest-e2e.json b/test/jest-e2e.json new file mode 100644 index 0000000..e9d912f --- /dev/null +++ b/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/test/testToken.js b/test/testToken.js new file mode 100644 index 0000000..0a1bb08 --- /dev/null +++ b/test/testToken.js @@ -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); +}) \ No newline at end of file diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..64f86c6 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d5a45aa --- /dev/null +++ b/tsconfig.json @@ -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/*" + ] + } + }, +}