mirror of
https://github.com/xiaoyiweb/YiAi.git
synced 2026-04-24 12:04:31 +08:00
初始化
This commit is contained in:
BIN
service/src/common/.DS_Store
vendored
Normal file
BIN
service/src/common/.DS_Store
vendored
Normal file
Binary file not shown.
19
service/src/common/auth/adminAuth.guard.ts
Normal file
19
service/src/common/auth/adminAuth.guard.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from './jwtAuth.guard';
|
||||
|
||||
@Injectable()
|
||||
export class AdminAuthGuard extends JwtAuthGuard {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const isAuthorized = await super.canActivate(context);
|
||||
if (!isAuthorized) {
|
||||
return false;
|
||||
}
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
if (user && ['admin', 'super'].includes(user.role)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new UnauthorizedException('非法操作、您的权限等级不足、无法执行当前请求!');
|
||||
}
|
||||
}
|
||||
}
|
||||
20
service/src/common/auth/jwt.strategy.ts
Normal file
20
service/src/common/auth/jwt.strategy.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ConfigService } from 'nestjs-config';
|
||||
import { AuthService } from '../../modules/auth/auth.service';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: configService.get('jwt').secret,
|
||||
});
|
||||
}
|
||||
|
||||
/* fromat decode token return */
|
||||
async validate(payload): Promise<any> {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
70
service/src/common/auth/jwtAuth.guard.ts
Normal file
70
service/src/common/auth/jwtAuth.guard.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { RedisCacheService } from '@/modules/redisCache/redisCache.service';
|
||||
import { HttpException, HttpStatus, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { GlobalConfigService } from '@/modules/globalConfig/globalConfig.service';
|
||||
import { atob, copyRightMsg, getRandomItemFromArray } from '../utils';
|
||||
import { AuthService } from '../../modules/auth/auth.service';
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(
|
||||
private redisCacheService: RedisCacheService,
|
||||
private readonly moduleRef: ModuleRef,
|
||||
private readonly globalConfigService: GlobalConfigService,
|
||||
private readonly authService: AuthService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async canActivate(context) {
|
||||
if (!this.redisCacheService) {
|
||||
this.redisCacheService = this.moduleRef.get(RedisCacheService, { strict: false });
|
||||
}
|
||||
const request = context.switchToHttp().getRequest();
|
||||
// TODO 域名检测
|
||||
const domain = request.headers['x-website-domain'];
|
||||
const token = this.extractToken(request);
|
||||
request.user = this.validateToken(token);
|
||||
const auth = this.globalConfigService.getNineAiToken();
|
||||
await this.redisCacheService.checkTokenAuth(token, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractToken(request) {
|
||||
if (!request.headers.authorization) {
|
||||
if (request.headers.fingerprint) {
|
||||
let id = request.headers.fingerprint;
|
||||
/* 超过mysql最大值进行截取 */
|
||||
if (id > 2147483647) {
|
||||
id = id.toString().slice(-9);
|
||||
id = Number(String(Number(id)));
|
||||
}
|
||||
const token = this.authService.createTokenFromFingerprint(id);
|
||||
return token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const parts = request.headers.authorization.split(' ');
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||
return null;
|
||||
}
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
private validateToken(token) {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_SECRET);
|
||||
} catch (error) {
|
||||
throw new HttpException('亲爱的用户,请登录后继续操作,我们正在等您的到来!', HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
handleRequest(err, user, info) {
|
||||
if (err || !user) {
|
||||
console.log('err: ', err);
|
||||
throw err || new UnauthorizedException();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
19
service/src/common/auth/superAuth.guard.ts
Normal file
19
service/src/common/auth/superAuth.guard.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from './jwtAuth.guard';
|
||||
|
||||
@Injectable()
|
||||
export class SuperAuthGuard extends JwtAuthGuard {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const isAuthorized = await super.canActivate(context);
|
||||
if (!isAuthorized) {
|
||||
return false;
|
||||
}
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
if (user && user.role === 'super') {
|
||||
return true;
|
||||
} else {
|
||||
throw new UnauthorizedException('非法操作、非超级管理员无权操作!');
|
||||
}
|
||||
}
|
||||
}
|
||||
32
service/src/common/constants/balance.constant.ts
Normal file
32
service/src/common/constants/balance.constant.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export const DeductionType = {
|
||||
BALANCE: 'BALANCE_TYPE',
|
||||
CHAT: 'CHAT_TYPE',
|
||||
PAINT: 'PAINT_TYPE',
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: 扣费类型
|
||||
* @param {type}
|
||||
* 1: 模型3 模型4 MJ TODO 新版更新已经修改了 TYPE 这里暂不处理
|
||||
*/
|
||||
export const DeductionKey = {
|
||||
BALANCE_TYPE: 'balance',
|
||||
CHAT_TYPE: 'usesLeft',
|
||||
PAINT_TYPE: 'paintCount',
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: 账户充值类型
|
||||
* @param {type}
|
||||
* 1: 注册赠送 2: 受邀请赠送 3: 邀请人赠送 4: 购买套餐赠送 5: 管理员赠送 6:扫码支付 7: 绘画失败退款 8: 签到奖励
|
||||
*/
|
||||
export const RechargeType = {
|
||||
REG_GIFT: 1,
|
||||
INVITE_GIFT: 2,
|
||||
REFER_GIFT: 3,
|
||||
PACKAGE_GIFT: 4,
|
||||
ADMIN_GIFT: 5,
|
||||
SCAN_PAY: 6,
|
||||
DRAW_FAIL_REFUND: 7,
|
||||
SIGN_IN: 8,
|
||||
};
|
||||
21
service/src/common/constants/errorMessage.constant.ts
Normal file
21
service/src/common/constants/errorMessage.constant.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export enum ErrorMessageEnum {
|
||||
USERNAME_OR_EMAIL_ALREADY_REGISTERED = '用户名或邮箱已注册!',
|
||||
USER_NOT_FOUND = '用户不存在!',
|
||||
VERIFICATION_NOT_FOUND = '验证记录不存在!',
|
||||
VERIFICATION_CODE_EXPIRED = '验证码已过期!',
|
||||
VERIFICATION_CODE_INVALID = '验证码无效!',
|
||||
VERIFICATION_CODE_MISMATCH = '验证码不匹配!',
|
||||
VERIFICATION_CODE_SEND_FAILED = '验证码发送失败!',
|
||||
VERIFICATION_CODE_SEND_TOO_OFTEN = '验证码发送过于频繁!',
|
||||
}
|
||||
|
||||
export const OpenAiErrorCodeMessage: Record<string, string> = {
|
||||
400: '[Inter Error] 服务端错误[400]',
|
||||
401: '[Inter Error] 服务出现错误、请稍后再试一次吧[401]',
|
||||
403: '[Inter Error] 服务器拒绝访问,请稍后再试 | Server refused to access, please try again later',
|
||||
429: '[Inter Error] 当前key调用频率过高、请重新对话再试一次吧[429]',
|
||||
502: '[Inter Error] 错误的网关 | Bad Gateway[502]',
|
||||
503: '[Inter Error] 服务器繁忙,请稍后再试 | Server is busy, please try again later[503]',
|
||||
504: '[Inter Error] 网关超时 | Gateway Time-out[504]',
|
||||
500: '[Inter Error] 服务器繁忙,请稍后再试 | Internal Server Error[500]',
|
||||
};
|
||||
23
service/src/common/constants/midjourney.constant.ts
Normal file
23
service/src/common/constants/midjourney.constant.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 任务状态枚举 1: 等待中 2: 绘制中 3: 绘制完成 4: 绘制失败 5: 绘制超时
|
||||
*/
|
||||
export enum MidjourneyStatusEnum {
|
||||
WAITING = 1,
|
||||
DRAWING = 2,
|
||||
DRAWED = 3,
|
||||
DRAWFAIL = 4,
|
||||
DRAWTIMEOUT = 5,
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘画动作枚举 1: 绘画 2: 放大 3: 变换 4: 图生图 5: 重新生成 6: 无线缩放 7: 单张变化【很大|微小】
|
||||
*/
|
||||
export enum MidjourneyActionEnum {
|
||||
DRAW = 1,
|
||||
UPSCALE = 2,
|
||||
VARIATION = 3,
|
||||
GENERATE = 4,
|
||||
REGENERATE = 5,
|
||||
ZOOM = 6,
|
||||
VARY = 7,
|
||||
}
|
||||
10
service/src/common/constants/status.constant.ts
Normal file
10
service/src/common/constants/status.constant.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export enum VerificationUseStatusEnum {
|
||||
UNUSED,
|
||||
USED,
|
||||
}
|
||||
|
||||
export const ModelsMapCn = {
|
||||
1: '系统内置大模型',
|
||||
2: '百度千帆大模型',
|
||||
3: '清华智谱大模型'
|
||||
}
|
||||
19
service/src/common/constants/user.constant.ts
Normal file
19
service/src/common/constants/user.constant.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* PENDING: 审核中
|
||||
* ACTIVE: 正常状态
|
||||
* LOCKED: 账号锁定
|
||||
* BLACKLISTED: 黑名单账号
|
||||
*/
|
||||
export enum UserStatusEnum {
|
||||
PENDING,
|
||||
ACTIVE,
|
||||
LOCKED,
|
||||
BLACKLISTED,
|
||||
}
|
||||
|
||||
export const UserStatusErrMsg = {
|
||||
[UserStatusEnum.PENDING]: '当前账户未激活,请前往邮箱验证或重新发送验证码!',
|
||||
[UserStatusEnum.ACTIVE]: '当前账户已激活!',
|
||||
[UserStatusEnum.LOCKED]: '当前账户已锁定,请联系管理员解锁!',
|
||||
[UserStatusEnum.BLACKLISTED]: '当前账户已被永久封禁!',
|
||||
};
|
||||
10
service/src/common/constants/verification.constant.ts
Normal file
10
service/src/common/constants/verification.constant.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Registration: 注册账户
|
||||
* PasswordReset: 重置密码
|
||||
* ChangeEmail: 换绑邮箱
|
||||
*/
|
||||
export enum VerificationEnum {
|
||||
Registration,
|
||||
PasswordReset,
|
||||
ChangeEmail,
|
||||
}
|
||||
16
service/src/common/entity/baseEntity.ts
Normal file
16
service/src/common/entity/baseEntity.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Entity, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class BaseEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@CreateDateColumn({ type: 'datetime', length: 0, nullable: false, name: 'createdAt', comment: '创建时间' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'datetime', length: 0, nullable: false, name: 'updatedAt', comment: '更新时间' })
|
||||
updatedAt: Date;
|
||||
|
||||
@DeleteDateColumn({ type: 'datetime', length: 0, nullable: false, name: 'deletedAt', comment: '删除时间' })
|
||||
deletedAt: Date;
|
||||
}
|
||||
19
service/src/common/filters/allExceptions.filter.ts
Normal file
19
service/src/common/filters/allExceptions.filter.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus, Logger } from '@nestjs/common';
|
||||
import { formatDate } from '@/common/utils/date';
|
||||
import { Result } from '@/common/result';
|
||||
|
||||
@Catch()
|
||||
export class AllExceptionsFilter<T> implements ExceptionFilter {
|
||||
catch(exception: HttpException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse();
|
||||
const request = ctx.getRequest();
|
||||
const exceptionRes: any = exception.getResponse() || 'inter server error';
|
||||
const message = exceptionRes?.message ? (Array.isArray(exceptionRes) ? exceptionRes['message'][0] : exceptionRes['message']) : exceptionRes;
|
||||
const statusCode = exception.getStatus() || 400;
|
||||
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
response.status(status);
|
||||
response.header('Content-Type', 'application/json; charset=utf-8');
|
||||
response.send(Result.fail(statusCode, Array.isArray(message) ? message[0] : message));
|
||||
}
|
||||
}
|
||||
23
service/src/common/filters/typeOrmQueryFailed.filter.ts
Normal file
23
service/src/common/filters/typeOrmQueryFailed.filter.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Catch, ArgumentsHost, ExceptionFilter, BadRequestException } from '@nestjs/common';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
|
||||
@Catch(QueryFailedError)
|
||||
export class TypeOrmQueryFailedFilter implements ExceptionFilter {
|
||||
catch(exception: QueryFailedError, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse();
|
||||
const request = ctx.getRequest();
|
||||
if ((exception as any).code === 'ER_DUP_ENTRY') {
|
||||
throw new BadRequestException('该记录已经存在,请勿重复添加!');
|
||||
} else {
|
||||
console.log('other query error');
|
||||
}
|
||||
|
||||
response.status(500).json({
|
||||
statusCode: 500,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
message: `Database query failed: ${exception.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
9
service/src/common/guards/roles/roles.guard.ts
Normal file
9
service/src/common/guards/roles/roles.guard.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
13
service/src/common/interceptors/abort.interceptor.ts
Normal file
13
service/src/common/interceptors/abort.interceptor.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { AbortController } from 'abort-controller';
|
||||
import { Observable } from 'rxjs';
|
||||
@Injectable()
|
||||
export class AbortInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
const abortController = new AbortController();
|
||||
request.abortController = abortController;
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
28
service/src/common/interceptors/transform.interceptor.ts
Normal file
28
service/src/common/interceptors/transform.interceptor.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { CallHandler, ExecutionContext, Injectable, NestInterceptor, HttpException, HttpCode, HttpStatus, Logger } from '@nestjs/common';
|
||||
import { Observable, catchError, throwError } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Result } from '@/common/result';
|
||||
|
||||
@Injectable()
|
||||
export class TransformInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): any {
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
const request = context.switchToHttp().getRequest();
|
||||
response.statusCode = 200;
|
||||
/* 微信类支付类通知接口需要原样输出 */
|
||||
if (request.path.includes('notify')) {
|
||||
return data;
|
||||
}
|
||||
const message = response.status < 400 ? null : response.statusText;
|
||||
return Result.success(data, message);
|
||||
}),
|
||||
catchError((error) => {
|
||||
const statusCode = error.status || 500;
|
||||
const message = (error.response || 'Internal server error') as string;
|
||||
return throwError(new HttpException(message, statusCode));
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
13
service/src/common/middleware/xml.middleware.ts
Normal file
13
service/src/common/middleware/xml.middleware.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import * as bodyParser from 'body-parser';
|
||||
|
||||
const bodyParserMiddleware = bodyParser.text({
|
||||
type: 'application/xml',
|
||||
});
|
||||
|
||||
@Injectable()
|
||||
export class XMLMiddleware implements NestMiddleware {
|
||||
use(req: any, res: any, next: () => void) {
|
||||
bodyParserMiddleware(req, res, next);
|
||||
}
|
||||
}
|
||||
21
service/src/common/result/index.ts
Normal file
21
service/src/common/result/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export class Result<T> {
|
||||
code: number;
|
||||
data?: T;
|
||||
success: boolean;
|
||||
message?: string;
|
||||
|
||||
constructor(code: number, success: boolean, data?: T, message?: string) {
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
static success<T>(data?: T, message = '请求成功'): Result<T> {
|
||||
return new Result<T>(200, true, data, message);
|
||||
}
|
||||
|
||||
static fail<T>(code: number, message = '请求失败', data?: T): Result<T> {
|
||||
return new Result<T>(code, false, data, message);
|
||||
}
|
||||
}
|
||||
14
service/src/common/swagger/index.ts
Normal file
14
service/src/common/swagger/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { PORT, SWAGGERPREFIX, APIPREFIX } from '@/config/main';
|
||||
|
||||
const swaggerOptions = new DocumentBuilder()
|
||||
.setTitle('Nine Team api document')
|
||||
.setDescription('Nine Team api document')
|
||||
.setVersion('1.0.0')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
|
||||
export function createSwagger(app) {
|
||||
const document = SwaggerModule.createDocument(app, swaggerOptions);
|
||||
SwaggerModule.setup('/nineai/swagger/docs', app, document);
|
||||
}
|
||||
24
service/src/common/utils/base.ts
Normal file
24
service/src/common/utils/base.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
const encryptionKey = 'bf3c116f2470cb4che9071240917c171';
|
||||
const initializationVector = '518363fh72eec1v4';
|
||||
const algorithm = 'aes-256-cbc';
|
||||
|
||||
export function encrypt(text: string): string {
|
||||
const cipher = crypto.createCipheriv(algorithm, encryptionKey, initializationVector);
|
||||
let encrypted = cipher.update(text, 'utf8', 'base64');
|
||||
encrypted += cipher.final('base64');
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
export function decrypt(text: string): string {
|
||||
try {
|
||||
const decipher = crypto.createDecipheriv(algorithm, encryptionKey, initializationVector);
|
||||
let decrypted = decipher.update(text, 'base64', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
return decrypted;
|
||||
} catch (error) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
25
service/src/common/utils/compileNetwork.ts
Normal file
25
service/src/common/utils/compileNetwork.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import axios from 'axios';
|
||||
|
||||
function formatSearchData(searchData, question) {
|
||||
const formatStr = searchData.map(({ title, body, href }) => `'${title}' : ${body} ;`).join('\n\n');
|
||||
// const formatStr = searchData.map(({ title, body, href }) => `'${title}' : ${body} ; (${href})`).join('\n\n');
|
||||
const instructions =
|
||||
'Instructions: Reply to me in the language of my request or question above. Give a comprehensive answer to the question or request I have made above. Below are some results from a web search. Use the following results to summarize the answers \n\n';
|
||||
return `${question}\n\n${instructions}\n${formatStr}`;
|
||||
}
|
||||
|
||||
export async function compileNetwork(question: string, limit = 7) {
|
||||
let searchData = [];
|
||||
try {
|
||||
const responseData = await axios.get(`https://s0.awsl.app/search?q=${question}&max_results=${limit}`);
|
||||
searchData = responseData.data;
|
||||
} catch (error) {
|
||||
console.log('error: ', error);
|
||||
searchData = [];
|
||||
}
|
||||
if (searchData.length === 0) {
|
||||
return question;
|
||||
} else {
|
||||
return formatSearchData(searchData, question);
|
||||
}
|
||||
}
|
||||
5
service/src/common/utils/createOrderId.ts
Normal file
5
service/src/common/utils/createOrderId.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { v1 as uuidv1 } from 'uuid';
|
||||
|
||||
export function createOrderId(): string {
|
||||
return uuidv1().toString().replace(/-/g, '');
|
||||
}
|
||||
5
service/src/common/utils/createRandomCode.ts
Normal file
5
service/src/common/utils/createRandomCode.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function createRandomCode(): number {
|
||||
const min = 100000;
|
||||
const max = 999999;
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
12
service/src/common/utils/createRandomInviteCode.ts
Normal file
12
service/src/common/utils/createRandomInviteCode.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export function generateRandomString(): string {
|
||||
const length = 10;
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex: number = Math.floor(Math.random() * characters.length);
|
||||
result += characters.charAt(randomIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
8
service/src/common/utils/createRandomNonceStr.ts
Normal file
8
service/src/common/utils/createRandomNonceStr.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function createRandomNonceStr(len: number): string {
|
||||
const data = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let str = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
str += data.charAt(parseInt((Math.random() * data.length).toFixed(0), 10));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
6
service/src/common/utils/createRandomUid.ts
Normal file
6
service/src/common/utils/createRandomUid.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Guid } from 'guid-typescript';
|
||||
|
||||
export function createRandomUid(): string {
|
||||
const uuid = Guid.create();
|
||||
return uuid.toString().substr(0, 10).replace('-', '');
|
||||
}
|
||||
41
service/src/common/utils/date.ts
Normal file
41
service/src/common/utils/date.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import * as a from 'dayjs/plugin/utc';
|
||||
import * as b from 'dayjs/plugin/timezone';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
dayjs.extend(a);
|
||||
dayjs.extend(b);
|
||||
dayjs.tz.setDefault('Asia/Shanghai');
|
||||
|
||||
export function formatDate(date: string | number | Date, format = 'YYYY-MM-DD HH:mm:ss'): string {
|
||||
return dayjs(date).format(format);
|
||||
}
|
||||
|
||||
export function formatCreateOrUpdateDate(input, format = 'YYYY-MM-DD HH:mm:ss'): any[] {
|
||||
if (Array.isArray(input)) {
|
||||
return input.map((t: any) => {
|
||||
t.createdAt = t?.createdAt ? dayjs(t.createdAt).format(format) : dayjs().format(format);
|
||||
t.updatedAt = t?.updatedAt ? dayjs(t.updatedAt).format(format) : dayjs().format(format);
|
||||
return t;
|
||||
});
|
||||
} else {
|
||||
let obj: any = {}
|
||||
try {
|
||||
obj = JSON.parse(JSON.stringify(input));
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
obj?.createdAt && (obj.createdAt = dayjs(obj.createdAt).format(format));
|
||||
obj?.updatedAt && (obj.updatedAt = dayjs(obj.updatedAt).format(format));
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
export function isExpired(createdAt: Date, days: number): boolean {
|
||||
const expireDate = new Date(createdAt.getTime() + days * 24 * 60 * 60 * 1000);
|
||||
const now = new Date();
|
||||
return now > expireDate;
|
||||
}
|
||||
|
||||
export default dayjs;
|
||||
12
service/src/common/utils/encrypt.ts
Normal file
12
service/src/common/utils/encrypt.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export function atob(str) {
|
||||
return Buffer.from(str, 'base64').toString('utf-8');
|
||||
}
|
||||
|
||||
|
||||
export const copyRightMsg = [
|
||||
'agxoTstMY8m+DJO89Iwy4zqcFTqlcj/Fa/erMTvn0IexetXaDttr4K/BN2+RbtfouXOeFjPDYnxOfQ+IIpuJ3PmtyHAzmlGFls/HvBDeh6EXAQ3waALbvK9Ue96soAb5/3Tv6VuZE7npISqXiYhI6Vqx4yDVYf6vUUkEO9jvVotWQkLOLkr6M/guLK6sik/ZOgHvSlDYKAv79NFJJ0Tt0WkH2SyN8l+woMiWVTOKkdE=',
|
||||
'nXdXi8UU7J5av2eDOFjxQWlZDa+3bdASE4UwpqT6B11XSCweKKuzHxmFO2wx45iVlib/V0tt+NbEcOQZtzEWKqHsREkwEb5aqVCUl2Kj4nJeEFId2iyvY6MWEV1lHtCY+htpJoyqwQJc7yeNfpTl2SLBubWk77p4AHei1QFEs1rpOOwyE79lF0RqzY/Cpzhs',
|
||||
'VjVCGib1VFp7hNynpKGQPUrX+ishpxi2u5a4txHXzk2nyUP1NZfIomEDmGhDTQ7VRJLox+8urtVG1CBBSct1v+4OA2ucAcDUFoy1H1Kl1z+dndVcNU6gz5YGnDppsxY8uGFAVGsWrDl2DIOKxk7kMURaRiQCXCHRF/3sLGyIEmE6KL9Q4kDInB6vuzBScxupFShMXTq2XrOhwRgn2elcig==',
|
||||
'ZPcz1IaPDMGI3Yn9sm4QOT0qCZo7yZbJl4/c2RTrhUKINkjGB5yb0yN5vAnLtt/o8cmpoOoH3PUSOOWQa9aKD86NWK+1r8wBOVjwXZOpp2gbB1ZJLbWvjRbENvEJxVsLROXnpNDqUXVGxFMaIt+gmEi3Rp0thqC1soXUpvM1zqU4+LkQmunR7UytvzwXEmXBlIfPwz5hv+n/lxDsw526KWixC3jLLpeijw5433Zh7cI=',
|
||||
'YPo1HNzS6p6190ku4f1PQENUBa/ip+v+6sPuQXVyAn3axo6SLKQBszNr3PAW2EzWhZLy2o+nBgr3o3IOy9OgNit1JHrCklpVp172wbGDKh8sB8HCXyJoRv3BaZVY5UhyhpV5K+4nPoM2RUwvIGONUGFPQfPQv9N8MS8UCL7UnWYcVLzxWo0ZDg+UXFRr7NhXKu7KQ7e1+Wiqm0qE+olfDVowi4pGDRGrYL154wEEJUo='
|
||||
]
|
||||
6
service/src/common/utils/generateCrami.ts
Normal file
6
service/src/common/utils/generateCrami.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export function generateCramiCode(): string {
|
||||
const code = uuidv4().replace(/-/g, '').slice(0, 16);
|
||||
return code;
|
||||
}
|
||||
51
service/src/common/utils/getClientIp.ts
Normal file
51
service/src/common/utils/getClientIp.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Request } from 'express';
|
||||
|
||||
export function getClientIp(request: Request): string {
|
||||
let ipAddress = '';
|
||||
|
||||
// 预定义的一组请求头列表,按优先级排序
|
||||
const headerList = [
|
||||
'X-Client-IP',
|
||||
'X-Real-IP',
|
||||
'X-Forwarded-For',
|
||||
'CF-Connecting-IP',
|
||||
'True-Client-IP',
|
||||
'X-Cluster-Client-IP',
|
||||
'Proxy-Client-IP',
|
||||
'WL-Proxy-Client-IP',
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
];
|
||||
|
||||
// 尝试从预定义的请求头列表中提取客户端的真实 IP 地址
|
||||
for (const header of headerList) {
|
||||
const value = request.headers[header];
|
||||
if (value && typeof value === 'string') {
|
||||
const ips = value.split(',');
|
||||
// 取最左侧的 IP 地址作为客户端的真实 IP 地址
|
||||
ipAddress = ips[0].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果无法从请求头中获取到客户端的真实 IP 地址,则回退到使用 connection.remoteAddress 属性
|
||||
if (!ipAddress) {
|
||||
ipAddress = request.connection.remoteAddress || '';
|
||||
}
|
||||
|
||||
// 对获取到的 IP 地址进行格式化和过滤操作
|
||||
if (ipAddress && ipAddress.includes('::')) {
|
||||
const isLocal = /^(::1|fe80(:1)?::1(%.*)?)$/i.test(ipAddress);
|
||||
if (isLocal) {
|
||||
ipAddress = '';
|
||||
} else if (ipAddress.includes('::ffff:')) {
|
||||
ipAddress = ipAddress.split(':').pop() || '';
|
||||
}
|
||||
}
|
||||
|
||||
// 如果获取到的 IP 地址不符合格式要求,则设置为空字符串
|
||||
if (!ipAddress || !/\d+\.\d+\.\d+\.\d+/.test(ipAddress)) {
|
||||
ipAddress = '';
|
||||
}
|
||||
return ipAddress;
|
||||
}
|
||||
14
service/src/common/utils/getDiffArray.ts
Normal file
14
service/src/common/utils/getDiffArray.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export function getDiffArray(aLength: number, bLength: number, str: string): string[] {
|
||||
const a = Array.from({ length: aLength }, (_, i) => i + 1);
|
||||
const b = Array.from({ length: bLength }, (_, i) => i + 1);
|
||||
|
||||
const diffArray: string[] = [];
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (!b.includes(a[i])) {
|
||||
diffArray.push(`${str}${a[i]}`);
|
||||
}
|
||||
}
|
||||
|
||||
return diffArray;
|
||||
}
|
||||
4
service/src/common/utils/getRandomItem.ts
Normal file
4
service/src/common/utils/getRandomItem.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export function getRandomItem<T>(array: T[]): T {
|
||||
const randomIndex = Math.floor(Math.random() * array.length);
|
||||
return array[randomIndex];
|
||||
}
|
||||
7
service/src/common/utils/getRandomItemFromArray.ts
Normal file
7
service/src/common/utils/getRandomItemFromArray.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function getRandomItemFromArray<T>(array: T[]): T | null {
|
||||
if (array.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const randomIndex = Math.floor(Math.random() * array.length);
|
||||
return array[randomIndex];
|
||||
}
|
||||
10
service/src/common/utils/hideString.ts
Normal file
10
service/src/common/utils/hideString.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export function hideString(input: string, str?: string): string {
|
||||
const length = input.length;
|
||||
const start = input.slice(0, (length - 10) / 2);
|
||||
const end = input.slice((length + 10) / 2, length);
|
||||
const hidden = '*'.repeat(10);
|
||||
if (str) {
|
||||
return `**********${str}**********`;
|
||||
}
|
||||
return `${start}${hidden}${end}`;
|
||||
}
|
||||
22
service/src/common/utils/index.ts
Normal file
22
service/src/common/utils/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export * from './date';
|
||||
export * from './createRandomCode';
|
||||
export * from './tools';
|
||||
export * from './createRandomInviteCode';
|
||||
export * from './maskEmail';
|
||||
export * from './createRandomUid';
|
||||
export * from './generateCrami';
|
||||
export * from './base';
|
||||
export * from './hideString';
|
||||
export * from './getDiffArray';
|
||||
export * from './getRandomItem';
|
||||
export * from './getClientIp';
|
||||
export * from './maskIpAddress';
|
||||
export * from './maskCrami';
|
||||
export * from './selectKeyWithWeight';
|
||||
export * from './createOrderId';
|
||||
export * from './createRandomNonceStr';
|
||||
export * from './utcformatTime';
|
||||
export * from './removeSpecialCharacters';
|
||||
export * from './encrypt';
|
||||
export * from './compileNetwork';
|
||||
export * from './getRandomItemFromArray'
|
||||
8
service/src/common/utils/maskCrami.ts
Normal file
8
service/src/common/utils/maskCrami.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function maskCrami(str: string): string {
|
||||
if (str.length !== 16) {
|
||||
throw new Error('Invalid input');
|
||||
}
|
||||
|
||||
const masked = str.substring(0, 6) + '****' + str.substring(10);
|
||||
return masked;
|
||||
}
|
||||
11
service/src/common/utils/maskEmail.ts
Normal file
11
service/src/common/utils/maskEmail.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export function maskEmail(email: string): string {
|
||||
if (!email) return '';
|
||||
const atIndex = email.indexOf('@');
|
||||
if (atIndex <= 1) {
|
||||
return email;
|
||||
}
|
||||
const firstPart = email.substring(0, atIndex - 1);
|
||||
const lastPart = email.substring(atIndex);
|
||||
const maskedPart = '*'.repeat(firstPart.length - 1);
|
||||
return `${firstPart.charAt(0)}${maskedPart}${email.charAt(atIndex - 1)}${lastPart}`;
|
||||
}
|
||||
6
service/src/common/utils/maskIpAddress.ts
Normal file
6
service/src/common/utils/maskIpAddress.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function maskIpAddress(ipAddress: string): string {
|
||||
if (!ipAddress) return '';
|
||||
const ipArray = ipAddress.split('.');
|
||||
ipArray[2] = '***';
|
||||
return ipArray.join('.');
|
||||
}
|
||||
3
service/src/common/utils/removeSpecialCharacters.ts
Normal file
3
service/src/common/utils/removeSpecialCharacters.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function removeSpecialCharacters(inputString) {
|
||||
return inputString.replace(/[^\w\s-]/g, '');
|
||||
}
|
||||
87
service/src/common/utils/selectKeyWithWeight.ts
Normal file
87
service/src/common/utils/selectKeyWithWeight.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
// export function selectKeyWithWeight(keys) {
|
||||
// // 创建两个数组用于存储每个键值及其对应的概率分布
|
||||
// const values = [];
|
||||
// const probabilities = [];
|
||||
|
||||
// // 获取所有 keys 的总权重
|
||||
// const totalWeight = keys.reduce((prev, curr) => prev + curr.weight, 0);
|
||||
|
||||
// // 计算每个键值所占的概率,并将其放入对应的数组中
|
||||
// for (let i = 0; i < keys.length; i++) {
|
||||
// const probability = keys[i].weight / totalWeight;
|
||||
// probabilities.push(probability);
|
||||
// values.push(i);
|
||||
// }
|
||||
|
||||
// // 创建两个辅助数组,用于记录各个键值的别名(alias)和概率分布(prob)
|
||||
// const alias = new Array(keys.length).fill(0);
|
||||
// const prob = new Array(keys.length).fill(0);
|
||||
|
||||
// // 创建两个栈,分别用于存储大于等于均值和小于均值的键值
|
||||
// const small = [];
|
||||
// const large = [];
|
||||
|
||||
// // 初始化栈以及 prob 和 alias 数组
|
||||
// for (let i = 0; i < keys.length; i++) {
|
||||
// if (probabilities[i] < 1) {
|
||||
// small.push(i);
|
||||
// } else {
|
||||
// large.push(i);
|
||||
// }
|
||||
// prob[i] = probabilities[i] * keys.length;
|
||||
// }
|
||||
|
||||
// // 循环填充 alias 和 prob 数组
|
||||
// while (small.length > 0 && large.length > 0) {
|
||||
// const smallIndex = small.pop();
|
||||
// const largeIndex = large.pop();
|
||||
|
||||
// alias[smallIndex] = largeIndex;
|
||||
// prob[largeIndex] = prob[largeIndex] + prob[smallIndex] - 1;
|
||||
|
||||
// if (prob[largeIndex] < 1) {
|
||||
// small.push(largeIndex);
|
||||
// } else {
|
||||
// large.push(largeIndex);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 随机生成一个 [0, keys.length) 范围内的整数
|
||||
// const rand = Math.floor(Math.random() * keys.length);
|
||||
|
||||
// // 根据随机值和对应的别名和概率分布数组,返回选中的键值
|
||||
// if (Math.random() < prob[rand]) {
|
||||
// return keys[rand];
|
||||
// } else {
|
||||
// return keys[alias[rand]];
|
||||
// }
|
||||
// }
|
||||
|
||||
export interface KeyItem {
|
||||
id: number;
|
||||
key: string;
|
||||
weight: number;
|
||||
model: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据概率按权重随机选择一项
|
||||
*
|
||||
* @param data 包含id、key和weight字段的Item数组
|
||||
* @returns 随机选择的一项
|
||||
*/
|
||||
export function selectKeyWithWeight(data: KeyItem[]): KeyItem | undefined {
|
||||
if (data.length === 0) return undefined;
|
||||
|
||||
const totalWeight = data.reduce((sum, item) => sum + item.weight, 0);
|
||||
let randomWeight = Math.random() * totalWeight;
|
||||
|
||||
for (const item of data) {
|
||||
randomWeight -= item.weight;
|
||||
if (randomWeight < 0) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return data[data.length - 1];
|
||||
}
|
||||
6
service/src/common/utils/tools.ts
Normal file
6
service/src/common/utils/tools.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function isNotEmptyString(value: any): boolean {
|
||||
return typeof value === 'string' && value.length > 0;
|
||||
}
|
||||
|
||||
// === await eval('import("module")');
|
||||
export const importDynamic = new Function('modulePath', 'return import(modulePath)');
|
||||
14
service/src/common/utils/utcformatTime.ts
Normal file
14
service/src/common/utils/utcformatTime.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export function utcToShanghaiTime(utcTime: string, format = 'YYYY/MM/DD hh:mm:ss'): string {
|
||||
const date = new Date(utcTime);
|
||||
const shanghaiTime = date.getTime() + 8 * 60 * 60 * 1000;
|
||||
const shanghaiDate = new Date(shanghaiTime);
|
||||
|
||||
let result = format.replace('YYYY', shanghaiDate.getFullYear().toString());
|
||||
result = result.replace('MM', `0${shanghaiDate.getMonth() + 1}`.slice(-2));
|
||||
result = result.replace('DD', `0${shanghaiDate.getDate()}`.slice(-2));
|
||||
result = result.replace('hh', `0${shanghaiDate.getHours()}`.slice(-2));
|
||||
result = result.replace('mm', `0${shanghaiDate.getMinutes()}`.slice(-2));
|
||||
result = result.replace('ss', `0${shanghaiDate.getSeconds()}`.slice(-2));
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user