import * as crypto from 'crypto' import { TcbContext } from './types' import { parse } from 'path' const BOT_REG = /\/v1\/aibot\/bots\/([^/]+)(\/)*([^\/]+)*(\/)*([^\/]+)*/ const SPECIAL_KEY = new Map() // 添加键值对 SPECIAL_KEY.set('msgid', 'msgId') SPECIAL_KEY.set('openKfid', 'openKfId') SPECIAL_KEY.set('externalUserid', 'externalUserId') SPECIAL_KEY.set('msgtype', 'msgType') export function parseBotId(url: string) { return parseUrl(url).botId } export function parseConversationId(url: string) { const { pathname } = new URL(url) // /v1/aibot/bots/:botId/conversation/:conversationId const parts = pathname.split('/') if (!parts || parts.length < 7) { return '' } return parts[6] } export function parseBotTag(url: string) { const botId = parseBotId(url) const botTag = botId.split('-')[2] // expect botId to be `ibot-xxx-${botTag}` return botTag } export function parseApiName(url: string) { try { return parseUrl(url).apiName } catch (e) { return null } } function parseUrl(url: string) { const { pathname } = new URL(url) const regResult = pathname.match(BOT_REG) if (!regResult) { throw new Error('Invalid pathname') } const [_, botId, _p, apiName] = regResult return { botId, apiName: apiName || '' } } export function parseWxEnvParam(ctx: TcbContext): { triggerSrc: string; wxVerify: boolean } { const envMap = parseEnvFromHeader(ctx, 'x-scf-environments') return { triggerSrc: envMap['WX_APPTYPE'], wxVerify: envMap['WX_QUALIFICATION_VERIFY'] === '1' ? true : false } } export function parseEnvFromHeader(ctx: TcbContext, key: string) { const headers = ctx.httpContext?.headers if (!headers) { throw new Error('Invalid headers') } const value = headers[key] as string const envMap = value.split(',').reduce>((env, pair) => { const [key, value] = pair.split('=') env[key.trim()] = value.trim() return env }, {}) return envMap } export function parseQueryFromCtx(ctx: TcbContext) { const url = ctx.httpContext?.url if (!url) { throw new Error('Invalid url: ' + url) } return parseQuery(url) } export function parseQuery(url: string) { const { search } = new URL(url) const query = new URLSearchParams(search) const ret: Record = {} for (const [key, value] of query.entries()) { ret[key] = value } return ret } export function genRecordId() { return 'record-' + genRandomStr(8) } /** * 生成随机字符串 */ export function genRandomStr(length: number): string { // 生成随机的字节数组 const randomBytes = crypto.randomBytes(Math.ceil(length / 2)) // 将字节数组转换为十六进制字符串 const hexString = randomBytes.toString('hex').slice(0, length) return hexString } /** * 将字符串转换为大驼峰格式 */ function toPascalCase(str: string): string { // 处理开头是下划线的情况 if (str.startsWith('_')) { return '_' + toPascalCase(str.slice(1)) } // 处理普通情况:转换所有分隔符后的字母为大写 return str.replace(/(^\w|[-_]\w)/g, match => { return match.replace(/[-_]/, '').toUpperCase() }) } function toCamelCase(str: string): string { // 处理开头是下划线的情况 if (str.startsWith('_')) { return '_' + toCamelCase(str.slice(1)) } // 处理普通情况:先转换为PascalCase,再将首字母小写 const pascal = toPascalCase(str) return pascal.charAt(0).toLowerCase() + pascal.slice(1) } /** * 递归转换对象的所有key为小驼峰格式 */ export function transformKeysToCamelCase(obj: any): any { if (Array.isArray(obj)) { return obj.map(item => transformKeysToCamelCase(item)) } if (obj !== null && typeof obj === 'object') { const newObj: { [key: string]: any } = {} Object.keys(obj).forEach(key => { let newKey = toCamelCase(key) // 企业微信的接口不符合命名规范,手动处理一下 if (SPECIAL_KEY.has(newKey)) { newKey = SPECIAL_KEY.get(newKey) } newObj[newKey] = transformKeysToCamelCase(obj[key]) }) return newObj } return obj }