xins.service.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import axios from 'axios'
  2. import { FastifyInstance } from 'fastify'
  3. import { GetSmsService } from './get-sms-service'
  4. import { XMLParser } from 'fast-xml-parser'
  5. import { SendSmsResult, PhoneStatus, GetReportResult } from './sms.types'
  6. const axiosInstance = axios.create({
  7. baseURL: 'http://114.199.71.130:8138/'
  8. })
  9. const MKT_USER_NAME = 'JIAXINMKT'
  10. const MKT_PASSWORD = 'RB7EAo3d'
  11. const OTP_USER_NAME = 'JIAXINOTP'
  12. const OTP_PASSWORD = 'OgXb8X4q'
  13. const DEFAULT_CALLER = 10123456789
  14. export class xinsService extends GetSmsService {
  15. private xmlParser: XMLParser
  16. private app: FastifyInstance
  17. constructor(app: FastifyInstance) {
  18. super()
  19. this.app = app
  20. this.xmlParser = new XMLParser({
  21. ignoreAttributes: false,
  22. attributeNamePrefix: '@_'
  23. })
  24. }
  25. /**
  26. * 解析 XML 字符串
  27. */
  28. private parseXml(xml: string): any {
  29. return this.xmlParser.parse(xml)
  30. }
  31. async sendSms(numbers: string[], message: string): Promise<SendSmsResult> {
  32. if (numbers.length > 100) {
  33. throw new Error('Maximum number of numbers is 100')
  34. }
  35. const Callee = numbers.join(',')
  36. const Text = encodeURIComponent(message)
  37. const response = await axiosInstance.get('14.dox', {
  38. params: {
  39. UserName: MKT_USER_NAME,
  40. PassWord: MKT_PASSWORD,
  41. Caller: DEFAULT_CALLER,
  42. Callee,
  43. CallerAddrTon: 1,
  44. CallerAddrNpi: 1,
  45. CalleeAddrTon: 1,
  46. CalleeAddrNpi: 1,
  47. CharSet: 1,
  48. DCS: 8,
  49. Text
  50. }
  51. })
  52. // 处理返回结果
  53. const xmlData = response.data
  54. const parsed = this.parseXml(xmlData)
  55. const status = parsed?.Message?.Head?.Status
  56. if (status !== '0') {
  57. const errorDesc = this.getAckErrorDescription(status)
  58. throw new Error(`发送短信失败 [Status:${status}]: ${errorDesc}`)
  59. }
  60. this.app.log.info(`发送短信成功 : ${xmlData}`)
  61. const msgid = parsed?.Message?.Body?.MsgID
  62. if (!msgid) {
  63. throw new Error('MsgID is missing in response')
  64. }
  65. return {
  66. msgid,
  67. success: true
  68. }
  69. }
  70. async getReport(params: any): Promise<GetReportResult> {
  71. const response = await axiosInstance.get('5.dox', {
  72. params: {
  73. UserName: MKT_USER_NAME,
  74. PassWord: MKT_PASSWORD
  75. }
  76. })
  77. const xmlData = response.data
  78. const parsed = this.parseXml(xmlData)
  79. const status = parsed?.Message?.Head?.Status
  80. if (status !== '0') {
  81. const errorDesc = this.getAckErrorDescription(status)
  82. throw new Error(`获取报告失败 [Status:${status}]: ${errorDesc}`)
  83. }
  84. this.app.log.info('获取报告成功')
  85. // Report
  86. const body = parsed?.Message?.Body
  87. let reports: any[] = []
  88. if (body?.Report) {
  89. if (Array.isArray(body.Report)) {
  90. reports = body.Report
  91. } else {
  92. reports = [body.Report]
  93. }
  94. }
  95. // msgid过滤
  96. if (params?.msgid) {
  97. reports = reports.filter(report => report.MsgID === params.msgid)
  98. }
  99. const phoneStatusList: PhoneStatus[] = reports.map(report => {
  100. // 000 表示成功
  101. const state = report.State || ''
  102. const error = report.Error || ''
  103. const status = this.parseReportStatus(state, error)
  104. return {
  105. number: `+${report.Callee}`,
  106. status
  107. }
  108. })
  109. return {
  110. phoneStatusList
  111. }
  112. }
  113. private parseReportStatus(state: string, error: string): 'success' | 'waiting' | 'fail' {
  114. if (error === '000' && state === 'DELIVRD') {
  115. return 'success'
  116. }
  117. if (error !== '000' && error !== '') {
  118. return 'fail'
  119. }
  120. if (state) {
  121. if (state.includes(':')) {
  122. const [statusType] = state.split(':')
  123. switch (statusType) {
  124. case 'REJECTD':
  125. case 'UNDELIV':
  126. case 'DELETED':
  127. case 'EXPIRED':
  128. return 'fail'
  129. default:
  130. return 'waiting'
  131. }
  132. } else {
  133. switch (state) {
  134. case 'DELIVRD':
  135. return 'waiting'
  136. case 'UNKNOWN':
  137. return 'waiting'
  138. default:
  139. return 'waiting'
  140. }
  141. }
  142. }
  143. return 'waiting'
  144. }
  145. private getAckErrorDescription(status: string): string {
  146. const errorMap: Record<string, string> = {
  147. '0': '成功',
  148. '1': '登陆错误:用户名、密码鉴权错误',
  149. '2': '登陆错误:IP地址错误',
  150. '3': '登陆错误:账号被禁用',
  151. '4': '登陆错误:登陆接口数量过多',
  152. '9': '系统内部错误:客户需要重新提交,直到提交成功为止',
  153. '12': '短信内容URL解码错误',
  154. '13': 'UCS2转换GBK错误',
  155. '14': '字符集错误',
  156. '15': '短信内容UTF8解码错误',
  157. '16': 'GBK转换UCS2错误',
  158. '17': '短信内容长度过长或为空',
  159. '20': '被叫匹配MCC/MNC失败',
  160. '21': '被叫没有对匹配到的费率',
  161. '22': '相同内容重复提交',
  162. '23': '目的账号没有配置',
  163. '24': '签名有前后各1个',
  164. '25': '高频自动拒绝',
  165. '26': '被叫长度错误',
  166. '27': '被叫是黑名单',
  167. '28': '短信内容鉴权失败:内容没有匹配到模板',
  168. '29': '短信内容鉴权失败:内容出现违禁词',
  169. '30': 'DCS错误',
  170. '31': '内容和DCS不一致',
  171. '44': '路由错误',
  172. '45': '短信内容长度超长',
  173. '46': '落地网关没有设置费率',
  174. '48': '客户自定义签名错误',
  175. '49': '系统没有匹配出签名',
  176. '50': '客户签名和主叫绑定失败',
  177. '60': '长消息过长或子消息数量过多',
  178. '61': '长消息的子消息DCS不一致',
  179. '62': '长消息组合超时',
  180. '63': '长消息中的子消息序号越界',
  181. '64': '长消息:子消息已经存在',
  182. '77': 'DR超时的错误码',
  183. '78': '定制DR的错误码',
  184. '88': '客户余额不足',
  185. '98': '落地原因导致消息不可到达',
  186. '99': '未知状态',
  187. '240': 'DCS错误',
  188. '241': '主叫号码异常:Caller字段值过长',
  189. '242': '被叫号码异常:被叫号码字段不存在或字段值过长',
  190. '253': '被叫号码数量过多'
  191. }
  192. return errorMap[status] || `未知错误码: ${status}`
  193. }
  194. }