mirror of
https://github.com/vastxie/99AI.git
synced 2026-04-23 10:44:25 +08:00
v4.3.0
This commit is contained in:
23
service/src/common/utils/base.ts
Normal file
23
service/src/common/utils/base.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
20
service/src/common/utils/convertUrlToBase64.ts
Normal file
20
service/src/common/utils/convertUrlToBase64.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import axios from 'axios';
|
||||
/**
|
||||
* 将URL转换为Base64
|
||||
* @param url - 需要转换的URL
|
||||
* @returns 转换后的Base64字符串
|
||||
*/
|
||||
export async function convertUrlToBase64(url: string): Promise<string> {
|
||||
try {
|
||||
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
||||
const buffer = Buffer.from(response.data, 'binary'); // 获取图片的二进制数据
|
||||
const base64Data = `data:${response.headers['content-type']};base64,${buffer.toString(
|
||||
'base64',
|
||||
)}`;
|
||||
return base64Data;
|
||||
} catch (error) {
|
||||
Logger.error('下载图片失败', error);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
24
service/src/common/utils/correctApiBaseUrl.ts
Normal file
24
service/src/common/utils/correctApiBaseUrl.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 规范化API基础URL
|
||||
* @param baseUrl - 需要规范化的API基础URL
|
||||
* @returns 规范化后的URL字符串
|
||||
*/
|
||||
export async function correctApiBaseUrl(baseUrl: string) {
|
||||
if (!baseUrl) return '';
|
||||
|
||||
// 去除两端空格
|
||||
let url = baseUrl.trim();
|
||||
|
||||
// 如果URL以斜杠'/'结尾,则移除这个斜杠
|
||||
if (url.endsWith('/')) {
|
||||
url = url.slice(0, -1);
|
||||
}
|
||||
|
||||
// 检查URL是否已包含任何版本标记,包括常见的模式如/v1, /v1beta, /v1alpha等
|
||||
if (!/\/v\d+(?:beta|alpha)?/.test(url)) {
|
||||
// 如果不包含任何版本号,添加 /v1
|
||||
return `${url}/v1`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
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('-', '');
|
||||
}
|
||||
39
service/src/common/utils/date.ts
Normal file
39
service/src/common/utils/date.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import * as b from 'dayjs/plugin/timezone';
|
||||
import * as a from 'dayjs/plugin/utc';
|
||||
|
||||
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;
|
||||
420
service/src/common/utils/doubaoSignature.ts
Normal file
420
service/src/common/utils/doubaoSignature.ts
Normal file
@@ -0,0 +1,420 @@
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
/**
|
||||
* 豆包签名配置接口
|
||||
*/
|
||||
export interface DoubaoSignatureConfig {
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
region?: string;
|
||||
service?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名结果接口
|
||||
*/
|
||||
export interface SignatureResult {
|
||||
authorization: string;
|
||||
xDate: string;
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 豆包(火山引擎)API签名工具类
|
||||
* 完全按照官方文档的标准HMAC-SHA256签名算法实现
|
||||
*/
|
||||
export class DoubaoSignature {
|
||||
private accessKeyId: string;
|
||||
private secretAccessKey: string;
|
||||
private region: string;
|
||||
private service: string;
|
||||
|
||||
constructor(
|
||||
accessKeyId: string,
|
||||
secretAccessKey: string,
|
||||
region: string = 'cn-north-1',
|
||||
service: string = 'cv',
|
||||
) {
|
||||
this.accessKeyId = accessKeyId;
|
||||
this.secretAccessKey = secretAccessKey;
|
||||
this.region = region;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成UTC时间字符串
|
||||
* @returns ISO 8601格式的UTC时间字符串 (YYYYMMDD'T'HHMMSS'Z')
|
||||
*/
|
||||
private generateTimestamp(): string {
|
||||
const now = new Date();
|
||||
return now
|
||||
.toISOString()
|
||||
.replace(/[-:]/g, '')
|
||||
.replace(/\.\d{3}/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日期字符串 (YYYYMMDD)
|
||||
* @param timestamp 时间戳
|
||||
* @returns 日期字符串
|
||||
*/
|
||||
private getDateString(timestamp: string): string {
|
||||
return timestamp.substring(0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* URI编码(按照火山引擎标准)
|
||||
* @param str 要编码的字符串
|
||||
* @returns 编码后的字符串
|
||||
*/
|
||||
private uriEscape(str: string): string {
|
||||
return encodeURIComponent(str).replace(
|
||||
/[!'()*]/g,
|
||||
c => '%' + c.charCodeAt(0).toString(16).toUpperCase(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算SHA256哈希
|
||||
* @param data 数据
|
||||
* @returns 十六进制哈希值
|
||||
*/
|
||||
private sha256(data: string): string {
|
||||
return crypto.createHash('sha256').update(data, 'utf8').digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算HMAC-SHA256
|
||||
* @param key 密钥
|
||||
* @param data 数据
|
||||
* @returns Buffer
|
||||
*/
|
||||
private hmacSha256(key: string | Buffer, data: string): Buffer {
|
||||
return crypto
|
||||
.createHmac('sha256', key as any)
|
||||
.update(data, 'utf8')
|
||||
.digest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建规范请求字符串(按照官方示例优化)
|
||||
* @param method HTTP方法
|
||||
* @param uri 请求URI
|
||||
* @param queryParams 查询参数
|
||||
* @param headers 请求头
|
||||
* @param payload 请求体
|
||||
* @returns 规范请求字符串
|
||||
*/
|
||||
private buildCanonicalRequest(
|
||||
method: string,
|
||||
uri: string,
|
||||
queryParams: Record<string, any>,
|
||||
headers: Record<string, string>,
|
||||
payload: string,
|
||||
): { canonicalRequest: string; signedHeaders: string } {
|
||||
// 1. HTTP方法
|
||||
const httpMethod = method.toUpperCase();
|
||||
|
||||
// 2. 规范URI
|
||||
const canonicalUri = uri || '/';
|
||||
|
||||
// 3. 规范查询字符串(按照官方示例格式)
|
||||
const sortedParams = Object.keys(queryParams)
|
||||
.sort()
|
||||
.map(key => `${this.uriEscape(key)}=${this.uriEscape(queryParams[key])}`)
|
||||
.join('&');
|
||||
|
||||
// 4. 计算请求体哈希(用于 x-content-sha256 头部)
|
||||
const payloadHash = this.sha256(payload);
|
||||
|
||||
// 5. 按照官方示例,只包含特定的头部进行签名
|
||||
// 官方示例只包含: host, x-content-sha256, x-date
|
||||
const signedHeadersMap: Record<string, string> = {};
|
||||
|
||||
// 添加必需的头部
|
||||
if (headers['host'] || headers['Host']) {
|
||||
signedHeadersMap['host'] = (headers['host'] || headers['Host']).trim();
|
||||
}
|
||||
|
||||
if (headers['x-date']) {
|
||||
signedHeadersMap['x-date'] = headers['x-date'].trim();
|
||||
}
|
||||
|
||||
// 添加 x-content-sha256
|
||||
signedHeadersMap['x-content-sha256'] = payloadHash;
|
||||
|
||||
// 6. 按字母顺序排序头部键
|
||||
const sortedHeaderKeys = Object.keys(signedHeadersMap).sort();
|
||||
const canonicalHeaders =
|
||||
sortedHeaderKeys.map(key => `${key}:${signedHeadersMap[key]}`).join('\n') + '\n';
|
||||
|
||||
// 7. 签名头部
|
||||
const signedHeaders = sortedHeaderKeys.join(';');
|
||||
|
||||
// 8. 构建规范请求(按照官方格式)
|
||||
const canonicalRequest = [
|
||||
httpMethod,
|
||||
canonicalUri,
|
||||
sortedParams,
|
||||
canonicalHeaders,
|
||||
signedHeaders,
|
||||
payloadHash,
|
||||
].join('\n');
|
||||
|
||||
// 规范请求构建完成
|
||||
|
||||
return { canonicalRequest, signedHeaders };
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建待签字符串
|
||||
* @param timestamp 时间戳
|
||||
* @param canonicalRequest 规范请求
|
||||
* @returns 待签字符串
|
||||
*/
|
||||
private buildStringToSign(timestamp: string, canonicalRequest: string): string {
|
||||
const algorithm = 'HMAC-SHA256';
|
||||
const requestDate = timestamp;
|
||||
const credentialScope = `${this.getDateString(timestamp)}/${this.region}/${
|
||||
this.service
|
||||
}/request`;
|
||||
const hashedCanonicalRequest = this.sha256(canonicalRequest);
|
||||
|
||||
const stringToSign = [algorithm, requestDate, credentialScope, hashedCanonicalRequest].join(
|
||||
'\n',
|
||||
);
|
||||
|
||||
// 待签字符串构建完成
|
||||
|
||||
return stringToSign;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算签名密钥
|
||||
* @param timestamp 时间戳
|
||||
* @returns 签名密钥
|
||||
*/
|
||||
private calculateSigningKey(timestamp: string): Buffer {
|
||||
const dateString = this.getDateString(timestamp);
|
||||
|
||||
// kSecret = Your Secret Access Key
|
||||
const kSecret = this.secretAccessKey;
|
||||
|
||||
// kDate = HMAC(kSecret, Date)
|
||||
const kDate = this.hmacSha256(kSecret, dateString);
|
||||
|
||||
// kRegion = HMAC(kDate, Region)
|
||||
const kRegion = this.hmacSha256(kDate, this.region);
|
||||
|
||||
// kService = HMAC(kRegion, Service)
|
||||
const kService = this.hmacSha256(kRegion, this.service);
|
||||
|
||||
// kSigning = HMAC(kService, "request")
|
||||
const kSigning = this.hmacSha256(kService, 'request');
|
||||
|
||||
// 签名密钥计算完成
|
||||
|
||||
return kSigning;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算签名
|
||||
* @param signingKey 签名密钥
|
||||
* @param stringToSign 待签字符串
|
||||
* @returns 签名
|
||||
*/
|
||||
private calculateSignature(signingKey: Buffer, stringToSign: string): string {
|
||||
const signature = crypto
|
||||
.createHmac('sha256', signingKey as any)
|
||||
.update(stringToSign, 'utf8')
|
||||
.digest('hex');
|
||||
// 签名计算完成
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Authorization头部签名(按照官方示例优化)
|
||||
* @param method HTTP方法
|
||||
* @param uri 请求URI
|
||||
* @param queryParams 查询参数
|
||||
* @param headers 请求头(不包含Authorization)
|
||||
* @param payload 请求体
|
||||
* @param timestamp 可选的时间戳,不提供则自动生成
|
||||
* @returns 包含Authorization头部、X-Date和完整头部的对象
|
||||
*/
|
||||
public generateHeaderSignature(
|
||||
method: string,
|
||||
uri: string,
|
||||
queryParams: Record<string, any> = {},
|
||||
headers: Record<string, string> = {},
|
||||
payload: string = '',
|
||||
timestamp?: string,
|
||||
): { authorization: string; xDate: string; headers: Record<string, string> } {
|
||||
const xDate = timestamp || headers['x-date'] || this.generateTimestamp();
|
||||
|
||||
// 确保headers中包含必要的头部
|
||||
const allHeaders = {
|
||||
...headers,
|
||||
'x-date': xDate,
|
||||
};
|
||||
|
||||
// 如果没有host头部,添加默认值
|
||||
if (!allHeaders['host'] && !allHeaders['Host']) {
|
||||
allHeaders['host'] = 'visual.volcengineapi.com';
|
||||
}
|
||||
|
||||
// 豆包签名请求参数准备完成
|
||||
|
||||
// 1. 构建规范请求
|
||||
const { canonicalRequest, signedHeaders } = this.buildCanonicalRequest(
|
||||
method,
|
||||
uri,
|
||||
queryParams,
|
||||
allHeaders,
|
||||
payload,
|
||||
);
|
||||
|
||||
// 2. 构建待签字符串
|
||||
const stringToSign = this.buildStringToSign(xDate, canonicalRequest);
|
||||
|
||||
// 3. 计算签名密钥
|
||||
const signingKey = this.calculateSigningKey(xDate);
|
||||
|
||||
// 4. 计算签名
|
||||
const signature = this.calculateSignature(signingKey, stringToSign);
|
||||
|
||||
// 5. 构建Authorization头部
|
||||
const credentialScope = `${this.getDateString(xDate)}/${this.region}/${this.service}/request`;
|
||||
const authorization = `HMAC-SHA256 Credential=${this.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||
|
||||
// 6. 构建完整的请求头部(按照官方示例格式)
|
||||
const payloadHash = this.sha256(payload);
|
||||
const finalHeaders = {
|
||||
Authorization: authorization,
|
||||
'Content-Type': 'application/json',
|
||||
Host: allHeaders['host'] || allHeaders['Host'],
|
||||
'X-Content-Sha256': payloadHash,
|
||||
'X-Date': xDate,
|
||||
// 不包含小写的 host,避免重复
|
||||
};
|
||||
|
||||
// 豆包签名Authorization头部构建完成
|
||||
// 豆包签名最终头部构建完成
|
||||
|
||||
return {
|
||||
authorization,
|
||||
xDate,
|
||||
headers: finalHeaders,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证签名是否有效
|
||||
* @param signature 要验证的签名
|
||||
* @param method HTTP方法
|
||||
* @param uri 请求URI
|
||||
* @param queryParams 查询参数
|
||||
* @param headers 请求头
|
||||
* @param payload 请求体
|
||||
* @param timestamp 时间戳
|
||||
* @returns 是否有效
|
||||
*/
|
||||
public verifySignature(
|
||||
signature: string,
|
||||
method: string,
|
||||
uri: string,
|
||||
queryParams: Record<string, any> = {},
|
||||
headers: Record<string, string> = {},
|
||||
payload: string = '',
|
||||
timestamp: string,
|
||||
): boolean {
|
||||
try {
|
||||
const { authorization } = this.generateHeaderSignature(
|
||||
method,
|
||||
uri,
|
||||
queryParams,
|
||||
headers,
|
||||
payload,
|
||||
timestamp,
|
||||
);
|
||||
const expectedSignature = authorization.split('Signature=')[1];
|
||||
return signature === expectedSignature;
|
||||
} catch (error) {
|
||||
console.error(`[DEBUG] 豆包签名 - 验证签名失败:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建豆包签名实例的工厂函数
|
||||
* @param accessKeyId 访问密钥ID
|
||||
* @param secretAccessKey 秘密访问密钥
|
||||
* @param region 区域,默认cn-north-1
|
||||
* @param service 服务名,默认cv
|
||||
* @returns DoubaoSignature实例
|
||||
*/
|
||||
export function createDoubaoSignature(
|
||||
accessKeyId: string,
|
||||
secretAccessKey: string,
|
||||
region: string = 'cn-north-1',
|
||||
service: string = 'cv',
|
||||
): DoubaoSignature {
|
||||
return new DoubaoSignature(accessKeyId, secretAccessKey, region, service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷的签名生成函数
|
||||
* @param config 签名配置
|
||||
* @param request 请求信息
|
||||
* @returns 签名结果
|
||||
*/
|
||||
export function generateDoubaoSignature(
|
||||
config: DoubaoSignatureConfig,
|
||||
request: {
|
||||
method: string;
|
||||
uri: string;
|
||||
queryParams?: Record<string, any>;
|
||||
headers?: Record<string, string>;
|
||||
payload?: string;
|
||||
host?: string;
|
||||
},
|
||||
): SignatureResult {
|
||||
const signer = createDoubaoSignature(
|
||||
config.accessKeyId,
|
||||
config.secretAccessKey,
|
||||
config.region,
|
||||
config.service,
|
||||
);
|
||||
|
||||
// 从headers中获取host,或使用传入的host,或使用默认值
|
||||
const host =
|
||||
request.headers?.['host'] ||
|
||||
request.headers?.['Host'] ||
|
||||
request.host ||
|
||||
'visual.volcengineapi.com';
|
||||
|
||||
// 确保headers中包含正确的Host
|
||||
const headersWithHost = {
|
||||
...request.headers,
|
||||
host: host,
|
||||
};
|
||||
|
||||
console.log(`[DEBUG] 豆包签名调试 - Host: ${host}`);
|
||||
console.log(`[DEBUG] 豆包签名调试 - Headers: ${JSON.stringify(headersWithHost)}`);
|
||||
console.log(`[DEBUG] 豆包签名调试 - Payload: ${request.payload?.substring(0, 100)}...`);
|
||||
|
||||
const {
|
||||
authorization,
|
||||
xDate,
|
||||
headers: signedHeaders,
|
||||
} = signer.generateHeaderSignature(
|
||||
request.method,
|
||||
request.uri,
|
||||
request.queryParams || {},
|
||||
headersWithHost,
|
||||
request.payload || '',
|
||||
);
|
||||
|
||||
return { authorization, xDate, headers: signedHeaders };
|
||||
}
|
||||
11
service/src/common/utils/encrypt.ts
Normal file
11
service/src/common/utils/encrypt.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
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=',
|
||||
];
|
||||
11
service/src/common/utils/fromatUrl.ts
Normal file
11
service/src/common/utils/fromatUrl.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export function formatUrl(url: string): string {
|
||||
// 去除空格
|
||||
let formattedUrl = url.replace(/\s+/g, '');
|
||||
|
||||
// 去除最后一位的 '/',如果有的话
|
||||
if (formattedUrl.endsWith('/')) {
|
||||
formattedUrl = formattedUrl.slice(0, -1);
|
||||
}
|
||||
|
||||
return formattedUrl;
|
||||
}
|
||||
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;
|
||||
}
|
||||
26
service/src/common/utils/getClientIp.ts
Normal file
26
service/src/common/utils/getClientIp.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Request } from 'express';
|
||||
|
||||
function getFirstValidIp(ipString: string): string {
|
||||
const ips = ipString.split(',').map(ip => ip.trim());
|
||||
// 可以在这里加入对IP地址有效性的额外验证
|
||||
return ips.find(ip => isValidIp(ip)) || '';
|
||||
}
|
||||
|
||||
function isValidIp(ip: string): boolean {
|
||||
return /^\d{1,3}(\.\d{1,3}){3}$/.test(ip) || /^::ffff:\d{1,3}(\.\d{1,3}){3}$/.test(ip);
|
||||
}
|
||||
|
||||
export function getClientIp(req: Request): string {
|
||||
const forwardedFor = req.header('x-forwarded-for');
|
||||
let clientIp = forwardedFor ? getFirstValidIp(forwardedFor) : '';
|
||||
|
||||
if (!clientIp) {
|
||||
clientIp = req.connection.remoteAddress || req.socket.remoteAddress || '';
|
||||
}
|
||||
|
||||
if (clientIp.startsWith('::ffff:')) {
|
||||
clientIp = clientIp.substring(7);
|
||||
}
|
||||
|
||||
return clientIp;
|
||||
}
|
||||
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];
|
||||
}
|
||||
29
service/src/common/utils/getTokenCount.ts
Normal file
29
service/src/common/utils/getTokenCount.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { encode } from 'gpt-tokenizer';
|
||||
|
||||
export const getTokenCount = async (input: any): Promise<number> => {
|
||||
let text = '';
|
||||
|
||||
if (Array.isArray(input)) {
|
||||
// 如果输入是数组,处理消息数组
|
||||
text = input.reduce((pre: string, cur: any) => {
|
||||
if (Array.isArray(cur.content)) {
|
||||
const contentText = cur.content
|
||||
.filter((item: { type: string }) => item.type === 'text')
|
||||
.map((item: { text: any }) => item.text)
|
||||
.join(' ');
|
||||
return pre + contentText;
|
||||
} else {
|
||||
return pre + (cur.content || '');
|
||||
}
|
||||
}, '');
|
||||
} else if (typeof input === 'string') {
|
||||
// 如果输入是字符串,直接处理
|
||||
text = input;
|
||||
} else if (input) {
|
||||
// 如果输入是其他类型,将其转换为字符串处理
|
||||
text = String(input);
|
||||
}
|
||||
|
||||
text = text.replace(/<\|endoftext\|>/g, '');
|
||||
return encode(text).length;
|
||||
};
|
||||
43
service/src/common/utils/handleError.ts
Normal file
43
service/src/common/utils/handleError.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export function handleError(error: { response: { status: any }; message: string }) {
|
||||
let message = '发生未知错误,请稍后再试';
|
||||
|
||||
if (axios.isAxiosError(error) && error.response) {
|
||||
switch (error.response.status) {
|
||||
case 400:
|
||||
message = '发生错误:400 Bad Request - 请求因格式错误无法被服务器处理。';
|
||||
break;
|
||||
case 401:
|
||||
message = '发生错误:401 Unauthorized - 请求要求进行身份验证。';
|
||||
break;
|
||||
case 403:
|
||||
message = '发生错误:403 Forbidden - 服务器拒绝执行请求。';
|
||||
break;
|
||||
case 404:
|
||||
message = '发生错误:404 Not Found - 请求的资源无法在服务器上找到。';
|
||||
break;
|
||||
case 500:
|
||||
message = '发生错误:500 Internal Server Error - 服务器内部错误,无法完成请求。';
|
||||
break;
|
||||
case 502:
|
||||
message =
|
||||
'发生错误:502 Bad Gateway - 作为网关或代理工作的服务器从上游服务器收到无效响应。';
|
||||
break;
|
||||
case 503:
|
||||
message =
|
||||
'发生错误:503 Service Unavailable - 服务器暂时处于超负载或维护状态,无法处理请求。';
|
||||
break;
|
||||
// 你可以继续添加其他你认为常见的HTTP错误状态码及其解释
|
||||
default:
|
||||
// message = `发生错误:${error.response.status} - ${error.response.statusText}`;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 处理非Axios错误
|
||||
message = error.message || message;
|
||||
}
|
||||
|
||||
// 返回处理后的错误信息
|
||||
return message;
|
||||
}
|
||||
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}`;
|
||||
}
|
||||
26
service/src/common/utils/index.ts
Normal file
26
service/src/common/utils/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export * from './base';
|
||||
export * from './convertUrlToBase64';
|
||||
export * from './createOrderId';
|
||||
export * from './createRandomCode';
|
||||
export * from './createRandomInviteCode';
|
||||
export * from './createRandomNonceStr';
|
||||
export * from './createRandomUid';
|
||||
export * from './date';
|
||||
export * from './encrypt';
|
||||
export * from './fromatUrl';
|
||||
export * from './generateCrami';
|
||||
export * from './getClientIp';
|
||||
export * from './getDiffArray';
|
||||
export * from './getRandomItem';
|
||||
export * from './getRandomItemFromArray';
|
||||
export * from './getTokenCount';
|
||||
export * from './handleError';
|
||||
export * from './hideString';
|
||||
export * from './maskCrami';
|
||||
export * from './maskEmail';
|
||||
export * from './maskIpAddress';
|
||||
export * from './removeSpecialCharacters';
|
||||
export * from './removeThinkTags';
|
||||
export * from './tools';
|
||||
export * from './utcformatTime';
|
||||
export * from './correctApiBaseUrl';
|
||||
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, '');
|
||||
}
|
||||
30
service/src/common/utils/removeThinkTags.ts
Normal file
30
service/src/common/utils/removeThinkTags.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 删除以 <think> 开头到 </think> 之间的内容
|
||||
* @param content - 需要处理的内容
|
||||
* @returns 处理后的内容
|
||||
*/
|
||||
export function removeThinkTags(content: any) {
|
||||
// 如果 content 为 null 或 undefined,直接返回原值
|
||||
if (content === null || content === undefined) {
|
||||
return content;
|
||||
}
|
||||
|
||||
if (Array.isArray(content)) {
|
||||
// 如果是数组,遍历其中的每个元素,处理其中的文本内容
|
||||
return content.map(item => {
|
||||
if (item && item.type === 'text' && typeof item.text === 'string') {
|
||||
// 对文本内容进行<think>标签处理
|
||||
item.text = item.text.replace(/<think>[\s\S]*?<\/think>/g, '');
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是普通文本,直接删除<think>标签
|
||||
if (typeof content === 'string') {
|
||||
return content.replace(/<think>[\s\S]*?<\/think>/g, '');
|
||||
}
|
||||
|
||||
// 如果既不是数组也不是字符串,原样返回
|
||||
return content;
|
||||
}
|
||||
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