├── .gitignore ├── .prettierrc ├── Dockerfile ├── README.md ├── Vagrantfile ├── eslint.config.js ├── nest-cli.json ├── package.json ├── src ├── app.module.ts ├── app_cluster.service.ts ├── controller │ ├── app.controller.ts │ ├── dto │ │ ├── req.dto.ts │ │ └── res.dto.ts │ └── h5st.controller.ts ├── factory │ ├── h5st.factory.ts │ └── token.factory.ts ├── filter │ ├── business-exception.filter.ts │ └── validation-exception.filter.ts ├── main.ts ├── services │ ├── cache │ │ ├── cache.config.ts │ │ ├── cache.service.ts │ │ └── none-cache.service.ts │ ├── h5st.service.ts │ ├── h5st │ │ ├── baseH5st.ts │ │ ├── constant.ts │ │ ├── customAlgorithm.ts │ │ ├── h5st4.2.0.ts │ │ ├── h5st4.3.1.ts │ │ ├── h5st4.3.3.ts │ │ ├── h5st4.4.0.ts │ │ ├── h5st4.7.1.ts │ │ ├── h5st4.7.2.ts │ │ ├── h5st4.7.3.ts │ │ ├── h5st4.7.4.ts │ │ ├── h5st4.8.1.ts │ │ ├── h5st4.8.2.ts │ │ ├── h5st4.9.1.ts │ │ ├── h5stAlgoConfig.ts │ │ ├── type.ts │ │ ├── xcx3.1.0.ts │ │ ├── xcx4.2.0.ts │ │ ├── xcx4.7.1.ts │ │ └── xcx4.9.1.ts │ ├── logger │ │ └── winston.config.ts │ └── token │ │ ├── baseLocalToken.ts │ │ ├── localTokenV3.ts │ │ └── localTokenV4.ts └── utils │ ├── baseUtils.ts │ └── error.ts ├── tsconfig.build.json ├── tsconfig.json └── types └── nestjs-cls.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | /yarn.lock 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | pnpm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | 16 | # OS 17 | .DS_Store 18 | 19 | # Tests 20 | /coverage 21 | /.nyc_output 22 | 23 | # IDEs and editors 24 | /.idea 25 | .project 26 | .classpath 27 | .c9/ 28 | *.launch 29 | .settings/ 30 | *.sublime-workspace 31 | 32 | # IDE - VSCode 33 | .vscode/* 34 | !.vscode/settings.json 35 | !.vscode/tasks.json 36 | !.vscode/launch.json 37 | !.vscode/extensions.json 38 | 39 | # dotenv environment variable files 40 | .env 41 | .env.development.local 42 | .env.test.local 43 | .env.production.local 44 | .env.local 45 | 46 | # temp directory 47 | .temp 48 | .tmp 49 | 50 | # Runtime data 51 | pids 52 | *.pid 53 | *.seed 54 | *.pid.lock 55 | 56 | # Diagnostic reports (https://nodejs.org/api/report.html) 57 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 58 | 59 | .vagrant 60 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 180, 4 | "semi": true, 5 | "trailingComma": "all" 6 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | EXPOSE 3001 4 | ENV TZ=Asia/Shanghai 5 | 6 | COPY --chmod=755 ./dist/app /usr/local/bin/ 7 | 8 | CMD ["/usr/local/bin/app"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 该仓库仅限于交流学习使用!请勿用于非法活动! 2 | # 该仓库仅限于交流学习使用!请勿用于非法活动! 3 | # 该仓库仅限于交流学习使用!请勿用于非法活动! 4 | 5 | 关联仓库:[Docker](https://hub.docker.com/r/zhx47/jd_h5st_server) 6 | 7 | 关联博客:[京东H5ST算法接口](https://blog.zhx47.top/archives/1719150000000) 8 | 9 | # 不接定制化脚本!!! 10 | 11 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/bookworm64" 3 | 4 | config.vm.network "private_network", ip: "192.168.56.2" 5 | 6 | config.vm.provider "virtualbox" do |vb| 7 | vb.name = "jd_server_nestjs" 8 | vb.cpus = 2 9 | vb.memory = "2048" 10 | end 11 | 12 | config.vm.provision "shell", inline: <<-SHELL 13 | # 自动切换到root用户 14 | echo "sudo su -" >> .bashrc 15 | SHELL 16 | 17 | config.vm.provision "shell", privileged: true, inline: <<-SHELL 18 | # 设置root用户,密码root 19 | echo "root:root" | chpasswd 20 | sed -i 's/^#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config 21 | sed -i 's/^PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config 22 | sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config 23 | sed -i 's/^PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config 24 | systemctl restart sshd 25 | 26 | # 时间自动更新 27 | apt-get update 28 | apt-get upgrade 29 | apt-get install -y chrony curl vim wget 30 | timedatectl set-timezone Asia/Shanghai 31 | echo "server cn.pool.ntp.org iburst" >> /etc/chrony/chrony.conf 32 | systemctl restart chronyd 33 | # 在root用户的bashrc中添加首次登录时的时间同步 34 | echo "chronyc -a makestep" >> /root/.bashrc 35 | 36 | # 安装 Docker 37 | curl -fsSL https://get.docker.com -o get-docker.sh 38 | sh get-docker.sh 39 | 40 | # 配置 Docker 使用 TCP 监听 41 | mkdir -p /etc/systemd/system/docker.service.d 42 | echo '[Service]' >> /etc/systemd/system/docker.service.d/override.conf 43 | echo 'ExecStart=' >> /etc/systemd/system/docker.service.d/override.conf 44 | echo 'ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375' >> /etc/systemd/system/docker.service.d/override.conf 45 | systemctl daemon-reload 46 | systemctl restart docker 47 | SHELL 48 | end 49 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const eslint = require("@eslint/js"); 2 | const globals = require("globals"); 3 | const tsEslint = require("typescript-eslint"); 4 | const eslintConfigPrettier = require("eslint-config-prettier"); 5 | 6 | module.exports = [ 7 | { 8 | ignores: [ 9 | "**/.eslintrc.js", 10 | "**/eslint.config.mjs", 11 | "**/eslint.config.cjs", 12 | "**/eslint.config.js", 13 | ], 14 | }, 15 | eslint.configs.recommended, 16 | ...tsEslint.configs.recommendedTypeChecked, 17 | ...tsEslint.configs.stylisticTypeChecked, 18 | eslintConfigPrettier, 19 | { 20 | languageOptions: { 21 | globals: { 22 | ...globals.node, 23 | }, 24 | parser: tsEslint.parser, 25 | parserOptions: { 26 | projectService: true, 27 | tsconfigRootDir: __dirname, 28 | }, 29 | }, 30 | rules: { 31 | '@typescript-eslint/interface-name-prefix': 'off', 32 | '@typescript-eslint/explicit-function-return-type': 'off', 33 | '@typescript-eslint/explicit-module-boundary-types': 'off', 34 | '@typescript-eslint/prefer-nullish-coalescing': 'off', 35 | '@typescript-eslint/no-explicit-any': 'off', 36 | '@typescript-eslint/no-unused-vars': [ 37 | 'error', 38 | { 39 | argsIgnorePattern: '^_', 40 | }, 41 | ], 42 | 'function-paren-newline': ['error', 'consistent'], 43 | }, 44 | }, 45 | ]; -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jd_h5st_server", 3 | "version": "0.0.1", 4 | "description": "京东h5st 接口版本", 5 | "author": "zhx47", 6 | "private": false, 7 | "license": "UNLICENSED", 8 | "url": "https://github.com/zhx47/jd_h5st_server", 9 | "bin": "./dist/index.js", 10 | "scripts": { 11 | "build": "nest build && ncc build dist/main.js -m", 12 | "format": "prettier --write \"src/**/*.ts\"", 13 | "start": "nest start", 14 | "start:dev": "nest start --watch", 15 | "start:debug": "nest start --debug --watch", 16 | "lint": "eslint \"{src,types}/**/*.ts\" --fix", 17 | "pkg:amd64": "yarn run build && pkg . -t node18-alpine-x64 -o ./dist/app", 18 | "pkg:arm64": "yarn run build && pkg . -t node18-alpine-arm64 -o ./dist/app" 19 | }, 20 | "dependencies": { 21 | "@keyv/redis": "^3.0.1", 22 | "@nestjs/common": "^10.4.3", 23 | "@nestjs/config": "^3.2.3", 24 | "@nestjs/core": "^10.4.3", 25 | "@nestjs/platform-express": "^10.4.3", 26 | "adler-32": "^1.3.1", 27 | "cache-manager": "^6.0.1", 28 | "class-transformer": "^0.5.1", 29 | "class-validator": "^0.14.1", 30 | "crypto-js": "^4.2.0", 31 | "express": "^4.21.1", 32 | "nest-winston": "^1.9.7", 33 | "nestjs-cache-manager-v6": "^2.2.6", 34 | "nestjs-cls": "^4.4.1", 35 | "qs": "^6.13.0", 36 | "reflect-metadata": "^0.2.2", 37 | "rxjs": "^7.8.1", 38 | "uuid": "^10.0.0", 39 | "winston": "^3.15.0" 40 | }, 41 | "devDependencies": { 42 | "@eslint/js": "^9.12.0", 43 | "@nestjs/cli": "^10.4.5", 44 | "@types/crypto-js": "^4.2.2", 45 | "@types/express": "^5.0.0", 46 | "@types/node": "^22.7.5", 47 | "@types/qs": "^6.9.16", 48 | "@types/uuid": "^10.0.0", 49 | "eslint": "^9.12.0", 50 | "eslint-config-prettier": "^9.1.0", 51 | "globals": "^15.11.0", 52 | "prettier": "^3.3.3", 53 | "typescript-eslint": "^8.8.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: app.module.ts 3 | * Description: nestjs 注入 4 | * Author: zhx47 5 | */ 6 | 7 | import { Module } from '@nestjs/common'; 8 | import { ConfigModule, ConfigService } from '@nestjs/config'; 9 | import { ClsModule, ClsService } from 'nestjs-cls'; 10 | import { AppController } from './controller/app.controller'; 11 | import { H5stService } from './services/h5st.service'; 12 | import { H5stController } from './controller/h5st.controller'; 13 | import { H5stFactory } from './factory/h5st.factory'; 14 | import { H5st420 } from './services/h5st/h5st4.2.0'; 15 | import { H5st431 } from './services/h5st/h5st4.3.1'; 16 | import { H5st433 } from './services/h5st/h5st4.3.3'; 17 | import { H5st440 } from './services/h5st/h5st4.4.0'; 18 | import { H5st471 } from './services/h5st/h5st4.7.1'; 19 | import { H5st472 } from './services/h5st/h5st4.7.2'; 20 | import { H5st473 } from './services/h5st/h5st4.7.3'; 21 | import { H5st474 } from './services/h5st/h5st4.7.4'; 22 | import { H5st481 } from './services/h5st/h5st4.8.1'; 23 | import { H5st482 } from './services/h5st/h5st4.8.2'; 24 | import { H5st491 } from './services/h5st/h5st4.9.1'; 25 | import { Xcx310 } from './services/h5st/xcx3.1.0'; 26 | import { Xcx420 } from './services/h5st/xcx4.2.0'; 27 | import { Xcx471 } from './services/h5st/xcx4.7.1'; 28 | import { Xcx491 } from './services/h5st/xcx4.9.1'; 29 | import { TokenFactory } from './factory/token.factory'; 30 | import { LocalTokenV3 } from './services/token/localTokenV3'; 31 | import { LocalTokenV4 } from './services/token/localTokenV4'; 32 | import { CacheModule } from 'nestjs-cache-manager-v6'; 33 | import { WinstonModule } from 'nest-winston'; 34 | import { CacheConfigService } from './services/cache/cache.service'; 35 | import cacheConfig from './services/cache/cache.config'; 36 | import { WinstonConfigService } from './services/logger/winston.config'; 37 | import { v4 } from 'uuid'; 38 | import { Request } from 'express'; 39 | import { CustomAlgorithm } from './services/h5st/customAlgorithm'; 40 | 41 | @Module({ 42 | imports: [ 43 | // 在 中间件 初始化上下文对象 44 | ClsModule.forRoot({ 45 | global: true, 46 | middleware: { 47 | mount: true, 48 | generateId: true, 49 | idGenerator: (req: Request) => req.header('cf-ray') ?? v4(), 50 | }, 51 | }), 52 | ConfigModule.forRoot({ 53 | envFilePath: ['.env'], 54 | isGlobal: true, 55 | load: [cacheConfig], 56 | }), 57 | CacheModule.registerAsync({ 58 | isGlobal: true, 59 | useClass: CacheConfigService, 60 | inject: [ConfigService], 61 | }), 62 | WinstonModule.forRootAsync({ 63 | useClass: WinstonConfigService, 64 | inject: [ClsService], 65 | }), 66 | ], 67 | controllers: [AppController, H5stController], 68 | providers: [ 69 | CustomAlgorithm, 70 | H5stService, 71 | H5stFactory, 72 | H5st420, 73 | H5st431, 74 | H5st433, 75 | H5st440, 76 | H5st471, 77 | H5st472, 78 | H5st473, 79 | H5st474, 80 | H5st481, 81 | H5st482, 82 | H5st491, 83 | Xcx310, 84 | Xcx420, 85 | Xcx471, 86 | Xcx491, 87 | TokenFactory, 88 | LocalTokenV3, 89 | LocalTokenV4, 90 | ], 91 | }) 92 | export class AppModule {} 93 | -------------------------------------------------------------------------------- /src/app_cluster.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: app_cluster.service.ts 3 | * Description: cluster 多进程启动 4 | * Author: zhx47 5 | */ 6 | 7 | import * as os from 'os'; 8 | import { Logger } from '@nestjs/common'; 9 | import * as clusterModule from 'cluster'; 10 | import { Cluster } from 'cluster'; 11 | 12 | const cluster = clusterModule as unknown as Cluster; 13 | 14 | const numCPUs = os.cpus().length; 15 | 16 | export class AppClusterService { 17 | private static readonly logger = new Logger(AppClusterService.name); 18 | 19 | static clusterize(callback: CallableFunction): void { 20 | if (cluster.isPrimary) { 21 | this.logger.log(`Master server started on ${process.pid}`); 22 | for (let i = 0; i < numCPUs; i++) { 23 | cluster.fork(); 24 | } 25 | cluster.on('exit', (worker, _code, _signal) => { 26 | this.logger.warn(`Worker ${worker.process.pid} died. Restarting`); 27 | cluster.fork(); 28 | }); 29 | } else { 30 | this.logger.log(`Cluster server started on ${process.pid}`); 31 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call 32 | callback(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/controller/app.controller.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: app.controller.ts 3 | * Description: 健康检测 4 | * Author: zhx47 5 | */ 6 | 7 | import { Controller, Get } from '@nestjs/common'; 8 | import { ResBaseDto } from './dto/res.dto'; 9 | 10 | @Controller() 11 | export class AppController { 12 | @Get('/health') 13 | getHello(): ResBaseDto { 14 | return new ResBaseDto('OK'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/controller/dto/req.dto.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: req.dto.ts 3 | * Description: 请求的自定义格式 4 | * Author: zhx47 5 | */ 6 | 7 | import 'reflect-metadata'; 8 | import { IsEnum, IsJSON, IsNotEmpty, ValidateIf, ValidateNested } from 'class-validator'; 9 | import { Transform, Type } from 'class-transformer'; 10 | import { H5stSignParamsType, H5stVersion } from '../../services/h5st/type'; 11 | import { ContainsChar } from '../../utils/baseUtils'; 12 | 13 | /** 14 | * h5st 的业务信息,真正发送给京东的信息,这里只定义三个必传的字段 15 | */ 16 | export class H5stBusinessBody implements H5stSignParamsType { 17 | @Type(() => String) 18 | @IsNotEmpty({ message: 'functionId不能为空' }) 19 | functionId: string; 20 | 21 | @Type(() => String) 22 | @IsNotEmpty({ message: 'appid不能为空' }) 23 | appid: string; 24 | 25 | @Transform(({ value }): string => (value && typeof value !== 'string' ? JSON.stringify(value) : decodeURIComponent(value as string))) 26 | @IsNotEmpty({ message: 'body不能为空' }) 27 | @IsJSON({ message: 'body需为JSON字符串' }) 28 | body: string; 29 | } 30 | 31 | /** 32 | * h5st 加签接口的报文 33 | */ 34 | export class H5stReqBody { 35 | @Type(() => String) 36 | @IsNotEmpty({ message: '版本不能为空' }) 37 | @IsEnum(H5stVersion, { message: '版本号不正确' }) 38 | version: H5stVersion; 39 | 40 | @Type(() => String) 41 | @ValidateIf((o: H5stReqBody) => o.version && !o.version.startsWith('xcx')) 42 | @IsNotEmpty({ message: '账号pin不能为空' }) 43 | pin: string; 44 | 45 | @Type(() => String) 46 | @ValidateIf((o: H5stReqBody) => o.version && !o.version.startsWith('xcx')) 47 | @IsNotEmpty({ message: '用户ua不能为空' }) 48 | ua: string; 49 | 50 | @ValidateNested() 51 | @Type(() => H5stBusinessBody) 52 | @IsNotEmpty({ message: '请确定body传递正确' }) 53 | body: H5stBusinessBody; 54 | 55 | @Type(() => String) 56 | @Transform(({ value }): string => decodeURIComponent(value as string)) 57 | // @IsNotEmpty({ message: 'h5st不能为空' }) 58 | @ValidateIf((o: H5stReqBody) => !!o.h5st) 59 | @ContainsChar(';', [7, 8], { message: 'h5st非法' }) 60 | h5st: string; 61 | 62 | @Type(() => String) 63 | @ValidateIf((o: H5stReqBody) => !o.h5st) 64 | @IsNotEmpty({ message: 'h5st 和 appId 不能同时为空' }) 65 | appId: string; 66 | 67 | debug: boolean; 68 | } 69 | -------------------------------------------------------------------------------- /src/controller/dto/res.dto.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: res.dto.ts 3 | * Description: 响应自定义格式 4 | * Author: zhx47 5 | */ 6 | 7 | import { H5stSignParamsType, H5stSignResultType } from '../../services/h5st/type'; 8 | import { stringify } from 'qs'; 9 | 10 | /** 11 | * 定义响应中的 code 12 | */ 13 | export enum ResErrorCodes { 14 | SUCCESS = 200, 15 | PARAM_ERROR = -1, 16 | BUSINESS_ERROR = 400, 17 | INTERNAL_SERVER_ERROR = 500, 18 | } 19 | 20 | /** 21 | * 响应中 code 对应的 message,在中间件中进行自动填充 22 | */ 23 | export const ResErrorMessages: Record = { 24 | [ResErrorCodes.SUCCESS]: '成功', 25 | [ResErrorCodes.PARAM_ERROR]: '参数异常', 26 | [ResErrorCodes.BUSINESS_ERROR]: '业务异常', 27 | [ResErrorCodes.INTERNAL_SERVER_ERROR]: '程序出现异常了', 28 | }; 29 | 30 | export class ResBaseDto { 31 | constructor(body: T) { 32 | this.body = body; 33 | this.code = ResErrorCodes.SUCCESS; 34 | this.message = ResErrorMessages[ResErrorCodes.SUCCESS]; 35 | } 36 | 37 | code: ResErrorCodes = ResErrorCodes.SUCCESS; 38 | body?: T; 39 | message?: string; 40 | 41 | static error(code: ResErrorCodes, body?: T, extendMessage?: string): ResBaseDto { 42 | const dto = new ResBaseDto(body); 43 | dto.code = code; 44 | dto.message = extendMessage ? `${ResErrorMessages[code]} - ${extendMessage}` : ResErrorMessages[code]; 45 | return dto; 46 | } 47 | } 48 | 49 | export class H5stRes { 50 | constructor(h5st: H5stSignParamsType & H5stSignResultType, body: H5stSignParamsType & { h5st: string }) { 51 | this.h5st = h5st; 52 | this.body = body; 53 | this.qs = stringify(body); 54 | } 55 | 56 | h5st: H5stSignParamsType & H5stSignResultType; 57 | 58 | body: H5stSignParamsType & { h5st: string }; 59 | 60 | qs: string; 61 | } 62 | -------------------------------------------------------------------------------- /src/controller/h5st.controller.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st.controller.ts 3 | * Description: h5st算法入口 4 | * Author: zhx47 5 | */ 6 | 7 | import { Body, Controller, Get, Post, Query } from '@nestjs/common'; 8 | import { H5stService } from '../services/h5st.service'; 9 | import { H5stReqBody } from './dto/req.dto'; 10 | import { H5stRes, ResBaseDto } from './dto/res.dto'; 11 | 12 | @Controller() 13 | export class H5stController { 14 | constructor(private readonly h5stService: H5stService) {} 15 | 16 | @Post('/h5st') 17 | async getH5st(@Body() reqBody: H5stReqBody): Promise> { 18 | const h5stRes: H5stRes = await this.h5stService.getH5st(reqBody); 19 | return new ResBaseDto(h5stRes); 20 | } 21 | 22 | @Get('/h5st') 23 | async getH5stFromGet(@Query() reqQuery: H5stReqBody): Promise> { 24 | const h5stRes: H5stRes = await this.h5stService.getH5st(reqQuery); 25 | return new ResBaseDto(h5stRes); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/factory/h5st.factory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st.factory.ts 3 | * Description: h5st 算法工厂 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable } from '@nestjs/common'; 8 | import { BaseH5st } from '../services/h5st/baseH5st'; 9 | import { H5st420 } from '../services/h5st/h5st4.2.0'; 10 | import { H5st431 } from '../services/h5st/h5st4.3.1'; 11 | import { H5st433 } from '../services/h5st/h5st4.3.3'; 12 | import { H5st440 } from '../services/h5st/h5st4.4.0'; 13 | import { H5st471 } from '../services/h5st/h5st4.7.1'; 14 | import { H5st472 } from '../services/h5st/h5st4.7.2'; 15 | import { H5st473 } from '../services/h5st/h5st4.7.3'; 16 | import { H5st474 } from '../services/h5st/h5st4.7.4'; 17 | import { H5st481 } from '../services/h5st/h5st4.8.1'; 18 | import { H5st482 } from '../services/h5st/h5st4.8.2'; 19 | import { H5st491 } from '../services/h5st/h5st4.9.1'; 20 | import { Xcx310 } from '../services/h5st/xcx3.1.0'; 21 | import { Xcx420 } from '../services/h5st/xcx4.2.0'; 22 | import { Xcx471 } from '../services/h5st/xcx4.7.1'; 23 | import { Xcx491 } from '../services/h5st/xcx4.9.1'; 24 | import { H5stVersion } from '../services/h5st/type'; 25 | 26 | @Injectable() 27 | export class H5stFactory { 28 | private instances = new Map(); 29 | 30 | constructor( 31 | @Inject(H5st420) private readonly h5st420: H5st420, 32 | @Inject(H5st431) private readonly h5st431: H5st431, 33 | @Inject(H5st433) private readonly h5st433: H5st433, 34 | @Inject(H5st440) private readonly h5st440: H5st440, 35 | @Inject(H5st471) private readonly h5st471: H5st471, 36 | @Inject(H5st472) private readonly h5st472: H5st472, 37 | @Inject(H5st473) private readonly h5st473: H5st473, 38 | @Inject(H5st474) private readonly h5st474: H5st474, 39 | @Inject(H5st481) private readonly h5st481: H5st481, 40 | @Inject(H5st482) private readonly h5st482: H5st482, 41 | @Inject(H5st491) private readonly h5st491: H5st491, 42 | @Inject(Xcx310) private readonly xcx310: Xcx310, 43 | @Inject(Xcx420) private readonly xcx420: Xcx420, 44 | @Inject(Xcx471) private readonly xcx471: Xcx471, 45 | @Inject(Xcx491) private readonly xcx491: Xcx491, 46 | ) { 47 | this.instances.set(H5stVersion['4.2.0'], this.h5st420); 48 | this.instances.set(H5stVersion['4.3.1'], this.h5st431); 49 | this.instances.set(H5stVersion['4.3.3'], this.h5st433); 50 | this.instances.set(H5stVersion['4.4.0'], this.h5st440); 51 | this.instances.set(H5stVersion['4.7.1'], this.h5st471); 52 | this.instances.set(H5stVersion['4.7.2'], this.h5st472); 53 | this.instances.set(H5stVersion['4.7.3'], this.h5st473); 54 | this.instances.set(H5stVersion['4.7.4'], this.h5st474); 55 | this.instances.set(H5stVersion['4.8.1'], this.h5st481); 56 | this.instances.set(H5stVersion['4.8.2'], this.h5st482); 57 | this.instances.set(H5stVersion['4.9.1'], this.h5st491); 58 | this.instances.set(H5stVersion['xcx3.1.0'], this.xcx310); 59 | this.instances.set(H5stVersion['xcx4.2.0'], this.xcx420); 60 | this.instances.set(H5stVersion['xcx4.7.1'], this.xcx471); 61 | this.instances.set(H5stVersion['xcx4.9.1'], this.xcx491); 62 | } 63 | 64 | getInstance(key: H5stVersion): BaseH5st | undefined { 65 | return this.instances.get(key); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/factory/token.factory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: token.factory.ts 3 | * Description: localToken 算法工厂 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable } from '@nestjs/common'; 8 | import { LocalTokenVersion } from '../services/h5st/type'; 9 | import { BaseLocalToken } from '../services/token/baseLocalToken'; 10 | import { LocalTokenV3 } from '../services/token/localTokenV3'; 11 | import { LocalTokenV4 } from '../services/token/localTokenV4'; 12 | 13 | @Injectable() 14 | export class TokenFactory { 15 | private instances = new Map(); 16 | 17 | constructor( 18 | @Inject(LocalTokenV3) private readonly localTokenV3: LocalTokenV3, 19 | @Inject(LocalTokenV4) private readonly localTokenV4: LocalTokenV4, 20 | ) { 21 | this.instances.set(LocalTokenVersion['03'], this.localTokenV3); 22 | this.instances.set(LocalTokenVersion['04'], this.localTokenV4); 23 | } 24 | 25 | getInstance(key: LocalTokenVersion): BaseLocalToken | undefined { 26 | return this.instances.get(key); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/filter/business-exception.filter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: business-exception.filter.ts 3 | * Description: 异常拦截器,拦截 BusinessError 自定义的业务异常 4 | * Author: zhx47 5 | */ 6 | 7 | import { ArgumentsHost, Catch, ExceptionFilter, Logger } from '@nestjs/common'; 8 | import { Response } from 'express'; 9 | import { ResBaseDto, ResErrorCodes } from '../controller/dto/res.dto'; 10 | import { BusinessError } from '../utils/error'; 11 | 12 | @Catch(BusinessError) 13 | export class BusinessExceptionFilter implements ExceptionFilter { 14 | private readonly logger = new Logger(BusinessExceptionFilter.name); 15 | 16 | catch(exception: BusinessError, host: ArgumentsHost) { 17 | const ctx = host.switchToHttp(); 18 | const response = ctx.getResponse(); 19 | this.logger.error('出现异常', exception.stack); 20 | 21 | const resDto = ResBaseDto.error(ResErrorCodes.BUSINESS_ERROR, null, exception.message); 22 | response.status(200).json(resDto); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/filter/validation-exception.filter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: validation-exception.filter.ts 3 | * Description: 异常拦截器,这里主要是将 nestjs 的 ValidationPipe 异常响应与整个项目进行对齐 4 | * Author: zhx47 5 | */ 6 | 7 | import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter, Logger } from '@nestjs/common'; 8 | import { Response } from 'express'; 9 | import { ResBaseDto, ResErrorCodes } from '../controller/dto/res.dto'; 10 | 11 | interface ExceptionResponse { 12 | message: string | string[]; // 根据实际返回结构调整 13 | error?: string; 14 | } 15 | 16 | @Catch(BadRequestException) 17 | export class ValidationExceptionFilter implements ExceptionFilter { 18 | private readonly logger = new Logger(ValidationExceptionFilter.name); 19 | catch(exception: BadRequestException, host: ArgumentsHost) { 20 | const ctx = host.switchToHttp(); 21 | const response = ctx.getResponse(); 22 | const exceptionResponse = exception.getResponse() as ExceptionResponse; 23 | this.logger.error('参数校验出现异常', exceptionResponse.message); 24 | 25 | const resDto = ResBaseDto.error(ResErrorCodes.PARAM_ERROR, exceptionResponse.message); 26 | response.status(200).json(resDto); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: main.ts 3 | * Description: nestjs 程序入口 4 | * Author: zhx47 5 | */ 6 | 7 | import { NestFactory } from '@nestjs/core'; 8 | import { AppModule } from './app.module'; 9 | import { ValidationPipe } from '@nestjs/common'; 10 | import { ValidationExceptionFilter } from './filter/validation-exception.filter'; 11 | import { AppClusterService } from './app_cluster.service'; 12 | import { BusinessExceptionFilter } from './filter/business-exception.filter'; 13 | import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; 14 | 15 | async function bootstrap() { 16 | const app = await NestFactory.create(AppModule, { 17 | bufferLogs: true, 18 | }); 19 | 20 | app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER)); 21 | 22 | // 启用自带的格式校验管道,并且自动进行格式转换 23 | app.useGlobalPipes( 24 | new ValidationPipe({ 25 | transform: true, 26 | }), 27 | ); 28 | 29 | // 添加一个过滤器,调整格式校验失败返回格式 30 | app.useGlobalFilters(new ValidationExceptionFilter(), new BusinessExceptionFilter()); 31 | 32 | // 开启跨域 33 | app.enableCors(); 34 | 35 | await app.listen(3001); 36 | } 37 | 38 | // bootstrap(); 39 | AppClusterService.clusterize(bootstrap); 40 | -------------------------------------------------------------------------------- /src/services/cache/cache.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: cache.config.ts 3 | * Description: 获取redis配置 4 | * Author: zhx47 5 | */ 6 | import { strictBoolean } from '../../utils/baseUtils'; 7 | import { registerAs } from '@nestjs/config'; 8 | import * as process from 'node:process'; 9 | 10 | export default registerAs('cache', () => ({ 11 | memory: strictBoolean(process.env.MEMORY_CACHE), 12 | redis: strictBoolean(process.env.REDIS_CACHE), 13 | options: { 14 | host: process.env.REDIS_HOST, 15 | port: parseInt(process.env.REDIS_PORT) || 6379, 16 | password: encodeURIComponent(process.env.REDIS_PASSWORD), 17 | database: parseInt(process.env.REDIS_DB) || 0, 18 | }, 19 | })); 20 | -------------------------------------------------------------------------------- /src/services/cache/cache.service.ts: -------------------------------------------------------------------------------- 1 | import { NoneCacheProvider } from './none-cache.service'; 2 | import { Injectable, Logger } from '@nestjs/common'; 3 | import { CacheOptions, CacheOptionsFactory } from 'nestjs-cache-manager-v6'; 4 | import KeyvRedis from '@keyv/redis'; 5 | import { ConfigService } from '@nestjs/config'; 6 | 7 | @Injectable() 8 | export class CacheConfigService implements CacheOptionsFactory { 9 | private readonly logger = new Logger(CacheConfigService.name); 10 | 11 | constructor(private configService: ConfigService) {} 12 | 13 | createCacheOptions(): Promise | CacheOptions { 14 | const redisCache = this.configService.get('cache.redis'); 15 | if (redisCache) { 16 | const host = this.configService.get('cache.options.host'); 17 | if (host) { 18 | this.logger.debug('使用 Redis 缓存'); 19 | const port = this.configService.get('cache.options.port'), 20 | password = this.configService.get('cache.options.password'), 21 | database = this.configService.get('cache.options.database'); 22 | const authInfo = password ? `:${password}@` : ''; 23 | return { 24 | stores: new KeyvRedis(`redis://${authInfo}${host}:${port}/${database}`), 25 | namespace: 'jd_server', 26 | }; 27 | } 28 | } 29 | const memoryCache = this.configService.get('cache.memory'); 30 | if (memoryCache) { 31 | this.logger.debug('使用内存缓存'); 32 | return {}; 33 | } 34 | this.logger.debug('不使用缓存'); 35 | return { 36 | stores: new NoneCacheProvider(), 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/services/cache/none-cache.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: none-cache.service.ts 3 | * Description: 空缓存模拟 4 | * Author: zhx47 5 | */ 6 | 7 | import { Injectable } from '@nestjs/common'; 8 | import { KeyvStoreAdapter } from 'keyv'; 9 | 10 | @Injectable() 11 | export class NoneCacheProvider implements KeyvStoreAdapter { 12 | opts: any; 13 | 14 | clear() { 15 | return null; 16 | } 17 | 18 | delete(_key: string) { 19 | return null; 20 | } 21 | 22 | get(_key: string) { 23 | return null; 24 | } 25 | 26 | on(_event: string, _listener: (...arguments_: any[]) => void) { 27 | return null; 28 | } 29 | 30 | set(_key: string, _value: any, _ttl?: number) { 31 | return null; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/services/h5st.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st.service.ts 3 | * Description: h5st 算法业务层,用于具体算法初始化和生成 4 | * Author: zhx47 5 | */ 6 | 7 | import { Injectable } from '@nestjs/common'; 8 | import { ClsService } from 'nestjs-cls'; 9 | import { H5stReqBody } from '../controller/dto/req.dto'; 10 | import { H5stFactory } from '../factory/h5st.factory'; 11 | import { H5stRes } from '../controller/dto/res.dto'; 12 | 13 | @Injectable() 14 | export class H5stService { 15 | constructor( 16 | private readonly cls: ClsService, 17 | private readonly h5stFactory: H5stFactory, 18 | ) {} 19 | 20 | async getH5st(reqBody: H5stReqBody): Promise { 21 | this.cls.set('h5stContext.userAgent', reqBody.ua); 22 | this.cls.set('h5stContext.pt_pin', reqBody.pin); 23 | this.cls.set('h5stContext.subVersion', reqBody.version); 24 | 25 | const instance = this.h5stFactory.getInstance(reqBody.version); 26 | 27 | const h5st = reqBody.h5st; 28 | if (h5st) { 29 | const h5stVar = h5st.split(';'); 30 | 31 | instance.init({ 32 | appId: h5stVar[2], 33 | debug: reqBody.debug, 34 | }); 35 | instance.envDecrypt(h5stVar[7]); 36 | } else { 37 | instance.init({ 38 | appId: reqBody.appId, 39 | debug: reqBody.debug, 40 | }); 41 | } 42 | 43 | const body = reqBody.body; 44 | 45 | const signResult = await instance.sign(body); 46 | return new H5stRes(signResult, Object.assign(body, { h5st: signResult.h5st })); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/services/h5st/baseH5st.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: baseH5st.ts 3 | * Description: h5st 加签算法通用父类 4 | * Author: zhx47 5 | */ 6 | 7 | import * as CryptoJS from 'crypto-js'; 8 | import { H5stInitConfig } from './h5stAlgoConfig'; 9 | import { ClsService } from 'nestjs-cls'; 10 | import { 11 | containsReservedParamName, 12 | decodeBase64URL, 13 | filterCharactersFromString, 14 | formatDate, 15 | getRandomIDPro, 16 | getRandomInt10, 17 | isEmpty, 18 | isPlainObject, 19 | isSafeParamValue, 20 | selectRandomElements, 21 | } from '../../utils/baseUtils'; 22 | import { EnvCollectType, ErrCodes, H5stAlgoConfigType, H5stSignParamsType, H5stSignResultType, KVType } from './type'; 23 | import { CANVAS_FP, STORAGE_FP_KEY, STORAGE_TOKEN_KEY, WEBGL_FP } from './constant'; 24 | import { BusinessError } from '../../utils/error'; 25 | import { Cache } from 'nestjs-cache-manager-v6'; 26 | import { Logger } from '@nestjs/common'; 27 | import { BaseLocalToken } from '../token/baseLocalToken'; 28 | import { CustomAlgorithm } from './customAlgorithm'; 29 | 30 | export class BaseH5st { 31 | protected readonly logger = new Logger(BaseH5st.name); 32 | 33 | constructor( 34 | protected readonly clsService: ClsService, 35 | protected readonly cacheManager: Cache, 36 | protected readonly localToken: BaseLocalToken, 37 | protected readonly algos: CustomAlgorithm, 38 | ) {} 39 | 40 | init(h5stInitConfig: H5stInitConfig, h5stAlgoConfig?: H5stAlgoConfigType) { 41 | const defaultAlgorithm = { 42 | local_key_1: (message: CryptoJS.lib.WordArray | string) => this.algos.MD5(message), 43 | local_key_2: (message: CryptoJS.lib.WordArray | string) => this.algos.SHA256(message), 44 | local_key_3: (message: CryptoJS.lib.WordArray | string, key: CryptoJS.lib.WordArray | string) => this.algos.HmacSHA256(message, key), 45 | }; 46 | 47 | this.clsService.set('h5stConfig', h5stAlgoConfig); 48 | this.clsService.set('h5stContext._defaultAlgorithm', defaultAlgorithm); 49 | this.clsService.set('h5stContext._version', h5stAlgoConfig.version); 50 | 51 | this.__iniConfig(Object.assign({}, new H5stInitConfig(), h5stInitConfig)); 52 | } 53 | 54 | /** 55 | * 打印调试日志(需要将settings.debug设置成true) 56 | * @param {string} message 调试日志 57 | */ 58 | _log(message: string): void { 59 | if (this.clsService.get('h5stContext._debug')) { 60 | this.logger.log(message); 61 | } 62 | } 63 | 64 | /** 65 | * 缓存信息 66 | * @param {string} key key 67 | * @param {string} value value 68 | * @param {number} expire 过期时间 69 | */ 70 | async setSync(key: string, value: string, expire: number): Promise { 71 | const { pt_pin } = this.clsService.get('h5stContext'); 72 | if (pt_pin) { 73 | await this.cacheManager.set(`${pt_pin}_${key}`, value, expire); 74 | } 75 | } 76 | 77 | /** 78 | * 获取缓存信息 79 | * @param {string} key key 80 | * @returns {string} value 81 | */ 82 | async getSync(key: string): Promise { 83 | const { pt_pin } = this.clsService.get('h5stContext'); 84 | if (pt_pin) { 85 | return await this.cacheManager.get(`${pt_pin}_${key}`); 86 | } 87 | return null; 88 | } 89 | 90 | /** 91 | * 初始化 92 | * @param {H5stInitConfig} config 93 | */ 94 | __iniConfig(config: H5stInitConfig): void { 95 | if ('string' !== typeof config.appId || !config.appId) { 96 | this.logger.error('settings.appId must be a non-empty string', this.constructor.name); 97 | } 98 | const appId = config.appId || ''; 99 | if (appId) { 100 | const subVersion = this.clsService.get('h5stContext.subVersion'); 101 | this.clsService.set('h5stContext._storageFpKey', `${STORAGE_FP_KEY}_${appId}_${subVersion}`); 102 | this.clsService.set('h5stContext._storageTokenKey', `${STORAGE_TOKEN_KEY}_${appId}_${subVersion}`); 103 | } 104 | 105 | this.clsService.set('h5stContext._debug', Boolean(config.debug)); 106 | this.clsService.set('h5stContext._appId', appId); 107 | 108 | this._log(`create instance with appId=${appId}`); 109 | } 110 | 111 | /** 112 | * 生成默认的签名key,在无algo信息或者lite算法才会调用 113 | * @param {string} token token 114 | * @param {string} fingerprint fp指纹 115 | * @param {string} time yyyyMMddhhmmssSSS 时间 + 随机字符串 116 | * @param {string} appId 应用ID 117 | * @returns {string} 签名key 118 | */ 119 | __genDefaultKey(token: string, fingerprint: string, time: string, appId: string): string { 120 | const defaultAlgorithm = this.clsService.get('h5stContext._defaultAlgorithm'); 121 | const input = `${token}${fingerprint}${time}${appId}${this.clsService.get('h5stConfig.defaultKey.extend')}`, 122 | express = this.algos.enc.Utf8.stringify(this.algos.enc.Base64.parse(decodeBase64URL(this.__parseToken(token, 16, 28)))), 123 | expressMatch = /^[123]([x+][123])+/.exec(express); 124 | let key = ''; 125 | if (expressMatch) { 126 | const expressHit = expressMatch[0].split(''); 127 | let keyRouter = ''; 128 | expressHit.forEach((router: string) => { 129 | if (isNaN(Number(router))) { 130 | if (['+', 'x'].includes(router)) keyRouter = router; 131 | } else { 132 | const algoKey = `local_key_${router}`; 133 | if (defaultAlgorithm[algoKey as keyof typeof defaultAlgorithm]) { 134 | switch (keyRouter) { 135 | case '+': 136 | key = `${key}${this.__algorithm(algoKey, input, token)}`; 137 | break; 138 | case 'x': 139 | key = this.__algorithm(algoKey, key, token); 140 | break; 141 | default: 142 | key = this.__algorithm(algoKey, input, token); 143 | } 144 | } 145 | } 146 | }); 147 | } 148 | this._log(`__genDefaultKey input=${input},express=${express},key=${key}`); 149 | return key; 150 | } 151 | 152 | /** 153 | * 配合__genDefaultKey的通用加密方法 154 | * @param {string} algoKey 默认加密方法key 155 | * @param {string} input 加密信息1 156 | * @param {string} token 加密信息2 157 | * @returns {string} 密文 158 | */ 159 | __algorithm(algoKey: string, input: string, token: string): string { 160 | const defaultAlgorithm = this.clsService.get('h5stContext._defaultAlgorithm'); 161 | 162 | if (algoKey === 'local_key_3') { 163 | return defaultAlgorithm[algoKey](input, token).toString(this.algos.enc.Hex); 164 | } else if (algoKey === 'local_key_1' || algoKey === 'local_key_2') { 165 | return defaultAlgorithm[algoKey](input).toString(this.algos.enc.Hex); 166 | } else { 167 | throw new Error('Unsupported algorithm key'); 168 | } 169 | } 170 | 171 | /** 172 | * slice切分token 173 | * @param {string} token 174 | * @param {number} begin 175 | * @param {number} end 176 | * @returns {string} 177 | */ 178 | __parseToken(token: string, begin: number, end: number): string { 179 | return token ? token.slice(begin, end) : ''; 180 | } 181 | 182 | /** 183 | * 组装H5ST签名 184 | * @param {string} bodySign 185 | * @param {number} timestamp 186 | * @param {string} timeStr yyyyMMddhhmmssSSS日期 187 | * @param {string} envSign 188 | * @param signStrDefault 189 | * @returns {string} 190 | */ 191 | __genSignParams(bodySign: string, timestamp: number, timeStr: string, envSign: string, signStrDefault?: string): string { 192 | const { _fingerprint, _appId, _isNormal, _token, _defaultToken, _version } = this.clsService.get('h5stContext'); 193 | signStrDefault = signStrDefault ? `;${signStrDefault}` : ''; 194 | return `${timeStr};${_fingerprint};${_appId};${_isNormal ? _token : _defaultToken};${bodySign};${_version};${timestamp};${envSign}${signStrDefault}`; 195 | } 196 | 197 | /** 198 | * 生成sign (每个版本不一致,需要自定义实现) 199 | * @param {string} _key __genDefaultKey或者__genKey结果 200 | * @param {object} body 请求体 201 | * @returns {string} 202 | */ 203 | __genSign(_key: string, body: KVType[]): string { 204 | return body 205 | .map((item: KVType) => { 206 | return item.key + ':' + item.value; 207 | }) 208 | .join('&'); 209 | } 210 | 211 | /** 212 | * 4.7.4新增 213 | * @param _key 214 | * @param body 215 | */ 216 | __genSignDefault(_key: string, body: KVType[]): string { 217 | // 使用body生成一个新的KVType数组,只保留key是functionId和appid的 218 | const newBody = body.filter((item) => item.key === 'functionId' || item.key === 'appid'); 219 | return newBody 220 | .map((item: KVType) => { 221 | return item.key + ':' + item.value; 222 | }) 223 | .join('&'); 224 | } 225 | 226 | /** 227 | * 获取依赖信息(LITE仅获取fp, 非LITE算法需要请求algo接口获取tk和__genKey算法) 228 | * @returns {void} 229 | */ 230 | async __requestDeps(): Promise { 231 | this._log('__requestDeps start.'); 232 | let fingerprint = await this.getSync(this.clsService.get('h5stContext._storageFpKey')); 233 | if (fingerprint) { 234 | this._log(`__requestDeps use cache fp, fp:${fingerprint}`); 235 | } else { 236 | fingerprint = this.generateVisitKey(); 237 | await this.setSync(this.clsService.get('h5stContext._storageFpKey'), fingerprint, 3600 * 24 * 365 * 1000); 238 | this._log(`__requestDeps use new fp, fp:${fingerprint}`); 239 | } 240 | this.clsService.set('h5stContext._fingerprint', fingerprint); 241 | this._log('__requestDeps end.'); 242 | } 243 | 244 | /** 245 | * 检测参数 246 | * @param {object} body 247 | * @returns {object|null} 需要参与签名的参数 248 | */ 249 | __checkParams(body: H5stSignParamsType): KVType[] | null { 250 | const appId = this.clsService.get('h5stContext._appId'); 251 | let errorInfo = null; 252 | if (!appId) { 253 | errorInfo = { 254 | code: ErrCodes.APPID_ABSENT, 255 | message: 'appId is required', 256 | }; 257 | } 258 | if (!isPlainObject(body)) { 259 | errorInfo = { 260 | code: ErrCodes.UNSIGNABLE_PARAMS, 261 | message: 'params is not a plain object', 262 | }; 263 | } 264 | if (isEmpty(body)) { 265 | errorInfo = { 266 | code: ErrCodes.UNSIGNABLE_PARAMS, 267 | message: 'params is empty', 268 | }; 269 | } 270 | if (containsReservedParamName(body)) { 271 | errorInfo = { 272 | code: ErrCodes.UNSIGNABLE_PARAMS, 273 | message: 'params contains reserved param name.', 274 | }; 275 | } 276 | if (errorInfo) { 277 | // this._onSign(errorInfo); 278 | return null; 279 | } 280 | const checkParams = Object.keys(body) 281 | .sort() 282 | .map(function (key: string): KVType { 283 | return { 284 | key: key, 285 | value: body[key as keyof H5stSignParamsType], 286 | }; 287 | }) 288 | .filter(function (obj: KVType) { 289 | return isSafeParamValue(obj.value); 290 | }); 291 | 292 | if (checkParams.length === 0) { 293 | return null; 294 | } 295 | return checkParams; 296 | } 297 | 298 | /** 299 | * h5st 加签 300 | * @param params 需要参与加签的请求内容,已格式化为 key - value 格式 301 | * @param envSign env 加密字符串 302 | */ 303 | __makeSign(params: KVType[], envSign: string): H5stSignResultType { 304 | const appId = this.clsService.get('h5stContext._appId'), 305 | fingerprint = this.clsService.get('h5stContext._fingerprint'), 306 | extendDateStr = this.clsService.get('h5stConfig.makeSign.extendDateStr'); 307 | 308 | const now = Date.now(), 309 | dateStr = formatDate(now, 'yyyyMMddhhmmssSSS'), 310 | dateStrExtend = dateStr + extendDateStr; 311 | 312 | const defaultToken = this.localToken.genLocalTK(fingerprint); 313 | // const defaultToken = 'tk04w74a88d9841lMXgzUXg3OXV0pjLfGmYdA_le26FSGqodD6VNrrbrZFvBGj_fAGVfVeGTjunS8mUTJeYfzLITJeYd'; 314 | 315 | const key = this.__genDefaultKey(defaultToken, fingerprint, dateStrExtend, appId); 316 | this.clsService.set('h5stContext._defaultToken', defaultToken); 317 | 318 | if (!key) { 319 | return {}; 320 | } 321 | const signStr = this.__genSign(key, params); 322 | const stk = params 323 | .map((item: KVType) => { 324 | return item.key; 325 | }) 326 | .join(','); 327 | 328 | // 4.7.4 新增 329 | let signStrDefault = ''; 330 | if (this.clsService.get('h5stConfig.genSignDefault')) { 331 | signStrDefault = this.__genSignDefault(key, params); 332 | } 333 | 334 | const h5st = this.__genSignParams(signStr, now, dateStr, envSign, signStrDefault); 335 | 336 | this._log( 337 | '__makeSign, result:' + 338 | JSON.stringify( 339 | { 340 | key: key, 341 | signStr: signStr, 342 | _stk: stk, 343 | _ste: 1, 344 | h5st: h5st, 345 | }, 346 | null, 347 | 2, 348 | ), 349 | ); 350 | return { 351 | _stk: stk, 352 | _ste: 1, 353 | h5st: h5st, 354 | }; 355 | } 356 | 357 | /** 358 | * 收集环境信息,env 加密 359 | */ 360 | async __collect() { 361 | const fingerprint = this.clsService.get('h5stContext._fingerprint'); 362 | 363 | const env = await this.envCollect(); 364 | env.fp = fingerprint; 365 | const envStr = JSON.stringify(env, null, 2); 366 | this._log(`__collect envCollect=${envStr}`); 367 | return this.envSign(envStr); 368 | } 369 | 370 | /** 371 | * h5st 加签入口 372 | */ 373 | async sign(params: H5stSignParamsType): Promise { 374 | try { 375 | const start = Date.now(); 376 | 377 | const keys = ['functionId', 'appid', 'client', 'body', 'clientVersion', 'sign', 't', 'jsonp']; 378 | const filterParams: H5stSignParamsType = { appid: '', body: '', functionId: '' }; 379 | keys.forEach((key) => { 380 | let value = params[key as keyof H5stSignParamsType]; 381 | if (value != undefined) { 382 | if (key === 'body') { 383 | value = CryptoJS.SHA256(value).toString(); 384 | } 385 | filterParams[key as keyof H5stSignParamsType] = value; 386 | } 387 | }); 388 | 389 | const checkParams = this.__checkParams(filterParams); 390 | if (checkParams == null) { 391 | return filterParams; 392 | } 393 | await this.__requestDeps(); 394 | const envSign = await this.__collect(); 395 | const makeSign = this.__makeSign(checkParams, envSign); 396 | this._log(`sign elapsed time!${Date.now() - start}ms`); 397 | return Object.assign({}, filterParams, makeSign); 398 | } catch (error) { 399 | this._log(`unknown error! ${(error as Error).message}`); 400 | return params; 401 | } 402 | } 403 | 404 | /** 405 | * 收集环境信息 406 | * @returns {EnvCollectType} 407 | */ 408 | async envCollect(): Promise { 409 | const envExtend = this.clsService.get('h5stContext.envExtend'), 410 | randomLength = this.clsService.get('h5stConfig.env.randomLength'); 411 | 412 | return this.coverEnv(envExtend, envExtend?.random?.length ?? randomLength); 413 | } 414 | 415 | /** 416 | * 生成fp 417 | * @returns {string} 418 | */ 419 | generateVisitKey(): string { 420 | const { seed, selectLength, randomLength } = this.clsService.get('h5stConfig.visitKey'); 421 | 422 | const selectedChars = selectRandomElements(seed, selectLength); 423 | const random = getRandomInt10(); 424 | const filteredChars = filterCharactersFromString(seed, selectedChars); 425 | const combinedString = 426 | getRandomIDPro({ 427 | size: random, 428 | customDict: filteredChars, 429 | }) + 430 | selectedChars + 431 | getRandomIDPro({ 432 | size: randomLength - random, 433 | customDict: filteredChars, 434 | }) + 435 | random; 436 | return this.convertVisitKey(combinedString); 437 | } 438 | 439 | convertVisitKey(combinedString: string): string { 440 | const { convertLength } = this.clsService.get('h5stConfig.visitKey'); 441 | 442 | const charArray = combinedString.split(''); 443 | const firstPartArray = charArray.slice(0, convertLength); 444 | const secondPartArray = charArray.slice(convertLength); 445 | let finalArray = []; 446 | for (; firstPartArray.length > 0; ) finalArray.push((35 - parseInt(firstPartArray.pop(), 36)).toString(36)); 447 | finalArray = finalArray.concat(secondPartArray); 448 | return finalArray.join(''); 449 | } 450 | 451 | /** 452 | * env 加密 453 | */ 454 | envSign(message: string): string { 455 | // 4.8.1开始不在使用AES算法,借助 Hex 魔改参数定位 456 | if (this.clsService.get('h5stConfig.customAlgorithm')?.convertIndex?.hex) { 457 | return this.algos.enc.Base64.encode(this.algos.enc.Utf8.parse(message)); 458 | } 459 | 460 | const secret = this.clsService.get('h5stConfig.env.secret'); 461 | const temp = this.algos.AES.encrypt(message, secret, { 462 | iv: this.algos.enc.Utf8.parse('0102030405060708'), 463 | }); 464 | 465 | // 这里从 4.7 开始会将 AES 加密结果通过自定义的 Base64.encode 编码 466 | if (this.clsService.get('h5stConfig.customAlgorithm')?.map) { 467 | return this.algos.enc.Base64.encode(temp.ciphertext); 468 | } 469 | 470 | return temp.ciphertext.toString(); 471 | } 472 | 473 | /** 474 | * Env 解密,并且存入当前上下文,用于后续生成新的 Env 475 | * @param envSign Env密文 476 | */ 477 | envDecrypt(envSign: string) { 478 | try { 479 | let envDecrypt: string; 480 | // 4.8.1开始不在使用AES算法,借助 Hex 魔改参数定位 481 | if (this.clsService.get('h5stConfig.customAlgorithm')?.convertIndex?.hex) { 482 | const wordArray = this.algos.enc.Base64.decode(envSign); 483 | envDecrypt = this.algos.enc.Utf8.stringify(wordArray); 484 | } else { 485 | const secret = this.clsService.get('h5stConfig.env.secret'); 486 | const decrypt = this.algos.AES.decrypt( 487 | this.algos.lib.CipherParams.create({ 488 | ciphertext: this.clsService.get('h5stConfig.customAlgorithm')?.map ? this.algos.enc.Base64.decode(envSign) : this.algos.enc.Hex.parse(envSign), 489 | }), 490 | secret, 491 | { iv: this.algos.enc.Utf8.parse('0102030405060708') }, 492 | ); 493 | envDecrypt = decrypt.toString(this.algos.enc.Utf8); 494 | const salt = this.clsService.get('h5stConfig.customAlgorithm')?.salt; 495 | if (salt && envDecrypt.endsWith(salt)) { 496 | envDecrypt = envDecrypt.slice(0, -salt.length); 497 | } 498 | } 499 | this.clsService.set('h5stContext.envExtend', JSON.parse(envDecrypt)); 500 | } catch (error) { 501 | throw new BusinessError(`h5st解析失败,请确定提供的h5st与version匹配!${(error as Error).message}`); 502 | } 503 | } 504 | 505 | /** 506 | * 将接口提供的 Env 对象进行部分变量的针对应覆盖,生成一个新的 Env 对象 507 | * @param envExtend 接口提供的官方生成的 Env 解密对象 508 | * @param randomLength extend.random 的长度 509 | * @returns 新的 Env 对象 510 | */ 511 | private async coverEnv(envExtend: EnvCollectType, randomLength: number): Promise { 512 | const { pt_pin, userAgent } = this.clsService.get('h5stContext'); 513 | 514 | const canvas = await this.getCanvasFp(), 515 | webglFp = await this.getWebgFp(); 516 | 517 | const updateEnv: EnvCollectType = { 518 | pp: (() => { 519 | const ptPin = pt_pin; 520 | if (ptPin) { 521 | return { 522 | p1: ptPin, 523 | }; 524 | } 525 | return {}; 526 | })(), 527 | random: getRandomIDPro({ size: randomLength, dictType: 'max' }), 528 | sua: (() => { 529 | const regex = new RegExp('Mozilla/5.0 \\((.*?)\\)'); 530 | const matches = regex.exec(userAgent); 531 | return matches?.[1] ?? ''; 532 | })(), 533 | canvas: canvas, 534 | canvas1: canvas, 535 | webglFp: webglFp, 536 | webglFp1: webglFp, 537 | }; 538 | 539 | if (envExtend) { 540 | Object.keys(envExtend).forEach((key: keyof EnvCollectType) => { 541 | if (updateEnv[key] !== undefined) { 542 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 543 | // @ts-expect-error 544 | envExtend[key] = updateEnv[key]; 545 | } 546 | }); 547 | } else { 548 | envExtend = { 549 | ...updateEnv, 550 | extend: { 551 | wd: 0, 552 | l: 0, 553 | ls: 5, 554 | wk: 0, 555 | bu1: '0.1.6', 556 | bu2: -1, 557 | bu3: 36, 558 | bu4: 0, 559 | bu5: 0, 560 | bu6: 33, 561 | bu7: '', 562 | bu8: 0, 563 | }, 564 | v: this.clsService.get('h5stConfig.env.fv'), 565 | fp: this.clsService.get('h5stContext._fingerprint'), 566 | }; 567 | } 568 | return envExtend; 569 | } 570 | 571 | /** 572 | * 随机生成一个 canvas 指纹 573 | * @returns canvas 指纹 574 | */ 575 | private async getCanvasFp(): Promise { 576 | let canvasFp = await this.getSync(CANVAS_FP); 577 | if (!canvasFp) { 578 | canvasFp = this.algos.MD5(`${getRandomInt10()}`).toString(this.algos.enc.Hex); 579 | await this.setSync(CANVAS_FP, canvasFp, 3600 * 24 * 365 * 1000); 580 | } 581 | return canvasFp; 582 | } 583 | 584 | /** 585 | * 随机生成一个 webgl 指纹 586 | * @returns webgl 指纹 587 | */ 588 | private async getWebgFp(): Promise { 589 | let webglFp = await this.getSync(WEBGL_FP); 590 | if (!webglFp) { 591 | webglFp = this.algos.MD5(`${getRandomInt10()}`).toString(this.algos.enc.Hex); 592 | await this.setSync(WEBGL_FP, webglFp, 3600 * 24 * 365 * 1000); 593 | } 594 | return webglFp; 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /src/services/h5st/constant.ts: -------------------------------------------------------------------------------- 1 | export const STORAGE_TOKEN_KEY = 'WQ_dy_tk_s'; 2 | export const STORAGE_FP_KEY = 'WQ_vk1'; 3 | export const CANVAS_FP = 'WQ_gather_cv1'; 4 | export const WEBGL_FP = 'WQ_gather_wgl1'; 5 | -------------------------------------------------------------------------------- /src/services/h5st/customAlgorithm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: customAlgorithm.ts 3 | * Description: 京东加强算法 4 | * Author: zhx47 5 | */ 6 | 7 | import * as CryptoJS from 'crypto-js'; 8 | import { Injectable } from '@nestjs/common'; 9 | import { ClsService } from 'nestjs-cls'; 10 | 11 | interface Format { 12 | stringify(cipherParams: CryptoJS.lib.CipherParams): string; 13 | 14 | parse(str: string): CryptoJS.lib.CipherParams; 15 | } 16 | 17 | interface CipherOption { 18 | iv?: CryptoJS.lib.WordArray | undefined; 19 | format?: Format | undefined; 20 | 21 | [key: string]: any; 22 | } 23 | 24 | @Injectable() 25 | export class CustomAlgorithm { 26 | constructor(protected readonly clsService: ClsService) {} 27 | 28 | AES = { 29 | encrypt: (message: CryptoJS.lib.WordArray | string, key: CryptoJS.lib.WordArray | string, cfg?: CipherOption): CryptoJS.lib.CipherParams => { 30 | if (typeof key === 'string') { 31 | if (this.clsService.get('h5stConfig.customAlgorithm')?.keyReverse) { 32 | key = key.split('').reverse().join(''); 33 | } 34 | key = this.enc.Utf8.parse(key); 35 | } 36 | return CryptoJS.AES.encrypt(this.addSalt(message), key, cfg); 37 | }, 38 | decrypt: (ciphertext: CryptoJS.lib.CipherParams | string, key: CryptoJS.lib.WordArray | string, cfg?: CipherOption): CryptoJS.lib.WordArray => { 39 | if (typeof key === 'string') { 40 | if (this.clsService.get('h5stConfig.customAlgorithm')?.keyReverse) { 41 | key = key.split('').reverse().join(''); 42 | } 43 | key = this.enc.Utf8.parse(key); 44 | } 45 | return CryptoJS.AES.decrypt(ciphertext, key, cfg); 46 | }, 47 | }; 48 | 49 | enc = { 50 | ...CryptoJS.enc, 51 | Utils: { 52 | toWordArray: (array: Uint8Array | number[]): CryptoJS.lib.WordArray => { 53 | const words = []; 54 | for (let i = 0; i < array.length; i++) { 55 | words[i >>> 2] |= array[i] << (24 - (i % 4) * 8); 56 | } 57 | return CryptoJS.lib.WordArray.create(words, array.length); 58 | }, 59 | fromWordArray: (wordArray: CryptoJS.lib.WordArray): Uint8Array => { 60 | const u8array = new Uint8Array(wordArray.sigBytes); 61 | for (let i = 0; i < wordArray.sigBytes; i++) { 62 | u8array[i] = (wordArray.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 63 | } 64 | return u8array; 65 | }, 66 | }, 67 | Hex: { 68 | ...CryptoJS.enc.Hex, 69 | stringify: (wordArray: CryptoJS.lib.WordArray): string => { 70 | const convertIndex = this.clsService.get('h5stConfig.customAlgorithm')?.convertIndex?.hex; 71 | 72 | if (!convertIndex) { 73 | return CryptoJS.enc.Hex.stringify(wordArray); 74 | } 75 | 76 | const array = Array.from(this.enc.Utils.fromWordArray(wordArray)); 77 | if (convertIndex > array.length) { 78 | wordArray = this.enc.Utils.toWordArray(array.reverse()); 79 | } else { 80 | const reversedPart = array.slice(0, convertIndex).reverse(); 81 | const remainingPart = array.slice(convertIndex); 82 | 83 | wordArray = this.enc.Utils.toWordArray(reversedPart.concat(remainingPart)); 84 | } 85 | 86 | return CryptoJS.enc.Hex.stringify(wordArray); 87 | }, 88 | }, 89 | Base64: { 90 | ...CryptoJS.enc.Base64, 91 | encode: (wordArray: CryptoJS.lib.WordArray): string => { 92 | const map = this.clsService.get('h5stConfig.customAlgorithm')?.map ?? ''; 93 | if (!map) { 94 | throw new Error('该版本算法未配置相关魔改参数'); 95 | } 96 | const typedArray = this.enc.Utils.fromWordArray(wordArray); 97 | const normalArray = Array.from(typedArray); 98 | const number = normalArray.length % 3; 99 | for (let j = 0; j < 3 - number; j++) { 100 | normalArray.push(3 - number); 101 | } 102 | let sigBytes = normalArray.length; 103 | let words: number[] = []; 104 | for (let j = sigBytes; j > 0; j -= 3) { 105 | words.push(...normalArray.slice(Math.max(j - 3, 0), j)); 106 | } 107 | wordArray = this.enc.Utils.toWordArray(words); 108 | words = wordArray.words; 109 | sigBytes = wordArray.sigBytes; 110 | wordArray.clamp(); 111 | const base64Chars: string[] = []; 112 | for (let i = 0; i < sigBytes; i += 3) { 113 | const byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 114 | const byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; 115 | const byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; 116 | 117 | const triplet = (byte1 << 16) | (byte2 << 8) | byte3; 118 | 119 | for (let j = 0; j < 4 && i + j * 0.75 < sigBytes; j++) { 120 | base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); 121 | } 122 | } 123 | const result: string[] = []; 124 | // 每四个拆成一个小组,进行反序,然后拼接 125 | for (let i = 0; i < base64Chars.length; i += 4) { 126 | result.push(...base64Chars.slice(i, i + 4).reverse()); 127 | } 128 | return result.join(''); 129 | }, 130 | decode: (base64Str: string): CryptoJS.lib.WordArray => { 131 | const map = this.clsService.get('h5stConfig.customAlgorithm')?.map ?? ''; 132 | if (!map) { 133 | throw new Error('该版本算法未配置相关魔改参数'); 134 | } 135 | 136 | const base64Chars = base64Str.split(''); 137 | const result = []; 138 | // 每四个拆成一个小组,进行反序,然后拼接 139 | for (let i = 0; i < base64Chars.length; i += 4) { 140 | result.push(...base64Chars.slice(i, i + 4).reverse()); 141 | } 142 | base64Str = result.join(''); 143 | 144 | let base64StrLength = base64Str.length; 145 | const reverseMap: number[] = []; 146 | for (let j = 0; j < map.length; j++) { 147 | reverseMap[map.charCodeAt(j)] = j; 148 | } 149 | 150 | const paddingChar = map.charAt(64); 151 | if (paddingChar) { 152 | const paddingIndex = base64Str.indexOf(paddingChar); 153 | if (paddingIndex !== -1) { 154 | base64StrLength = paddingIndex; 155 | } 156 | } 157 | const wordArray = this.parseLoop(base64Str, base64StrLength, reverseMap); 158 | 159 | const normalArray = Array.from(this.enc.Utils.fromWordArray(wordArray)); 160 | const sigBytes = normalArray.length; 161 | const words: number[] = []; 162 | for (let j = sigBytes; j > 0; j -= 3) { 163 | words.push(...normalArray.slice(Math.max(j - 3, 0), j)); 164 | } 165 | 166 | const lastElement = words[words.length - 1]; // 获取数组的最后一个元素, 这是我们预期要移除的元素数量 167 | // 检查数组末尾是否有count个相同的元素,并且这些元素的值都等于count 168 | let isValid = true; 169 | for (let i = 0; i < lastElement; i++) { 170 | if (words[words.length - 1 - i] !== lastElement) { 171 | isValid = false; 172 | break; 173 | } 174 | } 175 | // 如果末尾的元素满足条件,移除它们 176 | if (isValid) { 177 | words.splice(-lastElement, lastElement); // 移除数组末尾的count个元素 178 | } 179 | return this.enc.Utils.toWordArray(words); 180 | }, 181 | }, 182 | }; 183 | 184 | lib = CryptoJS.lib; 185 | 186 | MD5(message: CryptoJS.lib.WordArray | string): CryptoJS.lib.WordArray { 187 | return CryptoJS.MD5(this.addSalt(message)); 188 | } 189 | 190 | SHA256(message: CryptoJS.lib.WordArray | string): CryptoJS.lib.WordArray { 191 | return CryptoJS.SHA256(this.addSalt(message)); 192 | } 193 | 194 | HmacSHA256(message: CryptoJS.lib.WordArray | string, key: CryptoJS.lib.WordArray | string): CryptoJS.lib.WordArray { 195 | return CryptoJS.HmacSHA256(this.addSalt(message), this.eKey(key)); 196 | } 197 | 198 | addSalt(message: CryptoJS.lib.WordArray | string): CryptoJS.lib.WordArray | string { 199 | if (typeof message === 'string') { 200 | const salt = this.clsService.get('h5stConfig.customAlgorithm')?.salt ?? ''; 201 | return message + salt; 202 | } 203 | return message; 204 | } 205 | 206 | eKey(key: CryptoJS.lib.WordArray | string): CryptoJS.lib.WordArray | string { 207 | if (typeof key === 'string') { 208 | const convertIndex = this.clsService.get('h5stConfig.customAlgorithm')?.convertIndex?.hmac; 209 | 210 | if (!convertIndex) { 211 | return key; 212 | } 213 | 214 | const split = key.split(''); 215 | const slice1 = split.slice(0, convertIndex); 216 | const slice2 = split.slice(convertIndex); 217 | const array = []; 218 | 219 | for (let i = Math.min(convertIndex, key.length); i > 0; i--) { 220 | const pop = slice1.pop(); 221 | const number = pop.charCodeAt(0); 222 | const s = String.fromCharCode(158 - number); 223 | array.push(s); 224 | } 225 | 226 | return array.concat(slice2).join(''); 227 | } 228 | return key; 229 | } 230 | 231 | parseLoop(base64Str: string, base64StrLength: number, reverseMap: number[]) { 232 | const words = []; 233 | let nBytes = 0; 234 | for (let i = 0; i < base64StrLength; i++) { 235 | if (i % 4) { 236 | const bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); 237 | const bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); 238 | const bitsCombined = bits1 | bits2; 239 | words[nBytes >>> 2] |= bitsCombined << (24 - (nBytes % 4) * 8); 240 | nBytes++; 241 | } 242 | } 243 | return CryptoJS.lib.WordArray.create(words, nBytes); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.2.0.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.2.0.ts 3 | * Description: h5st4.2.0 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st420 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st420.name); 19 | 20 | constructor( 21 | protected readonly clsService: ClsService, 22 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 23 | protected readonly tokenFactory: TokenFactory, 24 | protected readonly customAlgorithm: CustomAlgorithm, 25 | ) { 26 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 27 | } 28 | 29 | init(h5stInitConfig: H5stInitConfig) { 30 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.2.0']]); 31 | } 32 | 33 | __genSign(key: string, body: KVType[]): string { 34 | const paramsStr = super.__genSign(key, body); 35 | const signedStr = this.algos.SHA256(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 36 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 37 | return signedStr; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.3.1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.3.1.ts 3 | * Description: h5st4.3.1 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st431 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st431.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.3.1']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.HmacSHA256(paramsStr, key).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.3.3.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.3.3.ts 3 | * Description: h5st4.3.3 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st433 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st433.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.3.3']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.HmacSHA256(paramsStr, key).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.4.0.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.4.0.ts 3 | * Description: h5st4.4.0 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st440 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st440.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.4.0']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.7.1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.7.1.ts 3 | * Description: h5st4.7.1 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st471 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st471.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.7.1']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.SHA256(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.7.2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.7.2.ts 3 | * Description: h5st4.7.2 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st472 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st472.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.7.2']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.HmacSHA256(paramsStr, key).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.7.3.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.7.3.ts 3 | * Description: h5st4.7.3 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st473 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st473.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.7.3']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.HmacSHA256(paramsStr, key).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.7.4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.7.4.ts 3 | * Description: h5st4.7.4 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st474 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st474.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.7.4']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | 39 | __genSignDefault(key: string, body: KVType[]): string { 40 | const paramsStr = super.__genSignDefault(key, body); 41 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 42 | this._log(`__genSignDefault, paramsStr:${paramsStr}, signedStr:${signedStr}`); 43 | return signedStr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.8.1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.8.1.ts 3 | * Description: h5st4.8.1 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st481 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st481.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['04']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.8.1']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.SHA256(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | 39 | __genSignDefault(key: string, body: KVType[]): string { 40 | const paramsStr = super.__genSignDefault(key, body); 41 | const signedStr = this.algos.SHA256(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 42 | this._log(`__genSignDefault, paramsStr:${paramsStr}, signedStr:${signedStr}`); 43 | return signedStr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.8.2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.8.2.ts 3 | * Description: h5st4.8.2 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st482 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st482.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['04']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.8.2']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | 39 | __genSignDefault(key: string, body: KVType[]): string { 40 | const paramsStr = super.__genSignDefault(key, body); 41 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 42 | this._log(`__genSignDefault, paramsStr:${paramsStr}, signedStr:${signedStr}`); 43 | return signedStr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/h5st/h5st4.9.1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.9.1.ts 3 | * Description: h5st4.9.1 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class H5st491 extends BaseH5st { 18 | protected readonly logger = new Logger(H5st491.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['04']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['4.9.1']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}, key:${key}`); 36 | return signedStr; 37 | } 38 | 39 | __genSignDefault(key: string, body: KVType[]): string { 40 | const paramsStr = super.__genSignDefault(key, body); 41 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 42 | this._log(`__genSignDefault, paramsStr:${paramsStr}, signedStr:${signedStr}, key:${key}`); 43 | return signedStr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/h5st/h5stAlgoConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5stAlgoConfig.ts 3 | * Description: h5st 加签算法参数配置 4 | * Author: zhx47 5 | */ 6 | 7 | import { H5stAlgoConfigType, H5stVersion } from './type'; 8 | 9 | export class H5stInitConfig { 10 | debug?: boolean = false; 11 | appId: string; 12 | } 13 | 14 | class H5st420AlgoConfig implements H5stAlgoConfigType { 15 | version = '4.2'; 16 | env = { 17 | secret: 'DNiHi703B0&17hh1', 18 | bu1: '0.1.9', 19 | fv: 'h5_file_v4.2.0', 20 | randomLength: 10, 21 | }; 22 | visitKey = { 23 | seed: '6d0jhqw3pa', 24 | selectLength: 4, 25 | randomLength: 11, 26 | convertLength: 14, 27 | }; 28 | defaultKey = { 29 | extend: '9>5*t5', 30 | }; 31 | makeSign = { 32 | extendDateStr: '74', 33 | }; 34 | genLocalTK = { 35 | baseInfo: { 36 | magic: 'tk', 37 | version: '02', 38 | platform: 'w', 39 | expires: '41', 40 | producer: 'l', 41 | expr: '', 42 | cipher: '', 43 | adler32: '', 44 | }, 45 | cipher: { 46 | secret1: 'qem7+)g%Dhw5', 47 | prefix: 'z7', 48 | secret2: 'x6e@RoHi$Fgy7!5k', 49 | }, 50 | }; 51 | } 52 | 53 | class H5st431AlgoConfig implements H5stAlgoConfigType { 54 | version = '4.3'; 55 | env = { 56 | secret: '&d74&yWoV.EYbWbZ', 57 | bu1: '0.1.7', 58 | fv: 'h5_file_v4.3.1', 59 | randomLength: 10, 60 | }; 61 | visitKey = { 62 | seed: 'kl9i1uct6d', 63 | selectLength: 3, 64 | randomLength: 12, 65 | convertLength: 10, 66 | }; 67 | defaultKey = { 68 | extend: 'Z=', 164 | secret2: 'eHL4|FW#Chc3#q?0', 165 | }, 166 | }; 167 | } 168 | 169 | class H5st471AlgoConfig implements H5stAlgoConfigType { 170 | version = '4.7'; 171 | env = { 172 | secret: '_M6Y?dvfN40VMF[X', 173 | bu1: '0.1.5', 174 | fv: 'h5_file_v4.7.1', 175 | randomLength: 12, 176 | }; 177 | visitKey = { 178 | seed: '1uct6d0jhq', 179 | selectLength: 5, 180 | randomLength: 10, 181 | convertLength: 15, 182 | }; 183 | defaultKey = { 184 | extend: 'hh1BNE', 185 | }; 186 | makeSign = { 187 | extendDateStr: '97', 188 | }; 189 | genLocalTK = { 190 | baseInfo: { 191 | magic: 'tk', 192 | version: '03', 193 | platform: 'w', 194 | expires: '41', 195 | producer: 'l', 196 | expr: '', 197 | cipher: '', 198 | adler32: '', 199 | }, 200 | cipher: { 201 | secret1: '8[8I[]d?960w', 202 | prefix: 'cw', 203 | secret2: 'XsiRvI<7|NC-1g5X', 204 | }, 205 | }; 206 | customAlgorithm = { 207 | salt: '23k@X!', 208 | map: 'WVUTSRQPONMLKJIHGFEDCBA-_9876543210zyxwvutsrqponmlkjihgfedcbaZYX', 209 | keyReverse: true, 210 | convertIndex: { 211 | hmac: 16, 212 | }, 213 | }; 214 | } 215 | 216 | class H5st472AlgoConfig implements H5stAlgoConfigType { 217 | version = '4.7'; 218 | env = { 219 | secret: '_M6Y?dvfN40VMF[X', 220 | bu1: '0.1.5', 221 | fv: 'h5_file_v4.7.2', 222 | randomLength: 12, 223 | }; 224 | visitKey = { 225 | seed: '1uct6d0jhq', 226 | selectLength: 5, 227 | randomLength: 10, 228 | convertLength: 15, 229 | }; 230 | defaultKey = { 231 | extend: '87n8!-', 232 | }; 233 | makeSign = { 234 | extendDateStr: '07', 235 | }; 236 | genLocalTK = { 237 | baseInfo: { 238 | magic: 'tk', 239 | version: '03', 240 | platform: 'w', 241 | expires: '41', 242 | producer: 'l', 243 | expr: '', 244 | cipher: '', 245 | adler32: '', 246 | }, 247 | cipher: { 248 | secret1: 'K3rOqML0Qq&D', 249 | prefix: 'C2', 250 | secret2: '=F)?n7@]OFX62bT5', 251 | }, 252 | }; 253 | customAlgorithm = { 254 | salt: 'JdM3|5', 255 | map: 'WVUTSRQPONMLKJIHGFEDCBA-_9876543210zyxwvutsrqponmlkjihgfedcbaZYX', 256 | keyReverse: true, 257 | convertIndex: { 258 | hmac: 7, 259 | }, 260 | }; 261 | } 262 | 263 | class H5st473AlgoConfig implements H5stAlgoConfigType { 264 | version = '4.7'; 265 | env = { 266 | secret: '_M6Y?dvfN40VMF[X', 267 | bu1: '0.1.5', 268 | fv: 'h5_file_v4.7.3', 269 | randomLength: 12, 270 | }; 271 | visitKey = { 272 | seed: '1uct6d0jhq', 273 | selectLength: 5, 274 | randomLength: 10, 275 | convertLength: 15, 276 | }; 277 | defaultKey = { 278 | extend: 'kEjxS-', 279 | }; 280 | makeSign = { 281 | extendDateStr: '78', 282 | }; 283 | genLocalTK = { 284 | baseInfo: { 285 | magic: 'tk', 286 | version: '03', 287 | platform: 'w', 288 | expires: '41', 289 | producer: 'l', 290 | expr: '', 291 | cipher: '', 292 | adler32: '', 293 | }, 294 | cipher: { 295 | secret1: 'A._/XV*bOm%!', 296 | prefix: 'dl', 297 | secret2: 'qV!+A!tmuU#Z7/2_', 298 | }, 299 | }; 300 | customAlgorithm = { 301 | salt: '=LN6GO', 302 | map: 'WVUTSRQPONMLKJIHGFEDCBA-_9876543210zyxwvutsrqponmlkjihgfedcbaZYX', 303 | keyReverse: true, 304 | convertIndex: { 305 | hmac: 3, 306 | }, 307 | }; 308 | } 309 | 310 | class H5st474AlgoConfig implements H5stAlgoConfigType { 311 | genSignDefault = true; 312 | version = '4.7'; 313 | env = { 314 | secret: '_M6Y?dvfN40VMF[X', 315 | bu1: '0.1.5', 316 | fv: 'h5_file_v4.7.4', 317 | randomLength: 11, 318 | }; 319 | visitKey = { 320 | seed: '1uct6d0jhq', 321 | selectLength: 5, 322 | randomLength: 10, 323 | convertLength: 15, 324 | }; 325 | defaultKey = { 326 | extend: 'Mp(2C1', 327 | }; 328 | makeSign = { 329 | extendDateStr: '47', 330 | }; 331 | genLocalTK = { 332 | baseInfo: { 333 | magic: 'tk', 334 | version: '03', 335 | platform: 'w', 336 | expires: '41', 337 | producer: 'l', 338 | expr: '', 339 | cipher: '', 340 | adler32: '', 341 | }, 342 | cipher: { 343 | secret1: '4*iK&33Z|+6)', 344 | prefix: 'FX', 345 | secret2: 'zR>U5mz40W99&8sg', 346 | }, 347 | }; 348 | customAlgorithm = { 349 | salt: '7n55*t5', 545 | }; 546 | makeSign = { 547 | extendDateStr: '74', 548 | }; 549 | genLocalTK = { 550 | baseInfo: { 551 | magic: 'tk', 552 | version: '02', 553 | platform: 'a', 554 | expires: '41', 555 | producer: 'l', 556 | expr: '', 557 | cipher: '', 558 | adler32: '', 559 | }, 560 | cipher: { 561 | secret1: 'qem7+)g%Dhw5', 562 | prefix: 'z7', 563 | secret2: 'x6e@RoHi$Fgy7!5k', 564 | }, 565 | }; 566 | } 567 | 568 | class Xcx471AlgoConfig implements H5stAlgoConfigType { 569 | version = '4.7'; 570 | env = { 571 | secret: '_M6Y?dvfN40VMF[X', 572 | fv: 'xcx_v4.7.1', 573 | randomLength: 10, 574 | }; 575 | visitKey = { 576 | seed: '1uct6d0jhq', 577 | selectLength: 5, 578 | randomLength: 10, 579 | convertLength: 15, 580 | }; 581 | defaultKey = { 582 | extend: '?SPKw5', 583 | }; 584 | makeSign = { 585 | extendDateStr: '22', 586 | }; 587 | genLocalTK = { 588 | baseInfo: { 589 | magic: 'tk', 590 | version: '03', 591 | platform: 'a', 592 | expires: '41', 593 | producer: 'l', 594 | expr: '', 595 | cipher: '', 596 | adler32: '', 597 | }, 598 | cipher: { 599 | secret1: 'TY5G5cIQz9WS', 600 | prefix: 'LS', 601 | secret2: '$Yr%39]TC2u_p<&9', 602 | }, 603 | }; 604 | customAlgorithm: { 605 | salt: 'j04vfp'; 606 | map: 'WVUTSRQPONMLKJIHGFEDCBA-_9876543210zyxwvutsrqponmlkjihgfedcbaZYX'; 607 | keyReverse: true; 608 | convertIndex: { 609 | hmac: 4; 610 | }; 611 | }; 612 | } 613 | 614 | class Xcx491AlgoConfig implements H5stAlgoConfigType { 615 | genSignDefault = true; 616 | version = '4.9'; 617 | env = { 618 | fv: 'xcx_v4.9.1', 619 | randomLength: 10, 620 | }; 621 | visitKey = { 622 | seed: 'z4rekl9i1u', 623 | selectLength: 4, 624 | randomLength: 11, 625 | convertLength: 8, 626 | }; 627 | defaultKey = { 628 | extend: 'K.%@Ou', 629 | }; 630 | makeSign = { 631 | extendDateStr: '98', 632 | }; 633 | genLocalTK = { 634 | baseInfo: { 635 | magic: 'tk', 636 | version: '04', 637 | platform: 'a', 638 | expires: '41', 639 | producer: 'l', 640 | expr: '', 641 | cipher: '', 642 | adler32: '', 643 | }, 644 | cipher: { 645 | secret1: 'Ox18GNmWXl00', 646 | prefix: 'kM', 647 | }, 648 | }; 649 | customAlgorithm: { 650 | salt: '$_+0zz'; 651 | map: 'rqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA-_9876543210zyxwvuts'; 652 | convertIndex: { 653 | hex: 8; 654 | hmac: 2; 655 | }; 656 | }; 657 | } 658 | 659 | export const H5stAlgoConfigCollection: Record = { 660 | [H5stVersion['4.2.0']]: new H5st420AlgoConfig(), 661 | [H5stVersion['4.3.1']]: new H5st431AlgoConfig(), 662 | [H5stVersion['4.3.3']]: new H5st433AlgoConfig(), 663 | [H5stVersion['4.4.0']]: new H5st440AlgoConfig(), 664 | [H5stVersion['4.7.1']]: new H5st471AlgoConfig(), 665 | [H5stVersion['4.7.2']]: new H5st472AlgoConfig(), 666 | [H5stVersion['4.7.3']]: new H5st473AlgoConfig(), 667 | [H5stVersion['4.7.4']]: new H5st474AlgoConfig(), 668 | [H5stVersion['4.8.1']]: new H5st481AlgoConfig(), 669 | [H5stVersion['4.8.2']]: new H5st482AlgoConfig(), 670 | [H5stVersion['4.9.1']]: new H5st491AlgoConfig(), 671 | [H5stVersion['xcx3.1.0']]: new Xcx310AlgoConfig(), 672 | [H5stVersion['xcx4.2.0']]: new Xcx420AlgoConfig(), 673 | [H5stVersion['xcx4.7.1']]: new Xcx471AlgoConfig(), 674 | [H5stVersion['xcx4.9.1']]: new Xcx491AlgoConfig(), 675 | }; 676 | -------------------------------------------------------------------------------- /src/services/h5st/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: type.ts 3 | * Description: h5st 加签算法通用类型 4 | * Author: zhx47 5 | */ 6 | 7 | /** 8 | * h5st 加签结果枚举 9 | */ 10 | export enum ErrCodes { 11 | // 参数错误 12 | UNSIGNABLE_PARAMS = 1, 13 | // 缺少验签ID 14 | APPID_ABSENT = 2, 15 | } 16 | 17 | export interface H5stAlgoContextType { 18 | /* 以下是每个线程的私有定义 */ 19 | _debug: boolean; 20 | _isNormal: boolean; 21 | __genKey: (token: string, fingerprint: string, dateStrExtend: string, appId: string, algo: typeof CryptoJS) => string; 22 | _storageTokenKey: string; 23 | _storageAlgoKey: string; 24 | _storageFpKey: string; 25 | _defaultAlgorithm: defaultAlgorithmType; 26 | _version: string; 27 | _appId: string; 28 | _fingerprint: string; 29 | _token: string; 30 | _defaultToken: string; 31 | envExtend?: EnvCollectType; 32 | userAgent: string; 33 | pt_pin: string; 34 | subVersion: string; 35 | } 36 | 37 | export interface defaultAlgorithmType { 38 | local_key_1: typeof CryptoJS.MD5; 39 | local_key_2: typeof CryptoJS.SHA256; 40 | local_key_3: typeof CryptoJS.HmacSHA256; 41 | } 42 | 43 | /** 44 | * h5st 算法参数类型 45 | */ 46 | export interface H5stAlgoConfigType { 47 | genSignDefault?: boolean; 48 | version: string; 49 | env: { 50 | secret?: string; 51 | bu1?: string; 52 | fv?: string; 53 | randomLength: number; 54 | }; 55 | visitKey: { 56 | seed: string; 57 | selectLength: number; 58 | randomLength: number; 59 | convertLength: number; 60 | }; 61 | defaultKey: { 62 | extend: string; 63 | }; 64 | makeSign: { 65 | extendDateStr: string; 66 | }; 67 | genLocalTK: { 68 | baseInfo: TokenType; 69 | cipher: { 70 | secret1: string; 71 | prefix: string; 72 | secret2?: string; 73 | }; 74 | }; 75 | customAlgorithm?: { 76 | salt?: string; 77 | map?: string; 78 | keyReverse?: boolean; 79 | convertIndex?: { 80 | hmac?: number; 81 | hex?: number; 82 | }; 83 | }; 84 | } 85 | 86 | /** 87 | * h5st token 类型 88 | */ 89 | export interface TokenType { 90 | magic: string; 91 | version: string; 92 | platform: string; 93 | expires: string; 94 | producer: string; 95 | expr: string; 96 | cipher: string; 97 | adler32: string; 98 | } 99 | 100 | /** 101 | * h5st env 类型 102 | */ 103 | export interface EnvCollectType { 104 | pp?: EnvPpType; 105 | extend?: EnvExtendType; 106 | random: string; 107 | sua?: string; 108 | v?: string; 109 | fp?: string; 110 | wc?: string | number; 111 | wd?: string | number; 112 | l?: string | number; 113 | ls?: string | number; 114 | ml?: string | number; 115 | pl?: string | number; 116 | av?: string | number; 117 | ua?: string | number; 118 | w?: string | number; 119 | h?: string | number; 120 | ow?: string | number; 121 | oh?: string | number; 122 | url?: string | number; 123 | og?: string | number; 124 | pf?: string | number; 125 | bu2?: string | number; 126 | canvas?: string; 127 | canvas1?: string; 128 | webglFp?: string; 129 | webglFp1?: string; 130 | ccn?: string | number; 131 | ai?: string | number; 132 | pr?: string | number; 133 | re?: string | number; 134 | referer?: string | number; 135 | pp1?: string | number; 136 | } 137 | 138 | export interface EnvPpType { 139 | p1?: string; 140 | p2?: string; 141 | } 142 | 143 | /** 144 | * h5st env extend 类型 145 | */ 146 | export interface EnvExtendType { 147 | bu1?: string | number; 148 | bu2?: string | number; 149 | bu3?: string | number; 150 | bu4?: string | number; 151 | bu5?: string | number; 152 | bu6?: string | number; 153 | bu7?: string | number; 154 | bu8?: string | number; 155 | l?: string | number; 156 | ls?: string | number; 157 | wd?: string | number; 158 | wk?: string | number; 159 | pm?: string | number; 160 | } 161 | 162 | /** 163 | * 目前支持的算法版本枚举 164 | */ 165 | export enum H5stVersion { 166 | '4.2.0' = '4.2.0', 167 | '4.3.1' = '4.3.1', 168 | '4.3.3' = '4.3.3', 169 | '4.4.0' = '4.4.0', 170 | '4.7.1' = '4.7.1', 171 | '4.7.2' = '4.7.2', 172 | '4.7.3' = '4.7.3', 173 | '4.7.4' = '4.7.4', 174 | '4.8.1' = '4.8.1', 175 | '4.8.2' = '4.8.2', 176 | '4.9.1' = '4.9.1', 177 | 'xcx3.1.0' = 'xcx3.1.0', 178 | 'xcx4.2.0' = 'xcx4.2.0', 179 | 'xcx4.7.1' = 'xcx4.7.1', 180 | 'xcx4.9.1' = 'xcx4.9.1', 181 | } 182 | 183 | export enum LocalTokenVersion { 184 | '03' = '03', 185 | '04' = '04', 186 | } 187 | 188 | /** 189 | * h5st 参与加签的业务参数 190 | */ 191 | export interface H5stSignParamsType { 192 | functionId: string; 193 | appid: string; 194 | client?: string; 195 | body: string; 196 | clientVersion?: string; 197 | sign?: string; 198 | t?: string; 199 | jsonp?: string; 200 | } 201 | 202 | /** 203 | * 一个 K-V 的类型 204 | */ 205 | export interface KVType { 206 | key: string; 207 | value?: string; 208 | } 209 | 210 | export interface H5stSignResultType { 211 | _stk?: string; 212 | _ste?: number; 213 | h5st?: string; 214 | } 215 | 216 | export interface LocalTokenType { 217 | genLocalTK: (fp: string) => string; 218 | } 219 | -------------------------------------------------------------------------------- /src/services/h5st/xcx3.1.0.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: xcx3.1.0.ts 3 | * Description: 小程序h5st3.1.0 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { getRandomIDPro } from '../../utils/baseUtils'; 14 | import { TokenFactory } from '../../factory/token.factory'; 15 | import { CustomAlgorithm } from './customAlgorithm'; 16 | 17 | @Injectable() 18 | export class Xcx310 extends BaseH5st { 19 | protected readonly logger = new Logger(Xcx310.name); 20 | constructor( 21 | protected readonly clsService: ClsService, 22 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 23 | protected readonly tokenFactory: TokenFactory, 24 | protected readonly customAlgorithm: CustomAlgorithm, 25 | ) { 26 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 27 | } 28 | 29 | init(h5stInitConfig: H5stInitConfig) { 30 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['xcx3.1.0']]); 31 | 32 | // 3.1 localTk中的参数是变化量,未写死 33 | const randomIDPro = getRandomIDPro({ size: 32, dictType: 'max' }); 34 | const prefix = randomIDPro.slice(0, 2); 35 | const secret1 = randomIDPro.slice(0, 12); 36 | this.clsService.set('h5stConfig.genLocalTK.cipher.prefix', prefix); 37 | this.clsService.set('h5stConfig.genLocalTK.cipher.secret1', secret1); 38 | } 39 | 40 | __genSign(key: string, body: KVType[]): string { 41 | const paramsStr = super.__genSign(key, body); 42 | const signedStr = this.algos.HmacSHA256(paramsStr, key).toString(this.algos.enc.Hex); 43 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 44 | return signedStr; 45 | } 46 | 47 | convertVisitKey(combinedString: string): string { 48 | const charArray = combinedString.split(''); 49 | const finalArray = []; 50 | for (; charArray.length > 0; ) finalArray.push(9 - parseInt(charArray.pop())); 51 | return finalArray.join(''); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/services/h5st/xcx4.2.0.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: xcx4.2.0.ts 3 | * Description: 小程序h5st4.2.0 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class Xcx420 extends BaseH5st { 18 | protected readonly logger = new Logger(Xcx420.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['xcx4.2.0']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.SHA256(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/services/h5st/xcx4.7.1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: xcx4.7.1.ts 3 | * Description: 小程序h5st4.7.1 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class Xcx471 extends BaseH5st { 18 | protected readonly logger = new Logger(Xcx471.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly customAlgorithm: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['03']), customAlgorithm); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['xcx4.7.1']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.SHA256(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/services/h5st/xcx4.9.1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: h5st4.9.1.ts 3 | * Description: h5st4.9.1 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { Inject, Injectable, Logger } from '@nestjs/common'; 8 | import { BaseH5st } from './baseH5st'; 9 | import { H5stVersion, KVType, LocalTokenVersion } from './type'; 10 | import { H5stAlgoConfigCollection, H5stInitConfig } from './h5stAlgoConfig'; 11 | import { ClsService } from 'nestjs-cls'; 12 | import { Cache, CACHE_MANAGER } from 'nestjs-cache-manager-v6'; 13 | import { TokenFactory } from '../../factory/token.factory'; 14 | import { CustomAlgorithm } from './customAlgorithm'; 15 | 16 | @Injectable() 17 | export class Xcx491 extends BaseH5st { 18 | protected readonly logger = new Logger(Xcx491.name); 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | @Inject(CACHE_MANAGER) protected readonly cacheManager: Cache, 22 | protected readonly tokenFactory: TokenFactory, 23 | protected readonly algos: CustomAlgorithm, 24 | ) { 25 | super(clsService, cacheManager, tokenFactory.getInstance(LocalTokenVersion['04']), algos); 26 | } 27 | 28 | init(h5stInitConfig: H5stInitConfig) { 29 | super.init(h5stInitConfig, H5stAlgoConfigCollection[H5stVersion['xcx4.9.1']]); 30 | } 31 | 32 | __genSign(key: string, body: KVType[]): string { 33 | const paramsStr = super.__genSign(key, body); 34 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 35 | this._log(`__genSign, paramsStr:${paramsStr}, signedStr:${signedStr}`); 36 | return signedStr; 37 | } 38 | 39 | __genSignDefault(key: string, body: KVType[]): string { 40 | const paramsStr = super.__genSignDefault(key, body); 41 | const signedStr = this.algos.MD5(`${key}${paramsStr}${key}`).toString(this.algos.enc.Hex); 42 | this._log(`__genSignDefault, paramsStr:${paramsStr}, signedStr:${signedStr}`); 43 | return signedStr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/logger/winston.config.ts: -------------------------------------------------------------------------------- 1 | import { WinstonModuleOptions, WinstonModuleOptionsFactory } from 'nest-winston'; 2 | import { ClsService } from 'nestjs-cls'; 3 | import * as winston from 'winston'; 4 | import { Injectable } from '@nestjs/common'; 5 | 6 | @Injectable() 7 | export class WinstonConfigService implements WinstonModuleOptionsFactory { 8 | constructor(protected readonly clsService: ClsService) {} 9 | 10 | createWinstonModuleOptions(): Promise | WinstonModuleOptions { 11 | return { 12 | level: 'debug', 13 | transports: [ 14 | new winston.transports.Console({ 15 | format: winston.format.combine( 16 | winston.format((info) => { 17 | info.level = info.level.toUpperCase().padStart(5); 18 | return info; 19 | })(), 20 | winston.format.timestamp({ 21 | format: 'YYYY-MM-DD HH:mm:ss', 22 | }), 23 | winston.format.colorize(), 24 | winston.format.printf((info) => { 25 | const traceId = this.clsService?.getId() || '', 26 | pid = String(process.pid).padStart(5); 27 | 28 | let context = String(info?.context || ''); 29 | if (context.length > 20) { 30 | context = context.slice(-25); 31 | } else { 32 | context = context.padEnd(25, ' '); 33 | } 34 | 35 | if (traceId) { 36 | return `${info.timestamp} ${info.level} ${pid} -- [${traceId}] - ${context}: ${info.message} ${info?.stack || ''}`; 37 | } else { 38 | return `${info.timestamp} ${info.level} ${pid} --- ${context}: ${info.message} ${info?.stack || ''}`; 39 | } 40 | }), 41 | ), 42 | }), 43 | ], 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/services/token/baseLocalToken.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: baseLocalToken.ts 3 | * Description: LocalToken 通用父类 4 | * Author: zhx47 5 | */ 6 | 7 | import { fromBase64, getRandomIDPro, stringToHex, toHexString, toUint8ArrayFromNumber } from '../../utils/baseUtils'; 8 | import { LocalTokenType, TokenType } from '../h5st/type'; 9 | import { ClsService } from 'nestjs-cls'; 10 | import { CustomAlgorithm } from '../h5st/customAlgorithm'; 11 | 12 | export class BaseLocalToken implements LocalTokenType { 13 | constructor( 14 | protected readonly clsService: ClsService, 15 | protected readonly algos: CustomAlgorithm, 16 | ) {} 17 | 18 | /** 19 | * 本地生成 token 20 | */ 21 | genLocalTK(fp: string): string { 22 | const h5stConfig = this.clsService.get('h5stConfig.genLocalTK.baseInfo'); 23 | const tokenData = { 24 | ...h5stConfig, 25 | }; 26 | 27 | tokenData.expr = this.generateTokenExpr(); 28 | tokenData.cipher = this.generateTokenCipher(fp); 29 | tokenData.adler32 = this.generateTokenAdler32(tokenData); 30 | return this.formatToken(tokenData); 31 | } 32 | 33 | /** 34 | * 生成 token expr 35 | */ 36 | generateTokenExpr() { 37 | const randomChars = () => getRandomIDPro({ size: 32, dictType: 'max' }); 38 | const numbers = ['1', '2', '3']; 39 | const operators = ['+', 'x']; 40 | const length = 2 + Math.floor(4 * Math.random()); 41 | let expression = ''; 42 | 43 | for (let i = 0; i < length; i++) { 44 | expression += numbers[Math.floor(Math.random() * 3)]; 45 | if (i < length - 1) { 46 | expression += operators[Math.floor(Math.random() * 2)]; 47 | } 48 | } 49 | 50 | if (expression.length < 9) { 51 | expression += randomChars().slice(0, 9 - expression.length); 52 | } 53 | 54 | const utf8Encoded = this.algos.enc.Utf8.parse(expression); 55 | return fromBase64(this.algos.enc.Base64.stringify(utf8Encoded)); 56 | } 57 | 58 | /** 59 | * 生成 token cipher 60 | */ 61 | generateTokenCipher(fp: string): string { 62 | const secret1 = this.clsService.get('h5stConfig.genLocalTK.cipher.secret1'), 63 | prefix = this.clsService.get('h5stConfig.genLocalTK.cipher.prefix'); 64 | 65 | let tokenCipherPlain = ''; 66 | const now = Date.now(), 67 | v = this.generateChecksumFromParameters(fp, now, prefix, secret1); 68 | tokenCipherPlain += stringToHex(v); 69 | tokenCipherPlain += stringToHex(prefix); 70 | tokenCipherPlain += stringToHex(secret1); 71 | tokenCipherPlain += toHexString(toUint8ArrayFromNumber(now)); 72 | tokenCipherPlain += stringToHex(fp); 73 | 74 | return this.tokenCipherEncrypt(tokenCipherPlain); 75 | } 76 | 77 | /** 78 | * 生成 token adler32 79 | */ 80 | generateTokenAdler32(_: TokenType): string { 81 | throw new Error('请实现'); 82 | } 83 | 84 | /** 85 | * 组装 token 86 | */ 87 | formatToken(tokenData: TokenType): string { 88 | return tokenData.magic + tokenData.version + tokenData.platform + tokenData.adler32 + tokenData.expires + tokenData.producer + tokenData.expr + tokenData.cipher; 89 | } 90 | 91 | /** 92 | * 通过参数生成校验码 93 | * @param fp fingerprint 指纹 94 | * @param now 当前时间戳 95 | * @param prefix 密文1 96 | * @param secret1 密文2 97 | * @returns 校验码 98 | */ 99 | generateChecksumFromParameters(fp: string, now: number, prefix: string, secret1: string): string { 100 | const fingerprintBytes = new Uint8Array(16), 101 | timeBytes = toUint8ArrayFromNumber(now), 102 | prefixBytes = new Uint8Array(2), 103 | secret1Bytes = new Uint8Array(12), 104 | combinedBytes = new Uint8Array(38); 105 | 106 | fingerprintBytes.forEach((_, index) => { 107 | fingerprintBytes[index] = fp.charCodeAt(index); 108 | }); 109 | 110 | prefixBytes.forEach((_, index) => { 111 | prefixBytes[index] = prefix.charCodeAt(index); 112 | }); 113 | 114 | secret1Bytes.forEach((_, index) => { 115 | secret1Bytes[index] = secret1.charCodeAt(index); 116 | }); 117 | 118 | combinedBytes.set(prefixBytes); 119 | combinedBytes.set(secret1Bytes, 2); 120 | combinedBytes.set(timeBytes, 14); 121 | combinedBytes.set(fingerprintBytes, 22); 122 | 123 | return this.generateChecksum(combinedBytes); 124 | } 125 | 126 | /** 127 | * 加密 Token Cipher 128 | * @param _ Token Cipher 明文 129 | * @returns Token Cipher密文 130 | */ 131 | tokenCipherEncrypt(_: string): string { 132 | throw new Error('请实现'); 133 | } 134 | 135 | /** 136 | * 生成校验码 137 | * @param _ 原始数据 138 | * @returns 校验码 139 | */ 140 | generateChecksum(_: Uint8Array): string { 141 | throw new Error('请实现'); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/services/token/localTokenV3.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: localTokenV3.ts 3 | * Description: localToken v3 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { ClsService } from 'nestjs-cls'; 8 | import { Injectable, Logger } from '@nestjs/common'; 9 | import { fromBase64 } from '../../utils/baseUtils'; 10 | import { TokenType } from '../h5st/type'; 11 | import * as ADLER32 from 'adler-32'; 12 | import { BaseLocalToken } from './baseLocalToken'; 13 | import { CustomAlgorithm } from '../h5st/customAlgorithm'; 14 | 15 | @Injectable() 16 | export class LocalTokenV3 extends BaseLocalToken { 17 | protected readonly logger = new Logger(LocalTokenV3.name); 18 | 19 | constructor( 20 | protected readonly clsService: ClsService, 21 | protected readonly algos: CustomAlgorithm, 22 | ) { 23 | super(clsService, algos); 24 | } 25 | 26 | /** 27 | * 加密 Token Cipher 28 | * @param tokenCipherPlain Token Cipher 明文 29 | * @returns Token Cipher密文 30 | */ 31 | tokenCipherEncrypt(tokenCipherPlain: string): string { 32 | const secret2 = this.clsService.get('h5stConfig.genLocalTK.cipher.secret2'); 33 | const b = this.algos.AES.encrypt(this.algos.enc.Hex.parse(tokenCipherPlain), this.algos.enc.Utf8.parse(secret2), { 34 | iv: this.algos.enc.Utf8.parse('0102030405060708'), 35 | }); 36 | return fromBase64(this.algos.enc.Base64.stringify(b.ciphertext)); 37 | } 38 | 39 | /** 40 | * 生成 token adler32 41 | * @param tokenData 42 | */ 43 | generateTokenAdler32(tokenData: TokenType) { 44 | const checksum = ADLER32.str(tokenData.magic + tokenData.version + tokenData.platform + tokenData.expires + tokenData.producer + tokenData.expr + tokenData.cipher) >>> 0; 45 | return ('00000000' + checksum.toString(16)).slice(-8); 46 | } 47 | 48 | /** 49 | * 生成校验码 50 | * @param combinedBytes 原始数据 51 | * @returns 校验码 52 | */ 53 | generateChecksum(combinedBytes: Uint8Array): string { 54 | let checksumValue = ADLER32.buf(combinedBytes); 55 | checksumValue >>>= 0; 56 | const checksumHex = '00000000' + checksumValue.toString(16); 57 | return checksumHex.slice(-8); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/services/token/localTokenV4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: localTokenV4.ts 3 | * Description: localToken v4 算法 4 | * Author: zhx47 5 | */ 6 | 7 | import { ClsService } from 'nestjs-cls'; 8 | import { Injectable, Logger } from '@nestjs/common'; 9 | import { TokenType } from '../h5st/type'; 10 | import { LocalTokenV3 } from './localTokenV3'; 11 | import { BaseLocalToken } from './baseLocalToken'; 12 | import { CustomAlgorithm } from '../h5st/customAlgorithm'; 13 | 14 | @Injectable() 15 | export class LocalTokenV4 extends BaseLocalToken { 16 | protected readonly logger = new Logger(LocalTokenV3.name); 17 | 18 | constructor( 19 | protected readonly clsService: ClsService, 20 | protected readonly algos: CustomAlgorithm, 21 | ) { 22 | super(clsService, algos); 23 | } 24 | 25 | tokenCipherEncrypt(tokenCipherPlain: string): string { 26 | return this.algos.enc.Base64.encode(this.algos.enc.Hex.parse(tokenCipherPlain)); 27 | } 28 | 29 | generateTokenAdler32(tokenData: TokenType) { 30 | const checksum = tokenData.magic + tokenData.version + tokenData.platform + tokenData.expires + tokenData.producer + tokenData.expr + tokenData.cipher; 31 | return this.algos.MD5(checksum).toString(this.algos.enc.Hex).slice(0, 8); 32 | } 33 | 34 | generateChecksum(combinedBytes: Uint8Array): string { 35 | const wordArray = this.algos.enc.Utils.toWordArray(combinedBytes); 36 | return this.algos.MD5(wordArray).toString(this.algos.enc.Hex).slice(0, 8); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/baseUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: baseUtils.ts 3 | * Description: 基础的工具类 4 | * Author: zhx47 5 | */ 6 | 7 | import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator'; 8 | 9 | /** 10 | * 将字符串进行 URL 安全的 Base64 解码 11 | * @param encodedString 12 | */ 13 | export function decodeBase64URL(encodedString: string): string { 14 | return (encodedString + '===') 15 | .slice(0, encodedString.length + 3 - ((encodedString.length + 3) % 4)) 16 | .replace(/-/g, '+') 17 | .replace(/_/g, '/'); 18 | } 19 | 20 | /** 21 | * 随机生成字符串定义 22 | */ 23 | export class RandomIDProConfig { 24 | size = 10; 25 | dictType?: string = 'number'; 26 | customDict?: string; 27 | } 28 | 29 | /** 30 | * 获取随机字符串 31 | * @param size 字符串长度 32 | * @param dictType 字符串字典模板,默认纯数字 alphabet:大小写字母 max:数字+大小写字母+_- 33 | * @param customDict 自定义字符串字典 34 | */ 35 | export function getRandomIDPro({ size, dictType, customDict }: RandomIDProConfig): string { 36 | let random = ''; 37 | if (!customDict) { 38 | switch (dictType) { 39 | case 'alphabet': 40 | customDict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 41 | break; 42 | case 'max': 43 | customDict = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-'; 44 | break; 45 | default: 46 | customDict = '0123456789'; 47 | } 48 | } 49 | for (; size--; ) random += customDict[(Math.random() * customDict.length) | 0]; 50 | return random; 51 | } 52 | 53 | /** 54 | * 将一个标准的 Base64 编码的字符串转换成 URL 安全的 Base64 编码 55 | * @param str 56 | */ 57 | export function fromBase64(str: string): string { 58 | return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 59 | } 60 | 61 | /** 62 | * 是否为一个普通的 JavaScript 对象(Plain Object) 63 | * @param params 64 | */ 65 | export function isPlainObject(params: object): boolean { 66 | return '[object Object]' === Object.prototype.toString.call(params); 67 | } 68 | 69 | /** 70 | * 判断一个对象是否为空 71 | * @param params 72 | */ 73 | export function isEmpty(params: object): boolean { 74 | return isPlainObject(params) && !Object.keys(params).length; 75 | } 76 | 77 | /** 78 | * 判断参数是否包含 ['h5st', '_stk', '_ste'] 79 | * @param params 80 | */ 81 | export function containsReservedParamName(params: object): boolean { 82 | const reservedParams = ['h5st', '_stk', '_ste']; 83 | const paramKeys = Object.keys(params); 84 | 85 | for (const key of paramKeys) { 86 | if (reservedParams.includes(key)) { 87 | return true; 88 | } 89 | } 90 | 91 | return false; 92 | } 93 | 94 | /** 95 | * 判断参数是否安全:数值型且不为空、字符串、布尔型 96 | * @param obj 97 | */ 98 | export function isSafeParamValue(obj: any) { 99 | const type = typeof obj; 100 | return (type === 'number' && !isNaN(obj as number)) || type === 'string' || type === 'boolean'; 101 | } 102 | 103 | /** 104 | * 格式化日期 105 | * @param timestamp 时间戳 106 | * @param pattern 日志模板 107 | */ 108 | export function formatDate(timestamp: number = Date.now(), pattern = 'yyyy-MM-dd'): string { 109 | const date = new Date(timestamp); 110 | const map: Record = { 111 | 'M+': date.getMonth() + 1, // 月份 112 | 'd+': date.getDate(), // 日 113 | 'D+': date.getDate(), // 日 114 | 'h+': date.getHours(), // 小时 115 | 'H+': date.getHours(), // 小时 116 | 'm+': date.getMinutes(), // 分 117 | 's+': date.getSeconds(), // 秒 118 | 'w+': date.getDay(), // 星期 119 | 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 120 | 'S+': date.getMilliseconds(), // 毫秒 121 | }; 122 | 123 | // 年份处理 124 | if (/(y+)/i.test(pattern)) { 125 | pattern = pattern.replace(/(y+)/i, (match) => { 126 | return (date.getFullYear() + '').slice(4 - match.length); 127 | }); 128 | } 129 | 130 | // 其他部分处理 131 | for (const key in map) { 132 | if (new RegExp(`(${key})`).test(pattern)) { 133 | pattern = pattern.replace(new RegExp(`(${key})`), (match) => { 134 | const value = map[key]; 135 | return match.length === 1 ? value.toString() : ('000' + value).slice(-match.length); 136 | }); 137 | } 138 | } 139 | 140 | return pattern; 141 | } 142 | 143 | /** 144 | * 从源数组中随机选择指定数量的元素。 145 | * @param {string} sourceArray 源数组。 146 | * @param {number} requiredCount 需要选择的元素数量。 147 | * @returns {string} 返回一个包含随机选中元素的字符串。 148 | */ 149 | export function selectRandomElements(sourceArray: string, requiredCount: number): string { 150 | requiredCount = Math.min(requiredCount, sourceArray.length); 151 | 152 | let remainingElements = sourceArray.length; 153 | const selectedElements: string[] = [], 154 | iterator = sourceArray.split(''); 155 | for (const element of iterator) { 156 | if (Math.random() * remainingElements < requiredCount) { 157 | selectedElements.push(element); 158 | if (--requiredCount === 0) { 159 | break; 160 | } 161 | } 162 | remainingElements--; 163 | } 164 | let result = ''; 165 | for (let index = 0; index < selectedElements.length; index++) { 166 | const P: number = (Math.random() * (selectedElements.length - index)) | 0; 167 | result += selectedElements[P]; 168 | selectedElements[P] = selectedElements[selectedElements.length - index - 1]; 169 | } 170 | return result; 171 | } 172 | 173 | /** 174 | * 生成一个0到9之间的随机整数。 175 | * @returns {number} 返回一个随机整数。 176 | */ 177 | export function getRandomInt10(): number { 178 | return (10 * Math.random()) | 0; 179 | } 180 | 181 | /** 182 | * 从字符串中过滤掉指定的字符。 183 | * @param {string} originalStr 原始字符串。 184 | * @param {string} charactersToRemove 需要移除的字符数组。 185 | * @returns {string} 返回过滤后的字符串。 186 | */ 187 | export function filterCharactersFromString(originalStr: string, charactersToRemove: string): string { 188 | for (const characters of charactersToRemove) { 189 | if (originalStr.includes(characters)) { 190 | originalStr = originalStr.replace(characters, ''); 191 | } 192 | } 193 | return originalStr; 194 | } 195 | 196 | @ValidatorConstraint({ async: false }) 197 | export class ContainsCharConstraint implements ValidatorConstraintInterface { 198 | validate(text: string, args: ValidationArguments) { 199 | const [char, num] = args.constraints as [string, number[]]; 200 | const count = text.split(char).length - 1; 201 | return num.includes(count); 202 | } 203 | 204 | defaultMessage(args: ValidationArguments) { 205 | const [char] = args.constraints as [string]; 206 | return `Text must contain exactly 7 occurrences of '${char}'`; 207 | } 208 | } 209 | 210 | export function ContainsChar(char: string, num: number[], validationOptions?: ValidationOptions) { 211 | return function (object: unknown, propertyName: string) { 212 | registerDecorator({ 213 | target: object.constructor, 214 | propertyName: propertyName, 215 | options: validationOptions, 216 | constraints: [char, num], 217 | validator: ContainsCharConstraint, 218 | }); 219 | }; 220 | } 221 | 222 | export function strictBoolean(str: string): boolean { 223 | return str === 'true'; 224 | } 225 | 226 | /** 227 | * Uint8Array 转换为一个十六进制字符串表示 228 | * @param {Uint8Array} byteArray 需要转换的数据 229 | * @return {string} 转换后的十六进制字符串 230 | */ 231 | export function toHexString(byteArray: Uint8Array): string { 232 | return Array.from(byteArray) 233 | .map((byte) => { 234 | const hex = '00' + (255 & byte).toString(16); 235 | return hex.slice(-2); 236 | }) 237 | .join(''); 238 | } 239 | 240 | /** 241 | * 将一个字符串转换为十六进制字符串 242 | * @param {string} str 需要转换的数据 243 | * @return {string} 转换后的十六进制字符串 244 | */ 245 | export function stringToHex(str: string): string { 246 | const byteArray = new Uint8Array(str.length); 247 | byteArray.forEach((_, index) => { 248 | byteArray[index] = str.charCodeAt(index); 249 | }); 250 | return toHexString(byteArray); 251 | } 252 | 253 | /** 254 | * 整数转换为一个 Uint8Array 255 | * @param {number} num 需要转换的数据 256 | * @return {Uint8Array} 转换后的Uint8Array 257 | */ 258 | export function toUint8ArrayFromNumber(num: number): Uint8Array { 259 | const isLittleEndian = (function () { 260 | const buffer = new ArrayBuffer(2); 261 | new DataView(buffer).setInt16(0, 256, true); 262 | return 256 === new Int16Array(buffer)[0]; 263 | })(); 264 | const high = Math.floor(num / Math.pow(2, 32)); 265 | const low = num % Math.pow(2, 32); 266 | const buffer = new ArrayBuffer(8); 267 | const dataView = new DataView(buffer); 268 | if (isLittleEndian) { 269 | dataView.setUint32(0, low, isLittleEndian); 270 | dataView.setUint32(4, high, isLittleEndian); 271 | } else { 272 | dataView.setUint32(0, high, isLittleEndian); 273 | dataView.setUint32(4, low, isLittleEndian); 274 | } 275 | return new Uint8Array(buffer); 276 | } 277 | -------------------------------------------------------------------------------- /src/utils/error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: error.ts 3 | * Description: 自定义异常 4 | * Author: zhx47 5 | */ 6 | 7 | /** 8 | * 自定义业务异常 9 | */ 10 | export class BusinessError extends Error { 11 | constructor(message: string) { 12 | super(message); 13 | this.name = 'BusinessError'; 14 | Object.setPrototypeOf(this, BusinessError.prototype); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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": false, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": false, 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 | "typeRoots": [ 21 | "./node_modules/@types", 22 | "./types", 23 | ] 24 | }, 25 | "include": [ 26 | "src/**/*", 27 | "types/**/*" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /types/nestjs-cls.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * File: global.d.ts 3 | * Description: 扩展定义模块内的默认类型 4 | * Author: zhx47 5 | */ 6 | 7 | import 'nestjs-cls'; 8 | import { H5stAlgoConfigType, H5stAlgoContextType } from '../src/services/h5st/type'; 9 | 10 | /** 11 | * 重新定义默认的上下文对象的类型 12 | */ 13 | declare module 'nestjs-cls' { 14 | interface ClsStore { 15 | h5stConfig?: H5stAlgoConfigType; 16 | h5stContext?: H5stAlgoContextType; 17 | } 18 | } 19 | --------------------------------------------------------------------------------