Эх сурвалжийг харах

feat(rcs-number): 集成 xlcode 短信通道

- 在 rcs-number.entity.ts 中添加 xlcode 作为新的短信通道来源
- 在 rcs-number.module.ts 中引入 xlcode 服务模块
- 在 rcs-number.service.ts 中集成 xlcode 服务- 新增 xlcode.service.ts 文件实现 xlcode 短信服务功能
wui 8 сар өмнө
parent
commit
58e456e482

+ 2 - 2
src/rcs-number/entities/rcs-number.entity.ts

@@ -48,8 +48,8 @@ export enum RcsNumberSource {
     svenkvint = 'svenkvint',
     smsapi = 'smsapi',
     smstiger = 'smstiger',
-    smstiger02 = 'smstiger02'
-
+    smstiger02 = 'smstiger02',
+    xlcode = 'xlcode'
 }
 
 @Entity()

+ 153 - 0
src/rcs-number/impl/xlcode.service.ts

@@ -0,0 +1,153 @@
+import { GetNumberResponse, GetNumberService } from './get-number-service'
+import { RcsNumberSource } from '../entities/rcs-number.entity'
+import { InternalServerErrorException, BadRequestException } from '@nestjs/common'
+import axios, { AxiosError } from 'axios'
+import { createHash } from 'node:crypto'
+import * as querystring from 'node:querystring'
+
+const axiosInstance = axios.create({
+    baseURL: 'http://api.xlcode.io'
+})
+
+const cid = '2940177066730633'
+const appId = '9'
+const key = 'K3oo52i13ebkm4smd75p1v5pzq8h2nsf'
+
+export class xlcode extends GetNumberService {
+    source: RcsNumberSource = RcsNumberSource.xlcode
+
+    private generateSign(params: Record<string, string>): string {
+        // 按字典序排序参数
+        const sortedParams = Object.keys(params)
+            .sort()
+            .reduce((acc, key) => {
+                acc[key] = params[key]
+                return acc
+            }, {})
+
+        // 构建签名字符串
+        let signStr = Object.entries(sortedParams)
+            .map(([key, value]) => `${key}=${value}`)
+            .join('&')
+        signStr += `&key=${key}`
+
+        // 计算MD5并转换为大写
+        return createHash('md5').update(signStr).digest('hex').toUpperCase()
+    }
+
+    async getNumber(country: string, num?: number): Promise<GetNumberResponse> {
+        const params = {
+            cid,
+            appId,
+            attr: country.toUpperCase()
+        }
+
+        const sign = this.generateSign(params)
+        const formData = querystring.stringify({
+            ...params,
+            sign
+        })
+
+        try {
+            const res = await axiosInstance.post('/api/account', formData, {
+                headers: {
+                    'Content-Type': 'application/x-www-form-urlencoded'
+                }
+            })
+
+            if (res.data.code !== 0) {
+                throw new InternalServerErrorException(res.data.msg)
+            }
+
+            return {
+                number: res.data.data.account,
+                orderId: res.data.data.id,
+                operatorCode: res.data.data.countryCode || '',
+                operatorName: res.data.data.regionCode || '',
+                rawResponse: res.data
+            }
+        } catch (error) {
+            if (error instanceof AxiosError && error.response?.status === 400) {
+                const errorData = error.response.data
+                throw new BadRequestException('xlcode error: ' + errorData.msg || '请求参数错误')
+            }
+            throw error
+        }
+    }
+
+    async retriveMessage(orderId: string, num?: number): Promise<string> {
+        const params = {
+            cid,
+            id: orderId
+        }
+
+        const sign = this.generateSign(params)
+        const formData = querystring.stringify({
+            ...params,
+            sign
+        })
+
+        try {
+            const res = await axiosInstance.post('/api/code', formData, {
+                headers: {
+                    'Content-Type': 'application/x-www-form-urlencoded'
+                }
+            })
+
+            console.log(res.data)
+
+            if (res.data.code !== 0) {
+                throw new InternalServerErrorException(res.data.msg)
+            }
+
+            if (res.data.data.code) {
+                return res.data.data.content || `Your Messenger verification code is G-${res.data.data.code}`
+            }
+
+            return null
+        } catch (error) {
+            if (error instanceof AxiosError && error.response?.status === 400) {
+                const errorData = error.response.data
+                throw new BadRequestException(errorData.msg || '请求参数错误')
+            }
+            throw error
+        }
+    }
+
+    async releaseNumber(number: string): Promise<void> {
+        const params = {
+            cid,
+            id: number
+        }
+
+        const sign = this.generateSign(params)
+        const formData = querystring.stringify({
+            ...params,
+            sign
+        })
+
+        try {
+            const res = await axiosInstance.post('/api/release', formData, {
+                headers: {
+                    'Content-Type': 'application/x-www-form-urlencoded'
+                }
+            })
+
+            if (res.data.code !== 0) {
+                throw new InternalServerErrorException(res.data.msg)
+            }
+        } catch (error) {
+            if (error instanceof AxiosError && error.response?.status === 400) {
+                const errorData = error.response.data
+                throw new BadRequestException(errorData.msg || '请求参数错误')
+            }
+            throw error
+        }
+    }
+
+    async blockNumber(number: string): Promise<void> {}
+
+    async cacheNumber(country: string, size: number): Promise<GetNumberResponse[]> {
+        return null
+    }
+}

+ 3 - 1
src/rcs-number/rcs-number.module.ts

@@ -30,6 +30,7 @@ import { sms23256 } from './impl/sms23256.service'
 import { svenkvint } from './impl/svenkvint.service'
 import { smsapi } from './impl/smsapi.service'
 import { smstiger } from './impl/smstiger.service'
+import { xlcode } from './impl/xlcode.service'
 
 @Module({
     imports: [
@@ -62,7 +63,8 @@ import { smstiger } from './impl/smstiger.service'
         sms23256,
         svenkvint,
         smsapi,
-        smstiger
+        smstiger,
+        xlcode
     ],
     controllers: [RcsNumberController],
     exports: [RcsNumberService]

+ 4 - 0
src/rcs-number/rcs-number.service.ts

@@ -39,6 +39,7 @@ import { sms23256 } from './impl/sms23256.service'
 import { svenkvint } from './impl/svenkvint.service'
 import { smsapi } from './impl/smsapi.service'
 import { smstiger } from './impl/smstiger.service'
+import { xlcode } from './impl/xlcode.service'
 
 @Injectable()
 export class RcsNumberService {
@@ -89,6 +90,7 @@ export class RcsNumberService {
         private sms23256: sms23256,
         private svenkvint: svenkvint,
         private smsapi: smsapi,
+        private xlcode: xlcode,
         private readonly redisService: RedisService
     ) {
         this.durian = new durian('unsnap3094', 'U3Jma1hkbUxXblEyL0ZYai9WWFVvdz09', RcsNumberSource.durian)
@@ -378,6 +380,8 @@ export class RcsNumberService {
                 return this.smstiger
             case RcsNumberSource.smstiger02:
                 return this.smstiger02
+            case RcsNumberSource.xlcode:
+                return this.xlcode
             default:
                 throw new ServiceUnavailableException('不支持的短信通道')
         }