import axios from 'axios' import { FastifyInstance } from 'fastify' import { GetSmsService } from './get-sms-service' import { XMLParser } from 'fast-xml-parser' import { SendSmsResult, PhoneStatus, GetReportResult } from './sms.types' const axiosInstance = axios.create({ baseURL: 'http://114.199.71.130:8138/' }) const MKT_USER_NAME = 'JIAXINMKT' const MKT_PASSWORD = 'RB7EAo3d' const OTP_USER_NAME = 'JIAXINOTP' const OTP_PASSWORD = 'OgXb8X4q' const DEFAULT_CALLER = 10123456789 export class xinsService extends GetSmsService { private xmlParser: XMLParser private app: FastifyInstance constructor(app: FastifyInstance) { super() this.app = app this.xmlParser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' }) } /** * 解析 XML 字符串 */ private parseXml(xml: string): any { return this.xmlParser.parse(xml) } async sendSms(numbers: string[], message: string): Promise { if (numbers.length > 100) { throw new Error('Maximum number of numbers is 100') } const Callee = numbers.join(',') const Text = encodeURIComponent(message) const response = await axiosInstance.get('14.dox', { params: { UserName: MKT_USER_NAME, PassWord: MKT_PASSWORD, Caller: DEFAULT_CALLER, Callee, CallerAddrTon: 1, CallerAddrNpi: 1, CalleeAddrTon: 1, CalleeAddrNpi: 1, CharSet: 1, DCS: 8, Text } }) // 处理返回结果 const xmlData = response.data const parsed = this.parseXml(xmlData) const status = parsed?.Message?.Head?.Status if (status !== '0') { const errorDesc = this.getAckErrorDescription(status) throw new Error(`发送短信失败 [Status:${status}]: ${errorDesc}`) } this.app.log.info(`发送短信成功 : ${xmlData}`) const msgid = parsed?.Message?.Body?.MsgID if (!msgid) { throw new Error('MsgID is missing in response') } return { msgid, success: true } } async getReport(params: any): Promise { const response = await axiosInstance.get('5.dox', { params: { UserName: MKT_USER_NAME, PassWord: MKT_PASSWORD } }) const xmlData = response.data const parsed = this.parseXml(xmlData) const status = parsed?.Message?.Head?.Status if (status !== '0') { const errorDesc = this.getAckErrorDescription(status) throw new Error(`获取报告失败 [Status:${status}]: ${errorDesc}`) } this.app.log.info('获取报告成功') // Report const body = parsed?.Message?.Body let reports: any[] = [] if (body?.Report) { if (Array.isArray(body.Report)) { reports = body.Report } else { reports = [body.Report] } } // msgid过滤 if (params?.msgid) { reports = reports.filter(report => report.MsgID === params.msgid) } const phoneStatusList: PhoneStatus[] = reports.map(report => { // 000 表示成功 const state = report.State || '' const error = report.Error || '' const status = this.parseReportStatus(state, error) return { number: `+${report.Callee}`, status } }) return { phoneStatusList } } private parseReportStatus(state: string, error: string): 'success' | 'waiting' | 'fail' { if (error === '000' && state === 'DELIVRD') { return 'success' } if (error !== '000' && error !== '') { return 'fail' } if (state) { if (state.includes(':')) { const [statusType] = state.split(':') switch (statusType) { case 'REJECTD': case 'UNDELIV': case 'DELETED': case 'EXPIRED': return 'fail' default: return 'waiting' } } else { switch (state) { case 'DELIVRD': return 'waiting' case 'UNKNOWN': return 'waiting' default: return 'waiting' } } } return 'waiting' } private getAckErrorDescription(status: string): string { const errorMap: Record = { '0': '成功', '1': '登陆错误:用户名、密码鉴权错误', '2': '登陆错误:IP地址错误', '3': '登陆错误:账号被禁用', '4': '登陆错误:登陆接口数量过多', '9': '系统内部错误:客户需要重新提交,直到提交成功为止', '12': '短信内容URL解码错误', '13': 'UCS2转换GBK错误', '14': '字符集错误', '15': '短信内容UTF8解码错误', '16': 'GBK转换UCS2错误', '17': '短信内容长度过长或为空', '20': '被叫匹配MCC/MNC失败', '21': '被叫没有对匹配到的费率', '22': '相同内容重复提交', '23': '目的账号没有配置', '24': '签名有前后各1个', '25': '高频自动拒绝', '26': '被叫长度错误', '27': '被叫是黑名单', '28': '短信内容鉴权失败:内容没有匹配到模板', '29': '短信内容鉴权失败:内容出现违禁词', '30': 'DCS错误', '31': '内容和DCS不一致', '44': '路由错误', '45': '短信内容长度超长', '46': '落地网关没有设置费率', '48': '客户自定义签名错误', '49': '系统没有匹配出签名', '50': '客户签名和主叫绑定失败', '60': '长消息过长或子消息数量过多', '61': '长消息的子消息DCS不一致', '62': '长消息组合超时', '63': '长消息中的子消息序号越界', '64': '长消息:子消息已经存在', '77': 'DR超时的错误码', '78': '定制DR的错误码', '88': '客户余额不足', '98': '落地原因导致消息不可到达', '99': '未知状态', '240': 'DCS错误', '241': '主叫号码异常:Caller字段值过长', '242': '被叫号码异常:被叫号码字段不存在或字段值过长', '253': '被叫号码数量过多' } return errorMap[status] || `未知错误码: ${status}` } }