| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- 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<SendSmsResult> {
- 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<GetReportResult> {
- 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<string, string> = {
- '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}`
- }
- }
|