├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── Dockerfile ├── README.md ├── jsconfig.json ├── nest-cli.json ├── package.json ├── src ├── app.module.ts ├── config │ ├── README.md │ ├── app.config.ts │ ├── db.config.ts │ ├── index.ts │ └── status_code.config.ts ├── core │ ├── common │ │ ├── common.controller.spec.ts │ │ ├── common.controller.ts │ │ ├── common.module.ts │ │ ├── common.service.spec.ts │ │ ├── common.service.ts │ │ └── entities │ │ │ └── resource.entity.ts │ ├── index.ts │ ├── user │ │ ├── dto │ │ │ ├── Test.dto.ts │ │ │ ├── create-user.dto.ts │ │ │ ├── login-user.dto.ts │ │ │ └── update-user.dto.ts │ │ ├── entities │ │ │ └── user.entity.ts │ │ ├── readme.md │ │ ├── user.controller.spec.ts │ │ ├── user.controller.ts │ │ ├── user.module.ts │ │ ├── user.service.spec.ts │ │ └── user.service.ts │ └── ws │ │ ├── ws.gateway.ts │ │ └── ws.module.ts ├── load.ts ├── main.ts ├── module │ └── index.ts ├── plugs │ ├── index.ts │ ├── log │ │ ├── index.ts │ │ └── logger.ts │ ├── swagger │ │ └── index.ts │ └── ws │ │ ├── index.ts │ │ └── ws.adapter.ts ├── share │ ├── apiResult.ts │ ├── db │ │ └── index.ts │ ├── email │ │ ├── README.md │ │ ├── email.config.ts │ │ ├── email.module.ts │ │ ├── email.service.spec.ts │ │ └── email.service.ts │ ├── filter │ │ ├── http-exception.filter.ts │ │ └── readme.md │ ├── geetest │ │ ├── geetest.config.ts │ │ ├── geetest.module.ts │ │ ├── geetest.service.spec.ts │ │ ├── geetest.service.ts │ │ └── lib │ │ │ ├── geetest_lib.ts │ │ │ └── geetest_lib_result.ts │ ├── index.ts │ ├── jwt │ │ ├── README.md │ │ ├── auth.service.ts │ │ ├── jwt.constants.ts │ │ ├── jwt.decorator.ts │ │ ├── jwt.guard.ts │ │ ├── jwt.module.ts │ │ ├── jwt.strategy.ts │ │ └── roles.guard.ts │ ├── oAuth │ │ ├── README.md │ │ ├── oAuth.module.ts │ │ ├── oAuth.service.ts │ │ └── oauth.config.ts │ ├── office │ │ ├── README.md │ │ ├── lib │ │ │ └── excel │ │ │ │ └── index.ts │ │ ├── office.module.ts │ │ ├── office.service.spec.ts │ │ └── office.service.ts │ ├── rbac │ │ └── README.md │ ├── role │ │ ├── role.decorator.ts │ │ └── role.guard.ts │ ├── schedule │ │ ├── schedule.module.ts │ │ ├── task.module.ts │ │ └── task.service.ts │ ├── sms │ │ ├── README.md │ │ ├── sms.config.ts │ │ ├── sms.module.ts │ │ ├── sms.service.spec.ts │ │ └── sms.service.ts │ ├── storage │ │ ├── README.md │ │ ├── lib │ │ │ ├── BaseStorage.ts │ │ │ └── OssStorage.ts │ │ ├── storage.config.ts │ │ ├── storage.module.ts │ │ ├── storage.service.spec.ts │ │ └── storage.service.ts │ └── video │ │ ├── README.md │ │ ├── video.config.ts │ │ ├── video.module.ts │ │ ├── video.service.spec.ts │ │ └── video.service.ts └── utils │ ├── README.md │ ├── index.ts │ ├── isType.ts │ ├── md5.ts │ ├── moment.ts │ └── random.ts ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.13.1 2 | 3 | # 容器默认时区为UTC,如需使用上海时间请启用以下时区设置命令 4 | # RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone 5 | 6 | # 使用 HTTPS 协议访问容器云调用证书安装 7 | # RUN apk add ca-certificates 8 | 9 | # 安装依赖包,如需其他依赖包,请到alpine依赖包管理(https://pkgs.alpinelinux.org/packages?name=php8*imagick*&branch=v3.13)查找。 10 | # 选用国内镜像源以提高下载速度 11 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tencent.com/g' /etc/apk/repositories \ 12 | && apk add --update --no-cache nodejs npm 13 | 14 | # # 指定工作目录 15 | WORKDIR /app 16 | 17 | # 拷贝包管理文件 18 | COPY . /app 19 | 20 | # npm 源,选用国内镜像源以提高下载速度 21 | # RUN npm config set registry https://mirrors.cloud.tencent.com/npm/ 22 | # RUN npm config set registry https://registry.npm.taobao.org/ 23 | 24 | 25 | RUN npm config set registry https://mirrors.cloud.tencent.com/npm/ \ 26 | && npm install -g npm@8.1.2 \ 27 | && npm install -g nest \ 28 | && npm install \ 29 | && npm run build 30 | 31 | EXPOSE 80 32 | 33 | # 执行启动命令 34 | CMD ["node","dist/main.js"] 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nest.js 开发模板 2 | 3 | - typeorm mysql 4 | 5 | > 整体还未全部测试,有能力的小伙伴可以尝试 6 | 7 | 8 | 9 | **列举** 10 | 11 | - config(配置) 12 | - app 13 | - db 14 | - core(核心) 15 | - user 16 | - common 17 | - ws (port:3002) 18 | - share(共享) 19 | - db 20 | - email 21 | - filter(过滤器) 22 | - geetest(极验证) 23 | - jwt 24 | - oAuth(第三方登录) 25 | - office 26 | - excel 27 | - rbac[暂无] 28 | - role(权限) 29 | - schedule(定时) 30 | - sms(阿里云短信) 31 | - storage(阿里云对象存储) 32 | - video(阿里云视频点播) 33 | - moudle(模块) 34 | - plugs(插件) 35 | - log(日志) 36 | - swagger 37 | - utils(工具) 38 | 39 | ``` 40 | ws 41 | ws://127.0.0.1:3002 42 | message:{"event":"events","data":123} 43 | ``` 44 | 45 | --- 46 | **说明** 47 | 48 | 主要讲接口放在`core` `moudle`上,`share` `utils` 则是应对后期的业务进行拓展的功能性模块 49 | 50 | 51 | **dockerfile** 52 | 53 | 方便更好的部署,已经在微信云托管 尝试过了 -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "experimentalDecorators": true, 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-template", 3 | "version": "0.0.1", 4 | "description": "一个 nest serve 的开发模板", 5 | "author": "chunshand", 6 | "license": "MIT", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@alicloud/pop-core": "^1.7.10", 24 | "@nestjs/axios": "0.0.3", 25 | "@nestjs/common": "^8.0.0", 26 | "@nestjs/config": "^1.1.0", 27 | "@nestjs/core": "^8.0.0", 28 | "@nestjs/jwt": "^8.0.0", 29 | "@nestjs/mapped-types": "*", 30 | "@nestjs/passport": "^8.0.1", 31 | "@nestjs/platform-express": "^8.0.0", 32 | "@nestjs/platform-fastify": "^8.2.5", 33 | "@nestjs/platform-socket.io": "^9.0.5", 34 | "@nestjs/schedule": "^1.0.1", 35 | "@nestjs/swagger": "^5.1.5", 36 | "@nestjs/typeorm": "^8.0.2", 37 | "@nestjs/websockets": "^9.0.5", 38 | "ali-oss": "^6.16.0", 39 | "axios": "^0.24.0", 40 | "cache-manager": "^3.5.0", 41 | "class-transformer": "^0.5.1", 42 | "class-validator": "^0.13.1", 43 | "emailjs": "^3.6.0", 44 | "exceljs": "^4.3.0", 45 | "fastify-swagger": "^4.13.1", 46 | "log4js": "^6.3.0", 47 | "mysql2": "^2.3.3-rc.0", 48 | "passport": "^0.5.0", 49 | "passport-jwt": "^4.0.0", 50 | "pg": "^8.7.1", 51 | "reflect-metadata": "^0.1.13", 52 | "rimraf": "^3.0.2", 53 | "rxjs": "^7.2.0", 54 | "stacktrace-js": "^2.0.2", 55 | "swagger-ui-express": "^4.1.6", 56 | "typeorm": "^0.2.39" 57 | }, 58 | "devDependencies": { 59 | "@compodoc/compodoc": "^1.1.15", 60 | "@nestjs/cli": "^8.0.0", 61 | "@nestjs/schematics": "^8.0.0", 62 | "@nestjs/testing": "^8.0.0", 63 | "@types/cache-manager": "^3.4.2", 64 | "@types/express": "^4.17.13", 65 | "@types/jest": "^27.0.1", 66 | "@types/node": "^16.0.0", 67 | "@types/passport-jwt": "^3.0.6", 68 | "@types/supertest": "^2.0.11", 69 | "@typescript-eslint/eslint-plugin": "^5.0.0", 70 | "@typescript-eslint/parser": "^5.0.0", 71 | "eslint": "^8.0.1", 72 | "eslint-config-prettier": "^8.3.0", 73 | "eslint-plugin-prettier": "^4.0.0", 74 | "jest": "^27.2.5", 75 | "prettier": "^2.3.2", 76 | "source-map-support": "^0.5.20", 77 | "supertest": "^6.1.3", 78 | "ts-jest": "^27.0.3", 79 | "ts-loader": "^9.2.3", 80 | "ts-node": "^10.0.0", 81 | "tsconfig-paths": "^3.10.1", 82 | "typescript": "^4.3.5" 83 | }, 84 | "jest": { 85 | "moduleFileExtensions": [ 86 | "js", 87 | "json", 88 | "ts" 89 | ], 90 | "rootDir": "src", 91 | "testRegex": ".*\\.spec\\.ts$", 92 | "transform": { 93 | "^.+\\.(t|j)s$": "ts-jest" 94 | }, 95 | "collectCoverageFrom": [ 96 | "**/*.(t|j)s" 97 | ], 98 | "coverageDirectory": "../coverage", 99 | "testEnvironment": "node" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | // 模块加载 3 | import { 4 | ImportModules, 5 | ProviderModules 6 | } from "./load"; 7 | @Module({ 8 | imports: [ 9 | ...ImportModules, 10 | ], 11 | controllers: [], 12 | providers: [ 13 | ...ProviderModules, 14 | ], 15 | exports: [] 16 | }) 17 | export class AppModule { } 18 | -------------------------------------------------------------------------------- /src/config/README.md: -------------------------------------------------------------------------------- 1 | ## 配置 2 | 3 | -------------------------------------------------------------------------------- /src/config/app.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs('app', () => ({ 3 | verstion: "0.0.1" 4 | })); 5 | 6 | /** 7 | * 应用配置 8 | */ -------------------------------------------------------------------------------- /src/config/db.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs('db', () => ({ 3 | type: 'mysql', 4 | host: process.env.DATABASE_HOST || "127.0.0.1", 5 | port: process.env.DATABASE_PORT || 3306, 6 | username: process.env.DATABASE_USER || 'dbname', 7 | password: process.env.DATABASE_PASS || 'dbname', 8 | database: process.env.DATABASE_BASE || 'dbname', 9 | // 重试连接数据库的次数(默认:10) 10 | retryAttempts: 10, 11 | // 两次重试连接的间隔(ms)(默认:3000) 12 | retryDelay: 3000, 13 | // 如果为true,将自动加载实体(默认:false) 14 | autoLoadEntities: true, 15 | // 如果为true,在应用程序关闭后连接不会关闭(默认:false) 16 | keepConnectionAlive: false, 17 | entities: [ 18 | "dist/entities/*.entity{.ts,.js}", 19 | "module/**/entities/*.entity{.ts,.js}", 20 | ], 21 | // %%% 危险的一个操作 线上库禁止使用 22 | synchronize: true, 23 | logging: true 24 | })); -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | // 配置模块 2 | import { ConfigModule } from '@nestjs/config'; 3 | // 4 | import appConfig from './app.config'; 5 | import dbConfig from './db.config'; 6 | import statusCodeConfig from './status_code.config'; 7 | export default ConfigModule.forRoot( 8 | { 9 | load: [ 10 | appConfig, 11 | dbConfig, 12 | statusCodeConfig, 13 | ] 14 | } 15 | ); -------------------------------------------------------------------------------- /src/config/status_code.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs('status_code', () => ({ 3 | "200": "", 4 | "400": "语法错误", 5 | "401": "需要认证", 6 | "404": "请求地址错误", 7 | })); -------------------------------------------------------------------------------- /src/core/common/common.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { EmailModule } from 'src/share/email/email.module'; 3 | import { EmailService } from 'src/share/email/email.service'; 4 | import { GeetestModule } from 'src/share/geetest/geetest.module'; 5 | import { GeetestService } from 'src/share/geetest/geetest.service'; 6 | import { StorageModule } from 'src/share/storage/storage.module'; 7 | import { StorageService } from 'src/share/storage/storage.service'; 8 | import { VideoModule } from 'src/share/video/video.module'; 9 | import { VideoService } from 'src/share/video/video.service'; 10 | import { CommonController } from './common.controller'; 11 | import { CommonService } from './common.service'; 12 | 13 | describe('CommonController', () => { 14 | let controller: CommonController; 15 | 16 | beforeEach(async () => { 17 | const module: TestingModule = await Test.createTestingModule({ 18 | imports: [ 19 | StorageModule, 20 | VideoModule, 21 | GeetestModule, 22 | EmailModule 23 | ], 24 | controllers: [CommonController], 25 | providers: [CommonService, 26 | StorageService, 27 | VideoService, 28 | GeetestService, 29 | EmailService 30 | ], 31 | }).compile(); 32 | 33 | controller = module.get(CommonController); 34 | }); 35 | 36 | it('should be defined', () => { 37 | expect(controller).toBeDefined(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/core/common/common.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, StreamableFile } from '@nestjs/common'; 2 | import { CommonService } from './common.service'; 3 | import { StorageService } from 'src/share/storage/storage.service'; 4 | import { VideoService } from 'src/share/video/video.service'; 5 | import { GeetestService } from 'src/share/geetest/geetest.service'; 6 | import { EmailService } from 'src/share/email/email.service'; 7 | import { OfficeService } from 'src/share/office/office.service'; 8 | import { SmsService } from 'src/share/sms/sms.service'; 9 | import { Resource } from './entities/resource.entity'; 10 | import { InjectRepository } from '@nestjs/typeorm'; 11 | import { Connection, In, Repository } from 'typeorm'; 12 | 13 | @Controller('common') 14 | export class CommonController { 15 | constructor( 16 | private readonly commonService: CommonService, 17 | private readonly storageService: StorageService, 18 | private readonly videoService: VideoService, 19 | private readonly geetestService: GeetestService, 20 | private readonly emailService: EmailService, 21 | private readonly officeService: OfficeService, 22 | private readonly smsService: SmsService, 23 | @InjectRepository(Resource) 24 | private resourceRepository: Repository, 25 | private readonly connection: Connection, 26 | 27 | ) { } 28 | 29 | @Get() 30 | async index() { 31 | // return this.storageService.CreateUpload({ dir: "/" }); 32 | // return this.videoService.CreateUploadVideo(); 33 | // return this.geetestService.register(); 34 | // return this.emailService.send("1484082125@qq.com", "测试邮件"); 35 | 36 | // let buff: Buffer = await this.officeService.createExcel([[1, 2, 3], [4, 5, 6]], "csv"); 37 | // Promise 38 | // return new StreamableFile(buff); 39 | // this.smsService.sendCode("15200087997"); 40 | 41 | // 事务操作 42 | // https://blog.csdn.net/weixin_44828005/article/details/116477232 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/core/common/common.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CommonService } from './common.service'; 3 | import { CommonController } from './common.controller'; 4 | // 对象存储 5 | import { StorageModule } from 'src/share/storage/storage.module'; 6 | import { StorageService } from 'src/share/storage/storage.service'; 7 | // 视频点播 8 | import { VideoModule } from 'src/share/video/video.module'; 9 | import { VideoService } from 'src/share/video/video.service'; 10 | // 极验证 11 | import { GeetestModule } from 'src/share/geetest/geetest.module'; 12 | import { GeetestService } from 'src/share/geetest/geetest.service'; 13 | // 邮箱 14 | import { EmailModule } from 'src/share/email/email.module'; 15 | import { EmailService } from 'src/share/email/email.service'; 16 | // 表格 17 | import { OfficeModule } from 'src/share/office/office.module'; 18 | import { OfficeService } from 'src/share/office/office.service'; 19 | // 短信 20 | import { SmsModule } from 'src/share/sms/sms.module'; 21 | import { SmsService } from 'src/share/sms/sms.service'; 22 | // 测试resource 表 23 | import { TypeOrmModule } from '@nestjs/typeorm'; 24 | import { Resource } from './entities/resource.entity'; 25 | @Module({ 26 | imports: [ 27 | StorageModule, 28 | VideoModule, 29 | GeetestModule, 30 | EmailModule, 31 | OfficeModule, 32 | SmsModule, 33 | TypeOrmModule.forFeature([Resource]) 34 | ], 35 | controllers: [CommonController], 36 | providers: [ 37 | CommonService, 38 | StorageService, 39 | VideoService, 40 | GeetestService, 41 | EmailService, 42 | OfficeService, 43 | SmsService 44 | ] 45 | }) 46 | export class CommonModule { } 47 | -------------------------------------------------------------------------------- /src/core/common/common.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { EmailModule } from 'src/share/email/email.module'; 3 | import { EmailService } from 'src/share/email/email.service'; 4 | import { GeetestModule } from 'src/share/geetest/geetest.module'; 5 | import { GeetestService } from 'src/share/geetest/geetest.service'; 6 | import { StorageModule } from 'src/share/storage/storage.module'; 7 | import { StorageService } from 'src/share/storage/storage.service'; 8 | import { VideoModule } from 'src/share/video/video.module'; 9 | import { VideoService } from 'src/share/video/video.service'; 10 | import { CommonService } from './common.service'; 11 | 12 | describe('CommonService', () => { 13 | let service: CommonService; 14 | 15 | beforeEach(async () => { 16 | const module: TestingModule = await Test.createTestingModule({ 17 | imports: [ 18 | StorageModule, 19 | VideoModule, 20 | GeetestModule, 21 | EmailModule 22 | ], 23 | providers: [CommonService, 24 | StorageService, 25 | VideoService, 26 | GeetestService, 27 | EmailService 28 | ], 29 | }).compile(); 30 | 31 | service = module.get(CommonService); 32 | }); 33 | 34 | it('should be defined', () => { 35 | expect(service).toBeDefined(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/core/common/common.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class CommonService {} 5 | -------------------------------------------------------------------------------- /src/core/common/entities/resource.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, PrimaryColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Resource { 5 | @PrimaryColumn() 6 | id: string; 7 | 8 | @Column({ 9 | type: "json" 10 | }) 11 | data: string; 12 | 13 | @Column() 14 | type: string; 15 | 16 | } -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | import { UserModule } from './user/user.module'; 2 | import { CommonModule } from './common/common.module'; 3 | import { WsModule } from './ws/ws.module'; 4 | 5 | export default [ 6 | UserModule, 7 | CommonModule, 8 | WsModule 9 | ]; -------------------------------------------------------------------------------- /src/core/user/dto/Test.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsNotEmpty, 3 | IsString, 4 | MaxLength 5 | } from 'class-validator' 6 | 7 | export class TestDto { 8 | @IsNotEmpty({ message: 'title不能为空' }) 9 | @MaxLength(10, { message: "最大成都不能大于10位" }) 10 | title: string 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/core/user/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class CreateUserDto { 5 | @IsNotEmpty({ message: "username 不能为空" }) 6 | @ApiProperty({ description: "账号", default: "" }) 7 | username: string; 8 | 9 | @IsNotEmpty({ message: "password 不能为空" }) 10 | @IsString({ message: "password 必须是字符串" }) 11 | @ApiProperty({ description: '密码', default: "" }) 12 | password: string; 13 | 14 | @IsString({ message: "类型 必须是字符串" }) 15 | @ApiProperty({ description: '类型', default: "pc" }) 16 | type: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/core/user/dto/login-user.dto.ts: -------------------------------------------------------------------------------- 1 | // import { PartialType,OmitType} from '@nestjs/mapped-types'; 2 | import { PartialType, OmitType, ApiProperty } from '@nestjs/swagger'; 3 | import { CreateUserDto } from './create-user.dto'; 4 | 5 | export class LoginUserDto extends OmitType(CreateUserDto, ['type'] as const) 6 | { 7 | 8 | } 9 | 10 | // PartialType 生成一个类,并将字段全部设置可选 11 | // OmitType 移除一些字段 12 | // PickType 只保留的一些字段 13 | // IntersectionType 组合两种类型 14 | 15 | -------------------------------------------------------------------------------- /src/core/user/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | // import { PartialType } from '@nestjs/mapped-types'; 2 | import { PartialType } from '@nestjs/swagger'; 3 | import { CreateUserDto } from './create-user.dto'; 4 | 5 | // export class UpdateUserDto extends PartialType(CreateUserDto) {} 6 | export class UpdateUserDto extends PartialType(CreateUserDto) { } 7 | -------------------------------------------------------------------------------- /src/core/user/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class User { 5 | @PrimaryGeneratedColumn() 6 | id: number; 7 | 8 | @Column() 9 | username: string; 10 | 11 | @Column() 12 | password: string; 13 | 14 | @Column() 15 | salt: string; 16 | 17 | } -------------------------------------------------------------------------------- /src/core/user/readme.md: -------------------------------------------------------------------------------- 1 | ### user模块 -------------------------------------------------------------------------------- /src/core/user/user.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { CacheModule } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { JwtStrategy } from 'src/share/jwt/jwt.strategy'; 6 | import { oAuthModule } from 'src/share/oAuth/oAuth.module'; 7 | import { User } from './entities/user.entity'; 8 | import { UserController } from './user.controller'; 9 | import { UserService } from './user.service'; 10 | import dbModule from "src/share/db" 11 | 12 | import * as request from 'supertest'; 13 | import { INestApplication } from '@nestjs/common'; 14 | 15 | describe('UserController', () => { 16 | let controller: UserController; 17 | let app: INestApplication; 18 | 19 | beforeEach(async () => { 20 | const module: TestingModule = await Test.createTestingModule({ 21 | imports: [ 22 | dbModule, 23 | TypeOrmModule.forFeature([User]), 24 | oAuthModule, 25 | CacheModule.register(), 26 | ], 27 | controllers: [UserController], 28 | providers: [ 29 | UserService, 30 | ConfigService, 31 | JwtStrategy 32 | ] 33 | }).compile(); 34 | 35 | controller = module.get(UserController); 36 | app = module.createNestApplication(); 37 | await app.init(); 38 | }); 39 | it('should be defined', (done) => { 40 | expect(controller).toBeDefined(); 41 | done(); 42 | }); 43 | 44 | // it('/login (POST)', async (done) => { 45 | // request(app.getHttpServer()) 46 | // .post('/user/login') 47 | // .expect(200) 48 | // .end(function (err, res) { 49 | // expect(res.body.code).toEqual(0); 50 | // done(); 51 | // }) 52 | // }); 53 | // it('/login (POST)', async () => { 54 | // let res = await request(app.getHttpServer()) 55 | // .post('/user/login') 56 | // .send({ 57 | // username: "test", 58 | // password: "test", 59 | // }) 60 | // expect(res.body.code).toBe(1); 61 | // }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/core/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Query, Post, Body } from '@nestjs/common'; 2 | import { TestDto } from "./dto/Test.dto" 3 | import { ApiResult } from 'src/share/apiResult'; 4 | import { AuthService } from 'src/share/jwt/auth.service'; 5 | import { JwtDecorator } from "src/share/jwt/jwt.decorator" 6 | import { UserService } from './user.service'; 7 | 8 | import { ApiTags } from "@nestjs/swagger" 9 | import { CreateUserDto } from './dto/create-user.dto'; 10 | import { LoginUserDto } from './dto/login-user.dto'; 11 | 12 | @ApiTags('user') 13 | @Controller({ 14 | path: 'user', 15 | }) 16 | export class UserController { 17 | constructor( 18 | private readonly authService: AuthService, 19 | private readonly userService: UserService, 20 | 21 | ) { } 22 | /** 23 | * 账号登录 24 | * @param user 25 | */ 26 | @Post('login') 27 | async login(@Body() user: LoginUserDto) { 28 | let user_data = await this.userService.validateUser(user.username, user.password); 29 | if (!user_data) { 30 | ApiResult.error({}, "账号或密码错误"); 31 | } 32 | // 注入权限路由 33 | user_data.roles = []; 34 | let res = await this.authService.sign(user_data); 35 | ApiResult.success(res); 36 | } 37 | 38 | /** 39 | * 创建账号 40 | * @param user 41 | */ 42 | @Post('create') 43 | async create(@Body() user: CreateUserDto) { 44 | let res = await this.userService.create(user); 45 | ApiResult.success(res); 46 | ApiResult.success([]); 47 | } 48 | 49 | 50 | /** 51 | * 测试方法 添加jwt装饰器 带有认证才会请求成功 52 | */ 53 | @JwtDecorator() 54 | @Get('test') 55 | async test() { 56 | ApiResult.success(); 57 | } 58 | 59 | // /** 60 | // * 缓存测试 demo 61 | // */ 62 | // @Post('create') 63 | // async cacheTest() { 64 | // this.userService.cacheTest(); 65 | // ApiResult.success([]); 66 | // } 67 | 68 | } -------------------------------------------------------------------------------- /src/core/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { CacheModule, Module } from '@nestjs/common'; 2 | import { UserController } from './user.controller'; 3 | import { UserService } from './user.service'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | import { User } from "./entities/user.entity" 7 | import { oAuthModule } from 'src/share/oAuth/oAuth.module'; 8 | import { JwtStrategy } from 'src/share/jwt/jwt.strategy'; 9 | 10 | @Module({ 11 | imports: [ 12 | TypeOrmModule.forFeature([User]), 13 | oAuthModule, 14 | CacheModule.register(), 15 | ], 16 | controllers: [UserController], 17 | providers: [ 18 | UserService, 19 | ConfigService, 20 | JwtStrategy 21 | ] 22 | }) 23 | export class UserModule { } 24 | -------------------------------------------------------------------------------- /src/core/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { CacheModule } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { JwtStrategy } from 'src/share/jwt/jwt.strategy'; 6 | import { oAuthModule } from 'src/share/oAuth/oAuth.module'; 7 | import { User } from './entities/user.entity'; 8 | import { UserService } from './user.service'; 9 | import dbModule from "src/share/db" 10 | 11 | describe('UserService', () => { 12 | let service: UserService; 13 | 14 | beforeEach(async () => { 15 | const module: TestingModule = await Test.createTestingModule({ 16 | imports: [ 17 | dbModule, 18 | TypeOrmModule.forFeature([User]), 19 | oAuthModule, 20 | CacheModule.register(), 21 | ], 22 | providers: [ 23 | UserService, 24 | ConfigService, 25 | JwtStrategy 26 | ] 27 | }).compile(); 28 | 29 | service = module.get(UserService); 30 | }); 31 | 32 | it('should be defined', () => { 33 | expect(service).toBeDefined(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/core/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from "@nestjs/typeorm" 3 | import { Repository } from "typeorm" 4 | import { User } from "./entities/user.entity" 5 | import { Cache } from 'cache-manager'; 6 | import { CreateUserDto } from './dto/create-user.dto'; 7 | import { UpdateUserDto } from './dto/update-user.dto'; 8 | import { md5_encode, randomString } from 'src/utils'; 9 | 10 | @Injectable() 11 | export class UserService { 12 | constructor( 13 | // user表 14 | @InjectRepository(User) 15 | private usersRepository: Repository, 16 | // 缓存 17 | @Inject(CACHE_MANAGER) 18 | private cacheManager: Cache 19 | ) { } 20 | async validateUser(username: string = "", password: string = ""): Promise { 21 | // 验证方法 22 | const user: any = await this.findOne({ username }); 23 | if (user && md5_encode(password + user.salt) == user.password) { 24 | const { password, ...result } = user; 25 | return result; 26 | } 27 | return null; 28 | } 29 | create(createUserDto: CreateUserDto) { 30 | let r = this.usersRepository.create(createUserDto); 31 | const salt = randomString(4); 32 | r.password = md5_encode(r.password + salt); 33 | r.salt = salt; 34 | return this.usersRepository.insert(r); 35 | } 36 | 37 | findAll() { 38 | return this.usersRepository.find(); 39 | } 40 | findOne(where: any) { 41 | return this.usersRepository.findOne(where); 42 | } 43 | 44 | update(id: number, updateUserDto: UpdateUserDto) { 45 | return this.usersRepository.update(id, updateUserDto); 46 | } 47 | 48 | remove(id: number) { 49 | return this.usersRepository.delete(id); 50 | } 51 | 52 | /** 53 | * 缓存测试方法 54 | */ 55 | async cacheTest() { 56 | await this.cacheManager.set('key', 'value', { ttl: 1000 }); 57 | const value = await this.cacheManager.get('key'); 58 | console.log(value); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/core/ws/ws.gateway.ts: -------------------------------------------------------------------------------- 1 | import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from "@nestjs/websockets"; 2 | import { Server } from 'socket.io'; 3 | import * as WebSocket from 'ws'; 4 | @WebSocketGateway(3002, { 5 | cors: { 6 | origin: '*', 7 | }, 8 | }) 9 | export class WsStartGateway { 10 | @WebSocketServer() 11 | server: Server; 12 | 13 | @SubscribeMessage('events') 14 | hello(@MessageBody() data: any, @ConnectedSocket() client: WebSocket): any { 15 | client.send(JSON.stringify({ g: 123 })) 16 | return { 17 | "event": "hello", 18 | "data": data, 19 | "msg": 'rustfisher.com' 20 | }; 21 | } 22 | } -------------------------------------------------------------------------------- /src/core/ws/ws.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { WsStartGateway } from './ws.gateway'; 3 | 4 | @Module({ 5 | providers: [WsStartGateway], 6 | }) 7 | export class WsModule { } -------------------------------------------------------------------------------- /src/load.ts: -------------------------------------------------------------------------------- 1 | // 加载器 主要总结需要模块 2 | 3 | // 配置 4 | import ConfigModule from "src/config" 5 | // 核心模块 6 | import DefaultModules from "src/core" 7 | // 其他模块 8 | import ExplendModules from "src/module" 9 | // 附加模块 10 | import ShareModules from "src/share" 11 | export const ImportModules = [ 12 | ConfigModule, 13 | ...DefaultModules, 14 | ...ExplendModules, 15 | ...ShareModules 16 | ]; 17 | 18 | 19 | // 全局注册 20 | import { 21 | // 守卫 22 | APP_GUARD, 23 | // 过滤器 24 | APP_FILTER, 25 | // 管道 26 | APP_PIPE, 27 | // 缓存 28 | APP_INTERCEPTOR 29 | } from '@nestjs/core'; 30 | 31 | // 异常过滤器 32 | import { HttpExceptionFilter } from "src/share/filter/http-exception.filter" 33 | 34 | 35 | // 验证守卫 36 | // https://github.com/typestack/class-validato 37 | // https://www.cxybb.com/article/marendu/105745069 38 | import { ValidationPipe } from '@nestjs/common'; 39 | 40 | // 权限守卫 41 | import { RolesGuard } from "src/share/jwt/roles.guard" 42 | 43 | 44 | // 全局 性质 45 | export const ProviderModules = [ 46 | { 47 | // 全局管道 主要为了参数验证 48 | provide: APP_PIPE, 49 | useClass: ValidationPipe 50 | }, 51 | { 52 | // 全局异常过滤器 主要为了http请求返回格式统一 53 | provide: APP_FILTER, 54 | useClass: HttpExceptionFilter, 55 | }, 56 | // { 57 | // provide: APP_GUARD, 58 | // useClass: RolesGuard, 59 | // }, 60 | // 全局缓存 61 | // { 62 | // provide: APP_INTERCEPTOR, 63 | // useClass: CacheInterceptor, 64 | // }, 65 | 66 | ]; -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { usePlugs } from "./plugs" 4 | async function bootstrap() { 5 | // 创建实例 6 | const app = await NestFactory.create(AppModule); 7 | // 设置前缀 8 | app.setGlobalPrefix('api'); 9 | // 添加插件 10 | usePlugs(app); 11 | // 监听 12 | await app.listen(8000); 13 | } 14 | bootstrap(); 15 | -------------------------------------------------------------------------------- /src/module/index.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default []; -------------------------------------------------------------------------------- /src/plugs/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { useSwagger } from "./swagger"; 3 | import { useLogger } from "./log"; 4 | import { useWs } from "./ws"; 5 | import { INestApplication } from "@nestjs/common"; 6 | export function usePlugs(app: INestApplication) { 7 | useSwagger(app); 8 | // useLogger(app); 9 | useWs(app) 10 | } -------------------------------------------------------------------------------- /src/plugs/log/index.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path'; 2 | import * as Log4js from 'log4js'; 3 | import * as Util from 'util'; 4 | import { format } from 'src/utils/moment'; 5 | import * as StackTrace from 'stacktrace-js'; 6 | import Chalk from 'chalk'; 7 | import log4jsConfig from './logger'; 8 | 9 | export enum LoggerLevel { 10 | ALL = 'ALL', 11 | MARK = 'MARK', 12 | TRACE = 'TRACE', 13 | DEBUG = 'DEBUG', 14 | INFO = 'INFO', 15 | WARN = 'WARN', 16 | ERROR = 'ERROR', 17 | FATAL = 'FATAL', 18 | OFF = 'OFF', 19 | } 20 | 21 | export class ContextTrace { 22 | constructor( 23 | public readonly context: string, 24 | public readonly path?: string, 25 | public readonly lineNumber?: number, 26 | public readonly columnNumber?: number, 27 | ) { } 28 | } 29 | 30 | Log4js.addLayout('Awesome-nest', (logConfig: any) => { 31 | return (logEvent: Log4js.LoggingEvent): string => { 32 | let moduleName = ''; 33 | let position = ''; 34 | 35 | // 日志组装 36 | const messageList: string[] = []; 37 | logEvent.data.forEach((value: any) => { 38 | if (value instanceof ContextTrace) { 39 | moduleName = value.context; 40 | // 显示触发日志的坐标(行,列) 41 | if (value.lineNumber && value.columnNumber) { 42 | position = `${value.lineNumber}, ${value.columnNumber}`; 43 | } 44 | return; 45 | } 46 | 47 | if (typeof value !== 'string') { 48 | value = Util.inspect(value, false, 3, true); 49 | } 50 | 51 | messageList.push(value); 52 | }); 53 | 54 | // 日志组成部分 55 | const messageOutput: string = messageList.join(' '); 56 | const positionOutput: string = position ? ` [${position}]` : ''; 57 | const typeOutput = `[${logConfig.type}] ${logEvent.pid.toString()} - `; 58 | const dateOutput = `${format( 59 | logEvent.startTime, 60 | 'YYYY-MM-D:HH:mm:ss', 61 | false, 62 | )}`; 63 | const moduleOutput: string = moduleName 64 | ? `[${moduleName}] ` 65 | : '[LoggerService] '; 66 | let levelOutput = `[${logEvent.level}] ${messageOutput}`; 67 | 68 | // 根据日志级别,用不同颜色区分 69 | switch (logEvent.level.toString()) { 70 | case LoggerLevel.DEBUG: 71 | levelOutput = Chalk.green(levelOutput); 72 | break; 73 | case LoggerLevel.INFO: 74 | levelOutput = Chalk.cyan(levelOutput); 75 | break; 76 | case LoggerLevel.WARN: 77 | levelOutput = Chalk.yellow(levelOutput); 78 | break; 79 | case LoggerLevel.ERROR: 80 | levelOutput = Chalk.red(levelOutput); 81 | break; 82 | case LoggerLevel.FATAL: 83 | levelOutput = Chalk.hex('#DD4C35')(levelOutput); 84 | break; 85 | default: 86 | levelOutput = Chalk.grey(levelOutput); 87 | break; 88 | } 89 | 90 | return `${Chalk.green(typeOutput)}${dateOutput} ${Chalk.yellow( 91 | moduleOutput, 92 | )}${levelOutput}${positionOutput}`; 93 | }; 94 | }); 95 | 96 | // 注入配置 97 | Log4js.configure(log4jsConfig); 98 | 99 | // 实例化 100 | const logger = Log4js.getLogger(); 101 | // logger.level = LoggerLevel.TRACE; 102 | import { LoggerService } from '@nestjs/common'; 103 | export class Logger implements LoggerService { 104 | /** 105 | * Write a 'log' level log. 106 | */ 107 | log(message: any, ...optionalParams: any[]) { 108 | logger.info(message, optionalParams); 109 | } 110 | /** 111 | * Write an 'error' level log. 112 | */ 113 | error(message: any, ...optionalParams: any[]) { 114 | logger.error(message, ...optionalParams); 115 | 116 | } 117 | /** 118 | * Write a 'warn' level log. 119 | */ 120 | warn(message: any, ...optionalParams: any[]) { 121 | logger.warn(message, ...optionalParams); 122 | } 123 | 124 | /** 125 | * Write a 'debug' level log. 126 | */ 127 | debug?(message: any, ...optionalParams: any[]) { 128 | logger.debug(message, ...optionalParams); 129 | } 130 | /** 131 | * Write a 'verbose' level log. 132 | */ 133 | verbose?(message: any, ...optionalParams: any[]) { 134 | logger.info(message, ...optionalParams); 135 | } 136 | } 137 | 138 | export function useLogger(app: any) { 139 | app.use(Logger); 140 | } -------------------------------------------------------------------------------- /src/plugs/log/logger.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | const baseLogPath = path.resolve(__dirname, "../../../logs"); // 日志根目录,视情况而定,这里将于项目同级 3 | 4 | const log4jsConfig = { 5 | appenders: { 6 | console: { 7 | type: "console", // 控制台打印 8 | }, 9 | access: { 10 | type: "dateFile", // 写入按日期分类文件 11 | filename: `${baseLogPath}/access/access.log`, // 日志名称,会加上pattern格式的日期 12 | alwaysIncludePattern: true, 13 | pattern: "yyyy-MM-dd", 14 | daysToKeep: 7, //保存天数 15 | numBackups: 3, // 日志文件 16 | category: "http", //category 类型 17 | keepFileExt: true, // 文件后缀 18 | compress: true, // 压缩 19 | }, 20 | app: { 21 | type: "dateFile", 22 | filename: `${baseLogPath}/apps/app.log`, 23 | alwaysIncludePattern: true, 24 | layout: { 25 | type: "pattern", 26 | pattern: 27 | '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}', 28 | }, 29 | maxLogSize: 10485760, 30 | pattern: "yyyy-MM-dd", 31 | daysToKeep: 7, 32 | numBackups: 3, 33 | keepFileExt: true, 34 | }, 35 | errorFile: { 36 | type: "dateFile", 37 | filename: `${baseLogPath}/errors/error.log`, 38 | alwaysIncludePattern: true, 39 | layout: { 40 | type: "pattern", 41 | pattern: 42 | '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}', 43 | }, 44 | pattern: "yyyy-MM-dd", 45 | daysToKeep: 7, 46 | numBackups: 3, 47 | keepFileExt: true, 48 | }, 49 | errors: { 50 | type: "logLevelFilter", 51 | level: "ERROR", 52 | appender: "errorFile", 53 | }, 54 | }, 55 | categories: { 56 | default: { 57 | appenders: ["console", "app", "errors"], 58 | level: "DEBUG", 59 | }, 60 | info: { appenders: ["console", "app", "errors"], level: "info" }, 61 | access: { appenders: ["console", "app", "errors"], level: "info" }, 62 | http: { appenders: ["access"], level: "DEBUG" }, 63 | }, 64 | pm2: true, // pm2时,此演示项目将通过云服务器Linux pm2部署 65 | pm2InstanceVar: "INSTANCE_ID", // 根据pm2 id分配 66 | }; 67 | 68 | export default log4jsConfig; 69 | -------------------------------------------------------------------------------- /src/plugs/swagger/index.ts: -------------------------------------------------------------------------------- 1 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 2 | import { INestApplication } from "@nestjs/common"; 3 | export function useSwagger(app: INestApplication) { 4 | const config = new DocumentBuilder() 5 | .setTitle('接口文档') 6 | .setDescription('接口文档') 7 | // .setVersion('1.0') 8 | .setBasePath('api') 9 | .build(); 10 | const document = SwaggerModule.createDocument(app, config, { 11 | // ignoreGlobalPrefix: true 12 | }); 13 | let options = { 14 | // customCss: '.swagger-ui .topbar { display: none }' 15 | }; 16 | SwaggerModule.setup('api', app, document, options); 17 | } -------------------------------------------------------------------------------- /src/plugs/ws/index.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from "@nestjs/common"; 2 | import { WsAdapter } from "./ws.adapter"; 3 | export function useWs(app: INestApplication) { 4 | app.useWebSocketAdapter(new WsAdapter()); // 使用我们的适配器 5 | } -------------------------------------------------------------------------------- /src/plugs/ws/ws.adapter.ts: -------------------------------------------------------------------------------- 1 | import * as WebSocket from 'ws'; 2 | import { WebSocketAdapter, INestApplicationContext } from '@nestjs/common'; 3 | import { MessageMappingProperties } from '@nestjs/websockets'; 4 | import { Observable, fromEvent, EMPTY } from 'rxjs'; 5 | import { mergeMap, filter } from 'rxjs/operators'; 6 | 7 | export class WsAdapter implements WebSocketAdapter { 8 | 9 | // constructor(private app: INestApplicationContext) { } 10 | 11 | create(port: number, options: any = {}): any { 12 | console.log('ws create') 13 | return new WebSocket.Server({ port, ...options }); 14 | } 15 | 16 | bindClientConnect(server, callback: Function) { 17 | console.log('ws bindClientConnect, server:\n', server); 18 | server.on('connection', callback); 19 | } 20 | 21 | bindMessageHandlers( 22 | client: WebSocket, 23 | handlers: MessageMappingProperties[], 24 | process: (data: any) => Observable, 25 | ) { 26 | console.log('[waAdapter]有新的连接进来') 27 | fromEvent(client, 'message') 28 | .pipe( 29 | mergeMap(data => this.bindMessageHandler(client, data, handlers, process)), 30 | filter(result => result), 31 | ) 32 | .subscribe(response => client.send(JSON.stringify(response))); 33 | } 34 | 35 | bindMessageHandler( 36 | client: WebSocket, 37 | buffer, 38 | handlers: MessageMappingProperties[], 39 | process: (data: any) => Observable, 40 | ): Observable { 41 | let message = null; 42 | try { 43 | message = JSON.parse(buffer.data); 44 | } catch (error) { 45 | console.log('ws解析json出错', error); 46 | return EMPTY; 47 | } 48 | const messageHandler = handlers.find( 49 | handler => handler.message === message.event, 50 | ); 51 | if (!messageHandler) { 52 | return EMPTY; 53 | } 54 | return process(messageHandler.callback(message.data)); 55 | } 56 | 57 | close(server) { 58 | console.log('ws server close'); 59 | server.close(); 60 | } 61 | } -------------------------------------------------------------------------------- /src/share/apiResult.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpException, 3 | HttpStatus 4 | } from "@nestjs/common" 5 | 6 | /** 7 | * 接口统一返回方法 8 | * 然后通过 http拦截器处理 9 | */ 10 | export class ApiResult { 11 | 12 | /** 13 | * 快速成功返回数据 14 | * @param message 15 | */ 16 | static success(data: any | Record = {}, message: string | Record = "成功", code: number = 1) { 17 | throw new HttpException({ 18 | code, 19 | message, 20 | data 21 | }, HttpStatus.OK); 22 | } 23 | 24 | /** 25 | * 快速失败返回数据 26 | * @param message 消息文本 27 | */ 28 | static error(data: Record = {}, message: string | Record = "失败", code: number = 0) { 29 | throw new HttpException({ 30 | code, 31 | message, 32 | data 33 | }, HttpStatus.OK); 34 | } 35 | 36 | /** 37 | * 基本状态响应 38 | * @param number 状态码 39 | */ 40 | static show( 41 | code: number = 0, 42 | data: Record = {}, 43 | message: string | Record = "成功", 44 | status_code: HttpStatus = 200 45 | ) { 46 | throw new HttpException({ 47 | code, 48 | message, 49 | data 50 | }, status_code); 51 | } 52 | } -------------------------------------------------------------------------------- /src/share/db/index.ts: -------------------------------------------------------------------------------- 1 | import { TypeOrmModule } from '@nestjs/typeorm'; 2 | import ConfigModule from "src/config" 3 | import { ConfigService } from "@nestjs/config" 4 | export default TypeOrmModule.forRootAsync({ 5 | imports: [ConfigModule], 6 | useFactory: (config: ConfigService) => { 7 | return config.get('db'); 8 | }, 9 | inject: [ConfigService], 10 | }); -------------------------------------------------------------------------------- /src/share/email/README.md: -------------------------------------------------------------------------------- 1 | ## 邮箱模块 2 | 3 | ### 依赖 4 | 5 | > npm install emailjs 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/share/email/email.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs("email", () => ({ 3 | 'driver': 'smtp', // 邮件驱动, 支持 smtp|sendmail|mail 三种驱动 4 | 'host': 'smtp.qq.com', // SMTP服务器地址 5 | 'port': 465, // SMTP服务器端口号,一般为25 6 | 'user': '1484082125@qq.com', // 发件邮箱地址 7 | 'password': '', // 发件邮箱密码 8 | 'name': '', // 发件邮箱名称 9 | 'content_type': 'text/html', // 默认文本内容 text/html|text/plain 10 | 'charset': 'utf-8', // 默认字符集 11 | 'security': 'ssl', // 加密方式 null|ssl|tls, QQ邮箱必须使用ssl 12 | 'sendmail': '/usr/sbin/sendmail -bs', // 不适用 sendmail 驱动不需要配置 13 | 'debug': true, // 开启debug模式会直接抛出异常, 记录邮件发送日志 14 | 'left_delimiter': '{', // 模板变量替换左定界符, 可选, 默认为 { 15 | 'right_delimiter': '}', // 模板变量替换右定界符, 可选, 默认为 } 16 | 'log_driver': '', // 日志驱动类, 可选, 如果启用必须实现静态 public static function write($content, $level = 'debug') 方法 17 | 'log_path': '', // 日志路径, 可选, 不配置日志驱动时启用默认日志驱动, 默认路径是 /path/to/think-mailer/log, 要保证该目录有可写权限, 最好配置自己的日志路径 18 | 'embed': 'embed:', // 邮件中嵌入图片元数据标记 19 | })); -------------------------------------------------------------------------------- /src/share/email/email.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { EmailService } from './email.service'; 3 | import { ConfigModule } from '@nestjs/config'; 4 | import emailConfig from './email.config'; 5 | import { ConfigService } from '@nestjs/config'; 6 | @Module({ 7 | imports: [ConfigModule.forFeature(emailConfig)], 8 | providers: [ConfigService, EmailService], 9 | exports: [ConfigService, EmailService] 10 | }) 11 | export class EmailModule { } 12 | -------------------------------------------------------------------------------- /src/share/email/email.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule, ConfigService } from '@nestjs/config'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import emailConfig from './email.config'; 4 | import { EmailService } from './email.service'; 5 | 6 | describe('EmailService', () => { 7 | let service: EmailService; 8 | 9 | beforeEach(async () => { 10 | const module: TestingModule = await Test.createTestingModule({ 11 | imports: [ConfigModule.forFeature(emailConfig)], 12 | providers: [ConfigService, EmailService], 13 | exports: [ConfigService, EmailService] 14 | }).compile(); 15 | 16 | service = module.get(EmailService); 17 | }); 18 | 19 | it('should be defined', () => { 20 | expect(service).toBeDefined(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/share/email/email.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { SMTPClient } from 'emailjs'; 4 | 5 | @Injectable() 6 | export class EmailService { 7 | private config: object; 8 | private client: any; 9 | constructor(private readonly configService: ConfigService) { 10 | this.config = this.configService.get("email"); 11 | this.client = new SMTPClient({ 12 | user: this.config['user'], 13 | password: this.config['password'], 14 | host: this.config['host'], 15 | port: this.config['port'], 16 | ssl: true, 17 | }); 18 | } 19 | 20 | send(to, text) { 21 | return this.client.sendAsync({ 22 | text: text, 23 | from: this.config['user'], 24 | to: to, 25 | subject: this.config['name'], 26 | }); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/share/filter/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | // 异常过滤器 2 | import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; 3 | import { ConfigService } from '@nestjs/config'; 4 | import { Request, Response } from 'express'; 5 | import { isArray } from 'class-validator'; 6 | 7 | 8 | @Catch(HttpException) 9 | export class HttpExceptionFilter implements ExceptionFilter { 10 | constructor( 11 | private readonly configService: ConfigService 12 | ) { } 13 | catch(exception: HttpException, host: ArgumentsHost) { 14 | console.log("异常过滤器"); 15 | const ctx = host.switchToHttp(); 16 | const response = ctx.getResponse(); 17 | const status = exception.getStatus(); 18 | const ResponseData = exception.getResponse(); 19 | let res = { 20 | // 服务状态 21 | status_code: status, 22 | // 业务状态 23 | code: 0, 24 | message: '', 25 | data: null, 26 | // path: request.url, 27 | // timestamp: new Date().toISOString(), 28 | }; 29 | if (status == HttpStatus.OK) { 30 | res.code = ResponseData['code']; 31 | res.data = ResponseData['data']; 32 | res.message = ResponseData['message']; 33 | } else { 34 | if (typeof ResponseData === "object") { 35 | const status_code: object = this.configService.get('status_code') 36 | const statusCode = ResponseData['statusCode']; 37 | switch (statusCode) { 38 | // 语法错误 39 | case 400: 40 | res.code = statusCode; 41 | res.message = isArray(ResponseData['message']) ? ResponseData['message'][0] : ResponseData['message']; 42 | res.data = ResponseData['message']; 43 | break; 44 | default: 45 | res.code = statusCode; 46 | res.message = status_code[statusCode] ? status_code[statusCode] : "出现了一些问题"; 47 | break; 48 | } 49 | 50 | } 51 | } 52 | response 53 | .status(status) 54 | .json(res); 55 | } 56 | } -------------------------------------------------------------------------------- /src/share/filter/readme.md: -------------------------------------------------------------------------------- 1 | # 异常过滤器 2 | 3 | -------------------------------------------------------------------------------- /src/share/geetest/geetest.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs("geetest", () => ({ 3 | 'GEETEST_ID': 'GEETEST_ID', 4 | 'GEETEST_KEY': 'GEETEST_KEY', 5 | })); -------------------------------------------------------------------------------- /src/share/geetest/geetest.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { GeetestService } from './geetest.service'; 3 | import { ConfigService } from '@nestjs/config'; 4 | import { ConfigModule } from '@nestjs/config'; 5 | import geetestConfig from './geetest.config'; 6 | @Module({ 7 | imports: [ConfigModule.forFeature(geetestConfig)], 8 | providers: [ConfigService, GeetestService], 9 | exports: [ConfigService, GeetestService] 10 | }) 11 | export class GeetestModule { } 12 | -------------------------------------------------------------------------------- /src/share/geetest/geetest.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule, ConfigService } from '@nestjs/config'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import geetestConfig from './geetest.config'; 4 | import { GeetestService } from './geetest.service'; 5 | 6 | describe('GeetestService', () => { 7 | let service: GeetestService; 8 | 9 | beforeEach(async () => { 10 | const module: TestingModule = await Test.createTestingModule({ 11 | imports: [ConfigModule.forFeature(geetestConfig)], 12 | providers: [ConfigService, GeetestService], 13 | exports: [ConfigService, GeetestService] 14 | }).compile(); 15 | 16 | service = module.get(GeetestService); 17 | }); 18 | 19 | it('should be defined', () => { 20 | expect(service).toBeDefined(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/share/geetest/geetest.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import GeetestLib from "./lib/geetest_lib" 4 | @Injectable() 5 | export class GeetestService { 6 | private config: any 7 | constructor( 8 | private readonly configService: ConfigService, 9 | ) { 10 | this.config = this.configService.get("geetest"); 11 | } 12 | 13 | /** 14 | * 初始化 15 | */ 16 | async register() { 17 | const gtLib = new GeetestLib(this.config.GEETEST_ID, this.config.GEETEST_KEY); 18 | const digestmod = "md5"; 19 | const userId = "test"; 20 | const params = { "digestmod": digestmod, "user_id": userId, "client_type": "web", "ip_address": "127.0.0.1" } 21 | let res = await gtLib.register(digestmod, params); 22 | return res.data; 23 | } 24 | 25 | /** 26 | * 验证 27 | */ 28 | async validate(challenge: string, validate: string, seccode) { 29 | const gtLib = new GeetestLib(this.config.GEETEST_ID, this.config.GEETEST_KEY); 30 | let params = new Array(); 31 | let result = await gtLib.successValidate(challenge, validate, seccode, params); 32 | return result.status === 1; 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/share/geetest/lib/geetest_lib.ts: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const axios = require('axios'); 3 | const qs = require('qs'); 4 | import GeetestLibResult from "./geetest_lib_result" 5 | export default class GeetestLib { 6 | static IS_DEBUG = false; // 调试开关,是否输出调试日志 7 | static API_URL = "http://api.geetest.com"; 8 | static REGISTER_URL = "/register.php"; 9 | static VALIDATE_URL = "/validate.php"; 10 | static JSON_FORMAT = "1"; 11 | static NEW_CAPTCHA = true; 12 | static HTTP_TIMEOUT_DEFAULT = 5000; // 单位:毫秒 13 | static VERSION = "node-express:3.1.1"; 14 | static GEETEST_CHALLENGE = "geetest_challenge"; // 极验二次验证表单传参字段 chllenge 15 | static GEETEST_VALIDATE = "geetest_validate"; // 极验二次验证表单传参字段 validate 16 | static GEETEST_SECCODE = "geetest_seccode"; // 极验二次验证表单传参字段 seccode 17 | static GEETEST_SERVER_STATUS_SESSION_KEY = "gt_server_status"; // 极验验证API服务状态Session Key 18 | private geetest_id: string; 19 | private geetest_key: string; 20 | private libResult: GeetestLibResult; 21 | constructor(geetest_id, geetest_key) { 22 | this.geetest_id = geetest_id; 23 | this.geetest_key = geetest_key; 24 | this.libResult = new GeetestLibResult(); 25 | } 26 | 27 | gtlog(message) { 28 | if (GeetestLib.IS_DEBUG) { 29 | console.log("gtlog: " + message); 30 | } 31 | } 32 | 33 | /** 34 | * 验证初始化 35 | */ 36 | async register(digestmod, params) { 37 | this.gtlog(`register(): 开始验证初始化, digestmod=${digestmod}.`); 38 | const origin_challenge = await this.requestRegister(params); 39 | this.buildRegisterResult(origin_challenge, digestmod) 40 | this.gtlog(`register(): 验证初始化, lib包返回信息=${this.libResult}.`); 41 | return this.libResult.toRes() 42 | } 43 | 44 | /** 45 | * 向极验发送验证初始化的请求,GET方式 46 | */ 47 | async requestRegister(params) { 48 | params = Object.assign(params, { 49 | "gt": this.geetest_id, 50 | "json_format": GeetestLib.JSON_FORMAT, 51 | "sdk": GeetestLib.VERSION 52 | }); 53 | const register_url = GeetestLib.API_URL + GeetestLib.REGISTER_URL; 54 | this.gtlog(`requestRegister(): 验证初始化, 向极验发送请求, url=${register_url}, params=${JSON.stringify(params)}.`); 55 | let origin_challenge; 56 | try { 57 | const res = await axios({ 58 | url: register_url, 59 | method: "GET", 60 | timeout: GeetestLib.HTTP_TIMEOUT_DEFAULT, 61 | params: params 62 | }); 63 | const resBody = (res.status === 200) ? res.data : ""; 64 | this.gtlog(`requestRegister(): 验证初始化, 与极验网络交互正常, 返回码=${res.status}, 返回body=${JSON.stringify(resBody)}.`); 65 | origin_challenge = resBody["challenge"]; 66 | } catch (e) { 67 | this.gtlog("requestRegister(): 验证初始化, 请求异常,后续流程走宕机模式, " + e.message); 68 | origin_challenge = ""; 69 | } 70 | return origin_challenge; 71 | } 72 | async localRegister() { 73 | this.gtlog("获取当前缓存中bypass状态为fail,后续流程走宕机模式 "); 74 | this.buildRegisterResult("", "") 75 | this.gtlog(`register(): 验证初始化, lib包返回信息=${this.libResult}.`); 76 | return this.libResult.toRes() 77 | } 78 | /** 79 | * 构建验证初始化返回数据 80 | */ 81 | buildRegisterResult(origin_challenge, digestmod) { 82 | // origin_challenge为空或者值为0代表失败 83 | if (!origin_challenge || origin_challenge === "0") { 84 | // 本地随机生成32位字符串 85 | const challenge = randomString(32); 86 | const data = { 87 | "success": 0, 88 | "gt": this.geetest_id, 89 | "challenge": challenge, 90 | "new_captcha": GeetestLib.NEW_CAPTCHA 91 | }; 92 | this.libResult.setAll(0, JSON.stringify(data), "获取当前缓存中bypass状态为fail,本地生成challenge,后续流程走宕机模式") 93 | } else { 94 | let challenge; 95 | if (digestmod === "md5") { 96 | challenge = this.md5_encode(origin_challenge + this.geetest_key); 97 | } else if (digestmod === "sha256") { 98 | challenge = this.sha256_encode(origin_challenge + this.geetest_key); 99 | } else if (digestmod === "hmac-sha256") { 100 | challenge = this.hmac_sha256_encode(origin_challenge, this.geetest_key); 101 | } else { 102 | challenge = this.md5_encode(origin_challenge + this.geetest_key); 103 | } 104 | const data = { 105 | "success": 1, 106 | "gt": this.geetest_id, 107 | "challenge": challenge, 108 | "new_captcha": GeetestLib.NEW_CAPTCHA 109 | }; 110 | this.libResult.setAll(1, JSON.stringify(data), ""); 111 | } 112 | } 113 | 114 | /** 115 | * 正常流程下(即验证初始化成功),二次验证 116 | */ 117 | async successValidate(challenge: string, validate: string, seccode, params) { 118 | this.gtlog(`successValidate(): 开始二次验证 正常模式, challenge=${challenge}, validate=${validate}, seccode=${validate}.`); 119 | if (!this.checkParam(challenge, validate, seccode)) { 120 | this.libResult.setAll(0, "", "正常模式,本地校验,参数challenge、validate、seccode不可为空"); 121 | } else { 122 | const response_seccode = await this.requestValidate(challenge, validate, seccode, params); 123 | if (!response_seccode) { 124 | this.libResult.setAll(0, "", "请求极验validate接口失败"); 125 | } else if (response_seccode === "false") { 126 | this.libResult.setAll(0, "", "极验二次验证不通过"); 127 | } else { 128 | this.libResult.setAll(1, "", ""); 129 | } 130 | } 131 | this.gtlog(`successValidate(): 二次验证 正常模式, lib包返回信息=${this.libResult}.`); 132 | return this.libResult.toRes(); 133 | } 134 | 135 | /** 136 | * 异常流程下(即验证初始化失败,宕机模式),二次验证 137 | * 注意:由于是宕机模式,初衷是保证验证业务不会中断正常业务,所以此处只作简单的参数校验,可自行设计逻辑。 138 | */ 139 | failValidate(challenge, validate, seccode) { 140 | this.gtlog(`failValidate(): 开始二次验证 宕机模式, challenge=${challenge}, validate=${validate}, seccode=${seccode}.`); 141 | if (!this.checkParam(challenge, validate, seccode)) { 142 | this.libResult.setAll(0, "", "宕机模式,本地校验,参数challenge、validate、seccode不可为空."); 143 | } else { 144 | this.libResult.setAll(1, "", ""); 145 | } 146 | this.gtlog(`failValidate(): 二次验证 宕机模式, lib包返回信息=${this.libResult}.`); 147 | return this.libResult; 148 | } 149 | 150 | /** 151 | * 向极验发送二次验证的请求,POST方式 152 | */ 153 | async requestValidate(challenge, validate, seccode, params) { 154 | params = Object.assign(params, { 155 | "seccode": seccode, 156 | "json_format": GeetestLib.JSON_FORMAT, 157 | "challenge": challenge, 158 | "sdk": GeetestLib.VERSION, 159 | "captchaid": this.geetest_id 160 | }); 161 | const validate_url = GeetestLib.API_URL + GeetestLib.VALIDATE_URL; 162 | this.gtlog(`requestValidate(): 二次验证 正常模式, 向极验发送请求, url=${validate_url}, params=${JSON.stringify(params)}.`); 163 | let response_seccode; 164 | try { 165 | const res = await axios({ 166 | url: validate_url, 167 | method: "POST", 168 | timeout: GeetestLib.HTTP_TIMEOUT_DEFAULT, 169 | data: qs.stringify(params), 170 | headers: { "Content-Type": "application/x-www-form-urlencoded" } 171 | }); 172 | const resBody = (res.status === 200) ? res.data : ""; 173 | this.gtlog(`requestValidate(): 二次验证 正常模式, 与极验网络交互正常, 返回码=${res.status}, 返回body=${JSON.stringify(resBody)}.`); 174 | response_seccode = resBody["seccode"]; 175 | } catch (e) { 176 | this.gtlog("requestValidate(): 二次验证 正常模式, 请求异常, " + e.message); 177 | response_seccode = ""; 178 | } 179 | return response_seccode; 180 | } 181 | 182 | /** 183 | * 校验二次验证的三个参数,校验通过返回true,校验失败返回false 184 | */ 185 | checkParam(challenge, validate, seccode) { 186 | return !(challenge == undefined || challenge.trim() === "" || validate == undefined || validate.trim() === "" || seccode == undefined || seccode.trim() === ""); 187 | } 188 | 189 | /** 190 | * md5 加密 191 | */ 192 | md5_encode(value) { 193 | return crypto.createHash("md5").update(value).digest("hex"); 194 | } 195 | 196 | /** 197 | * sha256加密 198 | */ 199 | sha256_encode(value) { 200 | return crypto.createHash("sha256").update(value).digest("hex"); 201 | } 202 | 203 | /** 204 | * hmac-sha256 加密 205 | */ 206 | hmac_sha256_encode(value, key) { 207 | return crypto.createHmac("sha256", key).update(value).digest("hex"); 208 | } 209 | 210 | } 211 | 212 | function randomString(len: number = 32): string { 213 | let $chars: string = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/ 214 | let maxPos: number = $chars.length; 215 | let pwd = ''; 216 | for (let i: number = 0; i < len; i++) { 217 | pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); 218 | } 219 | return pwd.toLocaleLowerCase(); 220 | } 221 | -------------------------------------------------------------------------------- /src/share/geetest/lib/geetest_lib_result.ts: -------------------------------------------------------------------------------- 1 | // sdk lib包的返回结果信息。 2 | export default class GeetestLibResult { 3 | private status: number; 4 | private data: object; 5 | private msg: string; 6 | constructor() { 7 | this.status = 0; // 成功失败的标识码,1表示成功,0表示失败 8 | this.data = {}; // 返回数据,对象 9 | this.msg = ""; // 备注信息,如异常信息等 10 | } 11 | 12 | setAll(status: number, data: string, msg: string) { 13 | this.status = status; 14 | this.data = JSON.parse(data); 15 | this.msg = msg; 16 | } 17 | 18 | toString() { 19 | return `GeetestLibResult{status=${this.status}, data=${JSON.stringify(this.data)}, msg=${this.msg}}`; 20 | } 21 | 22 | toRes() { 23 | return { 24 | status: this.status, 25 | data: this.data, 26 | msg: this.msg 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/share/index.ts: -------------------------------------------------------------------------------- 1 | import DBModule from "./db" 2 | import ScheduleModule from "./schedule/schedule.module" 3 | import { TaskModule } from "./schedule/task.module"; 4 | 5 | // 此处导出 则为导入到Appmodule 上 6 | export default [ 7 | DBModule, 8 | ScheduleModule, 9 | TaskModule 10 | ]; -------------------------------------------------------------------------------- /src/share/jwt/README.md: -------------------------------------------------------------------------------- 1 | ## jwt - json web token 2 | 3 | 在使用jwt基本上是在 用户登录 以及 用户验证 4 | 5 | 6 | 用户验证则使用jwt的装饰器完成 7 | 8 | 9 | ### jwt 装饰器使用 10 | 11 | ```ts 12 | // module 需要引入 13 | import { JwtStrategy } from 'src/share/jwt/jwt.strategy'; 14 | // providers:[JwtStrategy] 15 | 16 | 17 | // controller 18 | import { JwtDecorator } from "src/share/jwt/jwt.decorator" 19 | 20 | @JwtDecorator() 21 | 22 | 23 | ``` -------------------------------------------------------------------------------- /src/share/jwt/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { JwtService } from '@nestjs/jwt'; 3 | 4 | @Injectable() 5 | export class AuthService { 6 | constructor( 7 | private readonly jwtService: JwtService 8 | ) { } 9 | 10 | /** 11 | * 签名 12 | * @param user 13 | * @returns 14 | */ 15 | async sign(user: any) { 16 | let payload = user; 17 | payload.sub = user.id; 18 | // 查询数据库 获取userid 19 | return { 20 | access_token: this.jwtService.sign(payload), 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/share/jwt/jwt.constants.ts: -------------------------------------------------------------------------------- 1 | export const jwtConstants = { 2 | secret: '启嘉网6不6我就说这是一个秘钥你们觉得是不', 3 | }; -------------------------------------------------------------------------------- /src/share/jwt/jwt.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, UseGuards } from '@nestjs/common'; 2 | import { JwtGuard } from "./jwt.guard" 3 | export function JwtDecorator() { 4 | return applyDecorators( 5 | UseGuards(new JwtGuard()), 6 | ); 7 | } -------------------------------------------------------------------------------- /src/share/jwt/jwt.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | import { ExecutionContext, Injectable } from '@nestjs/common'; 3 | 4 | @Injectable() 5 | export class JwtGuard extends AuthGuard('jwt') { 6 | constructor() { 7 | super() 8 | } 9 | /** 10 | * 重写 AuthGuard jwt 的 canActivate 方法 后面可以增加自己的逻辑 11 | */ 12 | canActivate(context: ExecutionContext) { 13 | console.log("jwt守卫") 14 | return super.canActivate(context) 15 | } 16 | } -------------------------------------------------------------------------------- /src/share/jwt/jwt.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { JwtModule as jwtModule } from '@nestjs/jwt'; 3 | import { jwtConstants } from './jwt.constants'; 4 | import { JwtService } from '@nestjs/jwt'; 5 | import { AuthService } from './auth.service'; 6 | import { PassportModule } from '@nestjs/passport'; 7 | import { ConfigModule } from '@nestjs/config'; 8 | 9 | @Module({ 10 | imports: [ 11 | // PassportModule.register({ defaultStrategy: 'jwt' }), 12 | ], 13 | providers: [JwtService], 14 | exports: [AuthService] 15 | }) 16 | class JwtModule extends jwtModule { } 17 | export default JwtModule.register({ 18 | secret: jwtConstants.secret, 19 | signOptions: { expiresIn: '7d' }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/share/jwt/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from 'passport-jwt'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { jwtConstants } from './jwt.constants'; 5 | 6 | @Injectable() 7 | export class JwtStrategy extends PassportStrategy(Strategy) { 8 | constructor() { 9 | super({ 10 | jwtFromRequest: ExtractJwt.fromHeader("token"), 11 | ignoreExpiration: false, 12 | secretOrKey: jwtConstants.secret, 13 | }); 14 | } 15 | 16 | async validate(payload: any) { 17 | return { 18 | id: payload.sub, 19 | username: payload.username, 20 | roles: payload.roles ? payload.roles : [] 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/share/jwt/roles.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | 4 | @Injectable() 5 | export class RolesGuard implements CanActivate { 6 | // constructor(private reflector: Reflector) { } 7 | 8 | canActivate(context: ExecutionContext): boolean { 9 | // const roles = this.reflector.get('roles', context.getHandler()); 10 | // if (!roles) { 11 | // return true; 12 | // } 13 | console.log("role 守卫"); 14 | const request = context.switchToHttp().getRequest(); 15 | const user = request.user; 16 | console.log(user); 17 | const url = request.url; 18 | console.log(url); 19 | return true; 20 | } 21 | } -------------------------------------------------------------------------------- /src/share/oAuth/README.md: -------------------------------------------------------------------------------- 1 | ## 使用 2 | 3 | ```ts 4 | import { oAuthModule } from "src/share/oAuth/oAuth.module" 5 | 6 | ``` -------------------------------------------------------------------------------- /src/share/oAuth/oAuth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import JwtModule from "src/share/jwt/jwt.module" 3 | import { oAuthService } from "./oAuth.service" 4 | import { HttpModule } from '@nestjs/axios'; 5 | import { ConfigService } from '@nestjs/config'; 6 | import { AuthService } from "../jwt/auth.service"; 7 | import { ConfigModule } from "@nestjs/config"; 8 | import oAuthConfig from "./oAuth.config" 9 | @Module({ 10 | imports: [ 11 | ConfigModule.forFeature(oAuthConfig), 12 | JwtModule, 13 | HttpModule 14 | ], 15 | providers: [ 16 | oAuthService, 17 | ConfigService, 18 | AuthService 19 | ], 20 | exports: [oAuthService, JwtModule, AuthService] 21 | }) 22 | export class oAuthModule { } -------------------------------------------------------------------------------- /src/share/oAuth/oAuth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { HttpService } from '@nestjs/axios'; 3 | import { ConfigService } from '@nestjs/config'; 4 | const qs = require('qs'); 5 | // 第三方登录 6 | 7 | @Injectable() 8 | export class oAuthService { 9 | constructor( 10 | private readonly httpService: HttpService, 11 | private readonly configService: ConfigService, 12 | ) { 13 | 14 | } 15 | /** 16 | * 登录 17 | * @param code string code 18 | * @param type string 类型 19 | * @returns Promise 20 | */ 21 | login(code: string, type: string) { 22 | 23 | // https://graph.qq.com/oauth2.0/authorize 24 | // console.log("登录使用", this.httpService); 25 | let oauth_config = this.configService.get('oauth'); 26 | if (!oauth_config[type]) { 27 | return false; 28 | } 29 | let oauth_data = oauth_config[type]; 30 | return this[type](oauth_data, code); 31 | } 32 | /** 33 | * QQ登录获取用户信息 34 | */ 35 | qq(oauth_data: object, code: string) { 36 | // 因rxjs版本即将弃用toPromise 所以采用了自己写Promise 37 | return new Promise(async (resolve) => { 38 | let getTokenData = await new Promise((r) => { 39 | let res = this.httpService.get( 40 | 'https://graph.qq.com/oauth2.0/token', 41 | { 42 | params: { 43 | grant_type: "authorization_code", 44 | client_id: oauth_data['app_key'], 45 | client_secret: oauth_data['app_secret'], 46 | code: code, 47 | redirect_uri: oauth_data['callback'], 48 | fmt: "json" 49 | } 50 | } 51 | ); 52 | res.subscribe( 53 | { 54 | next: result => r(result.data), 55 | error: err => r({ error: true, err }), 56 | // complete: () => console.log('done'), 57 | } 58 | ); 59 | }); 60 | if (getTokenData['error']) { 61 | // access_token 62 | resolve({ 63 | code: false, 64 | data: null, 65 | meta: getTokenData 66 | }); 67 | } 68 | let access_token = getTokenData['access_token']; 69 | let getMeData = await new Promise((r) => { 70 | let res = this.httpService.get( 71 | 'https://graph.qq.com/oauth2.0/me', 72 | { 73 | params: { 74 | access_token: access_token, 75 | fmt: "json" 76 | } 77 | } 78 | ); 79 | res.subscribe( 80 | { 81 | next: result => r(result.data), 82 | error: err => r({ error: true, err }), 83 | // complete: () => console.log('done'), 84 | } 85 | ); 86 | }); 87 | if (getMeData['error']) { 88 | // access_token 89 | resolve({ 90 | code: false, 91 | data: null, 92 | meta: getMeData 93 | }); 94 | } 95 | let openid = getMeData['openid']; 96 | resolve({ 97 | code: true, 98 | data: openid, 99 | meta: getMeData 100 | }); 101 | 102 | }); 103 | 104 | } 105 | 106 | /** 107 | * 微信登录获取用户信息 108 | */ 109 | weixin(oauth_data: object, code: string) { 110 | return new Promise(async (resolve) => { 111 | let getTokenData = await new Promise((r) => { 112 | let res = this.httpService.get( 113 | 'https://api.weixin.qq.com/sns/oauth2/access_token', 114 | { 115 | params: { 116 | appid: oauth_data['app_key'], 117 | secret: oauth_data['app_secret'], 118 | code: code, 119 | grant_type: "authorization_code", 120 | } 121 | } 122 | ); 123 | 124 | res.subscribe( 125 | { 126 | next: result => r(result.data), 127 | error: err => r({ error: true, err }), 128 | // complete: () => console.log('done'), 129 | } 130 | ); 131 | }); 132 | if (getTokenData['error']) { 133 | // access_token 134 | resolve({ 135 | code: false, 136 | data: null, 137 | meta: getTokenData 138 | }); 139 | } 140 | let openid = getTokenData['openid']; 141 | resolve({ 142 | code: true, 143 | data: openid, 144 | meta: getTokenData 145 | }); 146 | }); 147 | 148 | } 149 | 150 | /** 151 | * 微博登录获取用户信息 152 | */ 153 | sina(oauth_data: object, code: string) { 154 | return new Promise(async (resolve) => { 155 | let getTokenData = await new Promise((r) => { 156 | const data = { 157 | client_id: oauth_data['app_key'], 158 | client_secret: oauth_data['app_secret'], 159 | grant_type: "authorization_code", 160 | code: code, 161 | redirect_uri: oauth_data['callback'] 162 | }; 163 | let res = this.httpService.request( 164 | { 165 | url: "https://api.weibo.com/oauth2/access_token?" + qs.stringify(data), 166 | method: "post" 167 | } 168 | 169 | ); 170 | res.subscribe( 171 | { 172 | next: result => r(result.data), 173 | error: err => r({ error: true, err }), 174 | complete: () => console.log('done'), 175 | } 176 | ); 177 | }); 178 | 179 | if (getTokenData['error']) { 180 | console.log(getTokenData); 181 | // access_token 182 | resolve({ 183 | code: false, 184 | data: null, 185 | meta: getTokenData 186 | }); 187 | } 188 | let access_token = getTokenData['access_token']; 189 | let getMeData = await new Promise((r) => { 190 | const data = { access_token: access_token }; 191 | let res = this.httpService.post( 192 | 'https://api.weibo.com/oauth2/get_token_info?' + qs.stringify(data), 193 | ); 194 | res.subscribe( 195 | { 196 | next: result => r(result.data), 197 | error: err => r({ error: true, err }), 198 | // complete: () => console.log('done'), 199 | } 200 | ); 201 | }); 202 | if (getMeData['error']) { 203 | // access_token 204 | resolve({ 205 | code: false, 206 | data: null, 207 | meta: getMeData 208 | }); 209 | } 210 | let openid = getTokenData['uid']; 211 | resolve({ 212 | code: true, 213 | data: openid, 214 | meta: getTokenData 215 | }); 216 | }); 217 | 218 | } 219 | 220 | /** 221 | * 钉钉登录获取用户信息 222 | */ 223 | dingtalk(oauth_data: object, code: string) { 224 | return new Promise(async (resolve) => { 225 | let getTokenData = await new Promise((r) => { 226 | let res = this.httpService.get( 227 | 'https://oapi.dingtalk.com/sns/gettoken', 228 | { 229 | params: { 230 | appid: oauth_data['app_key'], 231 | appsecret: oauth_data['app_secret'], 232 | } 233 | } 234 | ); 235 | res.subscribe( 236 | { 237 | next: result => r(result.data), 238 | error: err => r({ error: true, err }), 239 | // complete: () => console.log('done'), 240 | } 241 | ); 242 | }); 243 | if (getTokenData['error']) { 244 | // access_token 245 | resolve({ 246 | code: false, 247 | data: null, 248 | meta: getTokenData 249 | }); 250 | } 251 | let access_token = getTokenData['access_token']; 252 | let getMeData = await new Promise((r) => { 253 | let res = this.httpService.request( 254 | { 255 | url: "https://oapi.dingtalk.com/sns/get_persistent_code", 256 | method: "post", 257 | params: { access_token: access_token }, 258 | data: { tmp_auth_code: code } 259 | } 260 | 261 | ); 262 | res.subscribe( 263 | { 264 | next: result => r(result.data), 265 | error: err => r({ error: true, err }), 266 | // complete: () => console.log('done'), 267 | } 268 | ); 269 | }); 270 | if (getMeData['error']) { 271 | // access_token 272 | resolve({ 273 | code: false, 274 | data: null, 275 | meta: getMeData 276 | }); 277 | } 278 | let openid = getMeData['openid']; 279 | resolve({ 280 | code: true, 281 | data: openid, 282 | meta: getMeData 283 | }); 284 | 285 | }); 286 | } 287 | 288 | } 289 | -------------------------------------------------------------------------------- /src/share/oAuth/oauth.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs('oauth', () => ({ 3 | //腾讯QQ登录配置 4 | 'qq': { 5 | 'app_key': "", //应用注册成功后分配的 APP ID 6 | 'app_secret': "", //应用注册成功后分配的KEY 7 | 'callback': '', // 应用回调地址 8 | }, 9 | //微信扫码登录配置 10 | 'weixin': { 11 | 'app_key': '', //应用注册成功后分配的 APP ID 12 | 'app_secret': '', //应用注册成功后分配的KEY 13 | 'callback': '', // 应用回调地址 14 | }, 15 | //新浪登录配置 16 | 'sina': { 17 | 'app_key': '', //应用注册成功后分配的 APP ID 18 | 'app_secret': '', //应用注册成功后分配的KEY 19 | 'callback': '', // 应用回调地址 20 | }, 21 | //Baidu登录配置 22 | 'baidu': { 23 | 'app_key': '', //应用注册成功后分配的 APP ID 24 | 'app_secret': '', //应用注册成功后分配的KEY 25 | 'callback': '', // 应用回调地址 26 | }, 27 | //Gitee登录配置 28 | 'gitee': { 29 | 'app_key': '', //应用注册成功后分配的 APP ID 30 | 'app_secret': '', //应用注册成功后分配的KEY 31 | 'callback': '', // 应用回调地址 32 | }, 33 | //Github登录配置 34 | 'github': { 35 | 'app_key': '', //应用注册成功后分配的 APP ID 36 | 'app_secret': '', //应用注册成功后分配的KEY 37 | 'callback': '', // 应用回调地址 38 | }, 39 | //Google登录配置 40 | 'google': { 41 | 'app_key': '', //应用注册成功后分配的 APP ID 42 | 'app_secret': '', //应用注册成功后分配的KEY 43 | 'callback': '', // 应用回调地址 44 | }, 45 | //Facebook登录配置 46 | 'facebook': { 47 | 'app_key': '', //应用注册成功后分配的 APP ID 48 | 'app_secret': '', //应用注册成功后分配的KEY 49 | 'callback': '', // 应用回调地址 50 | }, 51 | //Oschina登录配置 52 | 'oschina': { 53 | 'app_key': '', //应用注册成功后分配的 APP ID 54 | 'app_secret': '', //应用注册成功后分配的KEY 55 | 'callback': '', // 应用回调地址 56 | }, 57 | //Taobao登录配置 58 | 'taobao': { 59 | 'app_key': '', //应用注册成功后分配的 APP ID 60 | 'app_secret': '', //应用注册成功后分配的KEY 61 | 'callback': '', // 应用回调地址 62 | }, 63 | //Douyin登录配置 64 | 'douyin': { 65 | 'app_key': '', //应用注册成功后分配的 APP ID 66 | 'app_secret': '', //应用注册成功后分配的KEY 67 | 'callback': '', // 应用回调地址 68 | }, 69 | //Xiaomi登录配置 70 | 'xiaomi': { 71 | 'app_key': '', //应用注册成功后分配的 APP ID 72 | 'app_secret': '', //应用注册成功后分配的KEY 73 | 'callback': '', // 应用回调地址 74 | }, 75 | //Dingtalk登录配置 76 | 'dingtalk': { 77 | 'app_key': '', //应用注册成功后分配的 APP ID 78 | 'app_secret': '', //应用注册成功后分配的KEY 79 | 'callback': '', // 应用回调地址 80 | } 81 | })); -------------------------------------------------------------------------------- /src/share/office/README.md: -------------------------------------------------------------------------------- 1 | ## office 模块 2 | 3 | 4 | ### 表格 5 | 6 | > npm install exceljs -------------------------------------------------------------------------------- /src/share/office/lib/excel/index.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/exceljs/exceljs/blob/master/README_zh.md 2 | import { Workbook } from "exceljs"; 3 | export default { 4 | async create(data: any[] = [], type: string = "xsl"): Promise { 5 | const workbook = new Workbook(); 6 | workbook.addWorksheet(); 7 | const worksheet = workbook.getWorksheet(1); 8 | worksheet.addRows(data); 9 | let buff; 10 | if (type == "xsl") { 11 | // await workbook.xlsx.writeFile('./1.xls'); 12 | buff = await workbook.xlsx.writeBuffer(); 13 | } else if (type == 'csv') { 14 | // await workbook.xlsx.writeFile('./1.csv'); 15 | buff = await workbook.csv.writeBuffer(); 16 | } 17 | return Buffer.from(buff); 18 | } 19 | } -------------------------------------------------------------------------------- /src/share/office/office.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { OfficeService } from './office.service'; 3 | 4 | @Module({ 5 | providers: [OfficeService], 6 | exports: [OfficeService] 7 | }) 8 | export class OfficeModule { } 9 | -------------------------------------------------------------------------------- /src/share/office/office.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OfficeService } from './office.service'; 3 | 4 | describe('OfficeService', () => { 5 | let service: OfficeService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OfficeService], 10 | }).compile(); 11 | 12 | service = module.get(OfficeService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/share/office/office.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, StreamableFile } from '@nestjs/common'; 2 | import Excel from "./lib/excel" 3 | @Injectable() 4 | export class OfficeService { 5 | 6 | /** 7 | * 快速创建一个简单的表格文件 8 | */ 9 | createExcel(data: any[] = [], type?: string) { 10 | return Excel.create(data, type) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/share/rbac/README.md: -------------------------------------------------------------------------------- 1 | ## rbac 2 | 3 | 用户登录后获取自身角色下的所有权限 4 | 5 | 权限关联着 接口 菜单 路由 行为等 作出约束. 6 | 7 | 前端在针对为进行接口请求时,对已知操作的触发,根据权限作出约束 8 | 9 | 10 | 11 | **RBAC** 12 | - 用户 13 | - 角色 14 | - 权限 15 | - 用户角色关联 16 | - 角色权限关联 17 | 18 | 19 | 20 | 针对约束 21 | - 管道 22 | - 守卫 23 | 24 | 25 | 针对数据上的权限问题 就需要接口针对具体需求具体分析了 -------------------------------------------------------------------------------- /src/share/role/role.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, UseGuards } from '@nestjs/common'; 2 | import { RoleGuard } from "./role.guard" 3 | import { JwtGuard } from '../jwt/jwt.guard'; 4 | /** 5 | * 权限装饰器 绑定后默认绑定jwt装饰器 6 | * @returns 7 | */ 8 | export function RoleDecorator(config?: any) { 9 | return applyDecorators( 10 | UseGuards(new JwtGuard(), new RoleGuard(config)), 11 | ); 12 | } -------------------------------------------------------------------------------- /src/share/role/role.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | 4 | @Injectable() 5 | export class RoleGuard implements CanActivate { 6 | constructor( 7 | private config?: any, 8 | private reflector?: Reflector 9 | ) { } 10 | 11 | canActivate(context: ExecutionContext): boolean { 12 | // 可以反射获取装饰器的参数 13 | // const roles = this.reflector.get('role', context.getHandler()); 14 | // if (!roles) { 15 | // return true; 16 | // } 17 | console.log("role 守卫", this.config); 18 | const request = context.switchToHttp().getRequest(); 19 | const user = request.user; 20 | const url = request.url; 21 | console.log(url); 22 | if (!user.roles.find((item) => { return item = url })) { 23 | return false 24 | } 25 | return true; 26 | } 27 | } -------------------------------------------------------------------------------- /src/share/schedule/schedule.module.ts: -------------------------------------------------------------------------------- 1 | import { ScheduleModule } from "@nestjs/schedule" 2 | export default ScheduleModule.forRoot(); 3 | -------------------------------------------------------------------------------- /src/share/schedule/task.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TasksService } from './task.service'; 3 | @Module({ 4 | providers: [TasksService] 5 | }) 6 | export class TaskModule { } 7 | -------------------------------------------------------------------------------- /src/share/schedule/task.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common'; 2 | import { Cron } from '@nestjs/schedule'; 3 | 4 | @Injectable() 5 | export class TasksService { 6 | private readonly logger = new Logger(TasksService.name); 7 | 8 | 9 | @Cron('*/2 * * * * *') 10 | handleCron() { 11 | this.logger.debug('定时任务例子 每两秒执行一次'); 12 | } 13 | } 14 | 15 | 16 | // @Cron('*/2 * * * * *') 2秒执行一次 17 | // @Cron('* * * * * *') 1秒执行一次 18 | // @Cron('0 * * * * *') 每分钟执行一次 19 | // @Cron('0 0 * * * *') 每小时执行一次 -------------------------------------------------------------------------------- /src/share/sms/README.md: -------------------------------------------------------------------------------- 1 | ## 短信模块 阿里云 2 | 3 | > npm install @alicloud/pop-core -S -------------------------------------------------------------------------------- /src/share/sms/sms.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs("sms", () => ({ 3 | accessKeyId: 'accessKeyId', 4 | accessKeySecret: 'accessKeySecret', 5 | endpoint: 'https://dysmsapi.aliyuncs.com', 6 | apiVersion: '2017-05-25', 7 | 8 | SignName: "SignName", 9 | // 发送验证码模板 10 | TemplateCode_Code: "TemplateCode_Code" 11 | })); -------------------------------------------------------------------------------- /src/share/sms/sms.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { SmsService } from './sms.service'; 3 | import { ConfigModule } from '@nestjs/config'; 4 | import smsConfig from './sms.config'; 5 | @Module({ 6 | imports: [ConfigModule.forFeature(smsConfig)], 7 | providers: [SmsService], 8 | exports: [SmsService] 9 | }) 10 | export class SmsModule { } 11 | -------------------------------------------------------------------------------- /src/share/sms/sms.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { SmsService } from './sms.service'; 3 | 4 | describe('SmsService', () => { 5 | let service: SmsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [SmsService], 10 | }).compile(); 11 | 12 | service = module.get(SmsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/share/sms/sms.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | const Core = require('@alicloud/pop-core'); 4 | import { randomNumber } from 'src/utils'; 5 | @Injectable() 6 | export class SmsService { 7 | private config: any; 8 | private client: any; 9 | constructor(private readonly configService: ConfigService) { 10 | this.config = this.configService.get('sms'); 11 | this.client = new Core(this.config); 12 | } 13 | 14 | /** 15 | * 16 | * 发送验证码 17 | * @param phone string 手机号 18 | */ 19 | sendCode(phone: string) { 20 | return new Promise(async (resolve, reject) => { 21 | let code = randomNumber(4); 22 | let params = { 23 | "PhoneNumbers": phone, 24 | "SignName": this.config.SignName, 25 | "TemplateCode": this.config.TemplateCode_Code, 26 | "TemplateParam": JSON.stringify({ 27 | code: code 28 | }) 29 | } 30 | let requestOption = { 31 | method: 'POST' 32 | }; 33 | let r: any; 34 | try { 35 | r = await this.client.request('SendSms', params, requestOption); 36 | } catch (error) { 37 | reject(error); 38 | } 39 | if (r.Code == 'OK') { 40 | resolve(code); 41 | } else { 42 | reject(r); 43 | } 44 | }); 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/share/storage/README.md: -------------------------------------------------------------------------------- 1 | ## 对象存储 2 | 3 | ### oss 阿里云对象存储 4 | 5 | #### 依赖 6 | 7 | > npm install ali-oss --save 8 | 9 | 10 | ### 如何使用 -------------------------------------------------------------------------------- /src/share/storage/lib/BaseStorage.ts: -------------------------------------------------------------------------------- 1 | export class BaseStorage { 2 | 3 | 4 | } 5 | -------------------------------------------------------------------------------- /src/share/storage/lib/OssStorage.ts: -------------------------------------------------------------------------------- 1 | let OSS = require('ali-oss'); 2 | 3 | import { BaseStorage } from "./BaseStorage" 4 | export class OssStorage implements BaseStorage { 5 | 6 | private client: any; 7 | private config: any; 8 | constructor(config: any) { 9 | this.client = new OSS(config); 10 | this.config = config; 11 | } 12 | /** 13 | * web直传 服务签名 14 | * @returns Promise 15 | */ 16 | CreateUpload(opstion: any) { 17 | return new Promise(async (resolve) => { 18 | const date = new Date(); 19 | date.setDate(date.getDate() + 1); 20 | const policy = { 21 | expiration: date.toISOString(), //设置Unix时间戳(自UTC时间1970年01月01号开始的秒数),用于标识该请求的超时时间。 22 | conditions: [ 23 | ["content-length-range", 0, 1048576000], //设置上传文件的大小限制。 24 | ], 25 | }; 26 | const formData = this.client.calculatePostSignature(policy); 27 | // 填写Bucket外网域名。 28 | const host = `http://${this.config.endpoint}`.toString(); 29 | // 返回参数。 30 | const params = { 31 | expire: Math.floor(Date.parse(date.toISOString()) / 1000), 32 | policy: formData.policy, // 从OSS服务器获取到的Policy。 33 | signature: formData.Signature, // 从OSS服务器获取到的Signature。 34 | accessid: formData.OSSAccessKeyId,// 从OSS服务器获取到的OSS AccessKeyId。 35 | host, // 格式为https://bucketname.endpoint,例如https://bucket-name.oss-cn-hangzhou.aliyuncs.com。 36 | // callback: Buffer.from(JSON.stringify(callback)).toString("base64"),// 通过Buffer.from对JSON进行Base64编码。 37 | dir: opstion.dir,// 获取到的文件前缀。 38 | }; 39 | resolve(params); 40 | }); 41 | } 42 | 43 | } 44 | 45 | 46 | /*** 47 | * 前端上传参考 48 | * 49 | * https://help.aliyun.com/document_detail/322691.html 50 | * 51 | */ -------------------------------------------------------------------------------- /src/share/storage/storage.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs('storage', () => ({ 3 | oss: { 4 | bucket: "bucket", 5 | region: 'region', 6 | accessKeyId: 'accessKeyId', 7 | accessKeySecret: 'accessKeySecret', 8 | endpoint: "endpoint" 9 | } 10 | })); -------------------------------------------------------------------------------- /src/share/storage/storage.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { StorageService } from './storage.service'; 3 | import { ConfigModule } from "@nestjs/config" 4 | import StorageConfig from "./storage.config" 5 | import { ConfigService } from '@nestjs/config'; 6 | @Module({ 7 | imports: [ConfigModule.forFeature(StorageConfig)], 8 | providers: [ConfigService, StorageService], 9 | exports: [StorageModule, ConfigService, StorageService] 10 | }) 11 | export class StorageModule { } 12 | -------------------------------------------------------------------------------- /src/share/storage/storage.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule, ConfigService } from '@nestjs/config'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import storageConfig from './storage.config'; 4 | import { StorageModule } from './storage.module'; 5 | import { StorageService } from './storage.service'; 6 | 7 | describe('StorageService', () => { 8 | let service: StorageService; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | imports: [ConfigModule.forFeature(storageConfig)], 13 | providers: [ConfigService, StorageService], 14 | // exports: [StorageModule, ConfigService] 15 | }).compile(); 16 | 17 | service = module.get(StorageService); 18 | }); 19 | 20 | it('should be defined', () => { 21 | expect(service).toBeDefined(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/share/storage/storage.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { OssStorage } from './lib/OssStorage'; 4 | import { ConfigService } from '@nestjs/config'; 5 | @Injectable() 6 | export class StorageService { 7 | constructor( 8 | private readonly configService: ConfigService 9 | ) { } 10 | 11 | /** 12 | * 创建web直传签名 13 | * @param opstion 配置参数 14 | * @returns 15 | */ 16 | CreateUpload(opstion: any) { 17 | let config = this.configService.get("storage.oss"); 18 | let ossStorage = new OssStorage(config); 19 | return ossStorage.CreateUpload(opstion); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/share/video/README.md: -------------------------------------------------------------------------------- 1 | ## 视频点播 2 | 3 | 4 | ### 阿里云ovd 视频点播 5 | 6 | > npm install @alicloud/pop-core --save 7 | 8 | 9 | ### 如何使用 10 | 11 | -------------------------------------------------------------------------------- /src/share/video/video.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from "@nestjs/config" 2 | export default registerAs('video', () => ({ 3 | vod: { 4 | accessKeyId: "", 5 | accessKeySecret: "", 6 | endpoint: 'http://vod.cn-shanghai.aliyuncs.com', 7 | apiVersion: '2017-03-21' 8 | } 9 | })); -------------------------------------------------------------------------------- /src/share/video/video.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { ConfigService } from '@nestjs/config'; 4 | import VideoConfig from "./video.config" 5 | import { VideoService } from './video.service'; 6 | @Module({ 7 | imports: [ConfigModule.forFeature(VideoConfig)], 8 | providers: [ConfigService, VideoService], 9 | exports: [ConfigService, VideoService] 10 | }) 11 | export class VideoModule { } 12 | -------------------------------------------------------------------------------- /src/share/video/video.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule, ConfigService } from '@nestjs/config'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import videoConfig from './video.config'; 4 | import { VideoService } from './video.service'; 5 | 6 | describe('VideoService', () => { 7 | let service: VideoService; 8 | 9 | beforeEach(async () => { 10 | const module: TestingModule = await Test.createTestingModule({ 11 | imports: [ConfigModule.forFeature(videoConfig)], 12 | providers: [ConfigService, VideoService], 13 | // exports: [ConfigService, VideoService] 14 | }).compile(); 15 | 16 | service = module.get(VideoService); 17 | }); 18 | 19 | it('should be defined', () => { 20 | expect(service).toBeDefined(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/share/video/video.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | const RPCClient = require('@alicloud/pop-core').RPCClient; 4 | @Injectable() 5 | export class VideoService { 6 | private config: object; 7 | private client: any; 8 | constructor( 9 | private readonly configService: ConfigService 10 | ) { 11 | this.config = this.configService.get("video.vod"); 12 | this.client = new RPCClient(this.config); 13 | } 14 | /** 15 | * 创建视频上传凭证 16 | * @returns Promise 17 | */ 18 | CreateUploadVideo(VideoInfo: object) { 19 | return new Promise(async (resolve) => { 20 | let res; 21 | try { 22 | res = await this.client.request("CreateUploadVideo", VideoInfo); 23 | 24 | } catch (error) { 25 | resolve(error.data); 26 | return; 27 | 28 | } 29 | resolve(res); 30 | }); 31 | 32 | } 33 | /** 34 | * 刷新视频上传凭证 35 | * @returns Promise 36 | */ 37 | RefreshUploadVideo(VideoId: string) { 38 | return new Promise(async (resolve) => { 39 | let res; 40 | try { 41 | res = await this.client.request("RefreshUploadVideo", { 42 | VideoId: VideoId 43 | }); 44 | 45 | } catch (error) { 46 | resolve(error.data); 47 | return; 48 | 49 | } 50 | resolve(res); 51 | }); 52 | } 53 | 54 | /** 55 | * 获取视频信息 56 | * @returns Promise 57 | */ 58 | GetPlayInfo(VideoId: string) { 59 | return new Promise(async (resolve) => { 60 | let res; 61 | try { 62 | res = await this.client.request("RefreshUploadVideo", { 63 | VideoId: VideoId 64 | }); 65 | 66 | } catch (error) { 67 | resolve(error.data); 68 | return; 69 | 70 | } 71 | resolve(res); 72 | }); 73 | } 74 | 75 | /** 76 | * 获取视频播放凭证 77 | * @returns Promise 78 | */ 79 | GetVideoPlayAuth(VideoId: string) { 80 | return new Promise(async (resolve) => { 81 | let res; 82 | try { 83 | res = await this.client.request("GetVideoPlayAuth", { 84 | VideoId: VideoId 85 | }); 86 | 87 | } catch (error) { 88 | resolve(error.data); 89 | return; 90 | 91 | } 92 | resolve(res); 93 | }); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/utils/README.md: -------------------------------------------------------------------------------- 1 | ### 工具方法函数 2 | 3 | 一般简单方法集合 4 | 5 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./random" 2 | export * from "./md5" -------------------------------------------------------------------------------- /src/utils/isType.ts: -------------------------------------------------------------------------------- 1 | const isType = function (o) { 2 | let s = Object.prototype.toString.call(o); 3 | return s.match(/\[object (.*?)\]/)[1].toLowerCase(); 4 | }; 5 | 6 | export const isPrimitive = (o) => { 7 | let name = isType(o); 8 | return ( 9 | name === "string" || 10 | name === "number" || 11 | name === "symbol" || 12 | name === "boolean" 13 | ); 14 | }; 15 | export const isDate = (o) => { 16 | return isType(o) === "date"; 17 | }; 18 | export const isNumber = (o) => { 19 | return isType(o) === "number"; 20 | }; 21 | export const isString = (o) => { 22 | return isType(o) === "string"; 23 | }; 24 | export const isObject = (o) => { 25 | return isType(o) === "object"; 26 | }; 27 | export const isArray = (o) => { 28 | return isType(o) === "array"; 29 | }; 30 | export const isBuffer = (o) => { 31 | return isType(o) === "buffer"; 32 | }; 33 | -------------------------------------------------------------------------------- /src/utils/md5.ts: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | /** 3 | * md5 4 | * @param value string 加密字符 5 | * @returns 6 | */ 7 | export function md5_encode(value: string) { 8 | return crypto.createHash("md5").update(value).digest("hex"); 9 | } -------------------------------------------------------------------------------- /src/utils/moment.ts: -------------------------------------------------------------------------------- 1 | import { isDate, isString, isNumber } from "./isType"; 2 | 3 | const format = (date: any, format: string, isUTC: any) => { 4 | if (!isNumber(date) && !isString(date) && !isDate(date)) { 5 | throw new Error("The first parameter must be number, string, Date object"); 6 | } 7 | 8 | let d = date; 9 | 10 | if (isNumber(date) || isString(date)) { 11 | d = new Date(date); 12 | } 13 | 14 | if (!isString(format)) { 15 | return d.toString(); 16 | } 17 | const year = isUTC 18 | ? d.getUTCFullYear().toString() 19 | : d.getFullYear().toString(); 20 | const month = isUTC 21 | ? (d.getUTCMonth() + 1).toString() 22 | : (d.getMonth() + 1).toString(); 23 | const day = isUTC ? d.getUTCDate().toString() : d.getDate().toString(); 24 | const hour = isUTC ? d.getUTCHours().toString() : d.getHours().toString(); 25 | const hour12 = (hour % 12).toString(); 26 | const amOrPm = hour < 12 ? "AM" : "PM"; 27 | const minute = isUTC 28 | ? d.getUTCMinutes().toString() 29 | : d.getMinutes().toString(); 30 | const second = isUTC 31 | ? d.getUTCSeconds().toString() 32 | : d.getSeconds().toString(); 33 | const millisecond = isUTC 34 | ? d.getUTCMilliseconds().toString() 35 | : d.getMilliseconds().toString(); 36 | 37 | return format 38 | .replace(/(^|[^Y])YYYY([^Y]|$)/g, `$1${year}$2`) 39 | .replace(/(^|[^Y])YY([^Y]|$)/g, `$1${String(year).slice(-2)}$2`) 40 | .replace(/(^|[^M])MM([^M]|$)/g, `$1${month.padStart(2, "0")}$2`) 41 | .replace(/(^|[^M])M([^M]|$)/g, `$1${month}$2`) 42 | .replace(/(^|[^D])DD([^D]|$)/g, `$1${day.padStart(2, "0")}$2`) 43 | .replace(/(^|[^D])D([^D]|$)/g, `$1${day}$2`) 44 | .replace(/(^|[^H])HH([^H]|$)/g, `$1${hour.padStart(2, "0")}$2`) 45 | .replace(/(^|[^H])H([^H]|$)/g, `$1${hour}$2`) 46 | .replace(/(^|[^h])hh([^h]|$)/g, `$1${hour12.padStart(2, "0")}$2`) 47 | .replace(/(^|[^h])h([^h]|$)/g, `$1${hour12}$2`) 48 | .replace(/(^|[^A])A([^A]|$)/g, `$1${amOrPm}$2`) 49 | .replace(/(^|[^a])a([^a]|$)/g, `$1${amOrPm.toLowerCase()}$2`) 50 | .replace(/(^|[^m])mm([^m]|$)/g, `$1${minute.padStart(2, "0")}$2`) 51 | .replace(/(^|[^m])m([^m]|$)/g, `$1${minute}$2`) 52 | .replace(/(^|[^s])ss([^s]|$)/g, `$1${second.padStart(2, "0")}$2`) 53 | .replace(/(^|[^s])s([^s]|$)/g, `$1${second}$2`) 54 | .replace( 55 | /(^|[^S]+)([S]+)([^S]+|$)/g, 56 | (match: any, s1: any, s2: string | any[], s3: any) => { 57 | let msStr = millisecond.padStart(3, "0"); 58 | for (let i = 3; i < s2.length; i++) { 59 | msStr += "0"; 60 | } 61 | msStr = msStr.slice(0, s2.length); 62 | return `${s1}${msStr}${s3}`; 63 | } 64 | ); 65 | }; 66 | 67 | export { format }; -------------------------------------------------------------------------------- /src/utils/random.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 随机字符串 3 | * @param len 位数 4 | * @returns 5 | */ 6 | export function randomString(len: number = 4) { 7 | let $chars: string = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; 8 | var maxPos = $chars.length; 9 | var pwd = ''; 10 | for (let i: number = 0; i < len; i++) { 11 | pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); 12 | } 13 | return pwd; 14 | } 15 | 16 | /** 17 | * 随机数字 18 | * @param len 位数 19 | * @returns 20 | */ 21 | export function randomNumber(len: number = 4) { 22 | let $chars: string = '23456789'; 23 | var maxPos = $chars.length; 24 | var pwd = ''; 25 | for (let i: number = 0; i < len; i++) { 26 | pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); 27 | } 28 | return pwd; 29 | } 30 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | --------------------------------------------------------------------------------