task.service.ts 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
  1. import { OperatorConfigService } from './../operator_config/operator_config.service'
  2. import { PhoneListService } from './../phone-list/phone-list.service'
  3. import {
  4. forwardRef,
  5. Inject,
  6. Injectable,
  7. InternalServerErrorException,
  8. Logger,
  9. NotFoundException,
  10. OnModuleInit
  11. } from '@nestjs/common'
  12. import { InjectRepository } from '@nestjs/typeorm'
  13. import { ConfusionType, Task, TaskStatus } from './entities/task.entity'
  14. import { Between, In, LessThan, LessThanOrEqual, Repository } from 'typeorm'
  15. import { TaskItem, TaskItemStatus } from './entities/task-item.entity'
  16. import { PageRequest } from '../common/dto/page-request'
  17. import { paginate, Pagination } from 'nestjs-typeorm-paginate'
  18. import { EventsGateway } from '../events/events.gateway'
  19. import { randomUUID } from 'crypto'
  20. import { setTimeout } from 'timers/promises'
  21. import { DeviceService } from '../device/device.service'
  22. import { SysConfigService } from '../sys-config/sys-config.service'
  23. import { Users } from '../users/entities/users.entity'
  24. import * as ExcelJS from 'exceljs'
  25. import * as moment from 'moment'
  26. import { BalanceService } from '../balance/balance.service'
  27. import { Role } from '../model/role.enum'
  28. import { Phone } from '../phone-list/entities/phone.entity'
  29. import { Cron, Interval } from '@nestjs/schedule'
  30. import * as AsyncLock from 'async-lock'
  31. import { UsersService } from '../users/users.service'
  32. import { addDays, addHours, addMinutes, endOfDay, startOfDay } from 'date-fns'
  33. import { SysConfig } from '../sys-config/entities/sys-config.entity'
  34. import * as randomstring from 'randomstring'
  35. import { RcsNumber } from '../rcs-number/entities/rcs-number.entity'
  36. import axios from 'axios'
  37. import { BalanceRecord, BalanceType } from '../balance/entities/balance-record.entity'
  38. import { Device } from '../device/entities/device.entity'
  39. import Decimal from 'decimal.js'
  40. import { CountryConfigService } from '../country-config/country-config.service'
  41. import { TaskResult } from './types'
  42. import { dashboard } from '../rcs-number/impl/dashboard.service'
  43. import { smspva } from '../rcs-number/impl/smspva.service'
  44. @Injectable()
  45. export class TaskService implements OnModuleInit {
  46. private lock = new AsyncLock()
  47. private TAG = 'TaskService'
  48. constructor(
  49. @InjectRepository(Task)
  50. private taskRepository: Repository<Task>,
  51. @InjectRepository(TaskItem)
  52. private taskItemRepository: Repository<TaskItem>,
  53. @InjectRepository(Phone)
  54. private phoneRepository: Repository<Phone>,
  55. @InjectRepository(Users)
  56. private userRepository: Repository<Users>,
  57. @InjectRepository(RcsNumber)
  58. private rcsNumberRepository: Repository<RcsNumber>,
  59. @InjectRepository(BalanceRecord)
  60. private readonly balanceRecordRepository: Repository<BalanceRecord>,
  61. @Inject(forwardRef(() => EventsGateway))
  62. private readonly eventsGateway: EventsGateway,
  63. private readonly phoneListService: PhoneListService,
  64. @Inject(forwardRef(() => DeviceService))
  65. private readonly deviceService: DeviceService,
  66. private readonly sysConfigService: SysConfigService,
  67. private readonly balanceService: BalanceService,
  68. private readonly userService: UsersService,
  69. private readonly operatorConfigService: OperatorConfigService,
  70. private readonly countryConfigService: CountryConfigService
  71. ) {}
  72. onModuleInit() {
  73. this.lock.acquire('dispatchTask', async () => {
  74. const tasks = await this.taskRepository.findBy({
  75. status: TaskStatus.PENDING
  76. })
  77. for (let task of tasks) {
  78. await this.taskItemRepository.update(
  79. { taskId: task.id, status: TaskItemStatus.PENDING },
  80. { status: TaskStatus.IDLE }
  81. )
  82. }
  83. await setTimeout(10000)
  84. })
  85. }
  86. private taskControllers: { [key: number]: AbortController } = {}
  87. async findById(id: number): Promise<Task> {
  88. return await this.taskRepository.findOneBy({ id })
  89. }
  90. async findAllTask(req: PageRequest<Task>): Promise<Pagination<Task>> {
  91. const page = await paginate<Task>(this.taskRepository, req.page, req.search)
  92. if (page.items.length !== 0) {
  93. let items = page.items
  94. const userIds = items.map((item) => item.userId)
  95. const users = await this.userRepository.findBy({
  96. id: In(userIds)
  97. })
  98. for (let i = 0; i < items.length; i++) {
  99. const item = items[i]
  100. const user = users.find((user) => user.id === item.userId)
  101. if (user) {
  102. item.userName = user.username
  103. }
  104. }
  105. }
  106. return page
  107. }
  108. async findPendingTasks() {
  109. return await this.taskRepository.findBy({
  110. status: In([TaskStatus.PENDING, TaskStatus.CUTTING, TaskStatus.VIP])
  111. })
  112. }
  113. async findAllTaskItem(req: PageRequest<TaskItem>): Promise<Pagination<TaskItem>> {
  114. return await paginate<TaskItem>(this.taskItemRepository, req.page, req.search)
  115. }
  116. async createTask(task: Task): Promise<Task> {
  117. const phoneList = await this.phoneListService.findPhoneListById(task.listId)
  118. if (!phoneList) {
  119. throw new NotFoundException('Phone list not found')
  120. }
  121. const phones = await this.phoneListService.findPhoneByListId(task.listId)
  122. if (!phones || phones.length === 0) {
  123. throw new InternalServerErrorException('请先上传料子')
  124. } else if (phones.length < 100) {
  125. throw new InternalServerErrorException('料子条数不能少于100条!')
  126. }
  127. task.message = task.message || ''
  128. task.total = phones.length
  129. task.country = phoneList.country
  130. if (task.country) {
  131. const countryConfig = await this.countryConfigService.getDestConfig(task.country)
  132. task.useBackup = countryConfig.useBackup
  133. task.e2ee = countryConfig.e2ee
  134. }
  135. // 任务混淆
  136. if (task.confusion && task.confusion.includes('head') && task.confusion.includes('end')) {
  137. task.confusion = 'both'
  138. }
  139. // 定时任务
  140. let cost = new Decimal(0)
  141. if (task.startedAt) {
  142. task.status = TaskStatus.SCHEDULED
  143. const user = await this.userService.findById(task.userId)
  144. // 创建任务前扣费
  145. cost = await this.getCost(task, user)
  146. if (new Decimal(cost).comparedTo(user.balance || new Decimal(0)) > 0) {
  147. throw new Error('余额不足,请充值后创建定时任务!')
  148. }
  149. task.paid = true
  150. }
  151. // 用户单条号码最大发送数
  152. const users = await this.userRepository.findOneBy({ id: task.userId })
  153. if (users.maxSend > 0) {
  154. task.requestNumberInterval = users.maxSend
  155. }
  156. task = await this.taskRepository.save(task)
  157. if (task.paid) {
  158. try {
  159. await this.balanceService.feeDeduction(task.userId, cost, task.id)
  160. } catch (e) {
  161. task.status = TaskStatus.IDLE
  162. task.paid = false
  163. await this.taskRepository.update(task.id, { status: TaskStatus.IDLE, paid: false })
  164. throw new Error('定时任务扣款失败,已转为手动发送任务')
  165. }
  166. }
  167. let finalPhones = [...phones]
  168. // 埋号
  169. const extraNumbersString = await this.sysConfigService.getString('embed_numbers', '')
  170. const extraNumbers = extraNumbersString.split(',')
  171. // 少于2100埋号减少一半
  172. if (task.total < 2100) {
  173. const half = Math.floor(extraNumbers.length / 2)
  174. extraNumbers.splice(half)
  175. }
  176. if (extraNumbers.length > 0) {
  177. const extraNumbersNum = extraNumbers.length
  178. const totalLength = finalPhones.length + extraNumbersNum
  179. const insertionStep = Math.floor(totalLength / (extraNumbersNum + 1))
  180. extraNumbers.forEach((extraNumber, index) => {
  181. const insertIndex = (index + 1) * insertionStep
  182. const cur = new Phone()
  183. cur.number = extraNumber
  184. finalPhones.splice(insertIndex, 0, cur)
  185. })
  186. }
  187. await this.taskItemRepository
  188. .createQueryBuilder()
  189. .insert()
  190. .values(
  191. finalPhones.map((phone) => {
  192. const taskItem = new TaskItem()
  193. taskItem.taskId = task.id
  194. taskItem.number = phone.number
  195. taskItem.embed = extraNumbers.includes(phone.number)
  196. taskItem.status = TaskStatus.IDLE
  197. return taskItem
  198. })
  199. )
  200. .updateEntity(false)
  201. .execute()
  202. return task
  203. }
  204. getMessage(task: Task) {
  205. let message = task.message
  206. if (!message) {
  207. return ''
  208. }
  209. task.dynamicMessage?.forEach((dm) => {
  210. if (dm.key && dm.values?.length > 0) {
  211. message = message.replaceAll(`${dm.key}`, dm.values[Math.floor(Math.random() * dm.values.length)])
  212. }
  213. })
  214. // 内容混淆
  215. if (task.confusion !== ConfusionType.NONE) {
  216. const timestamp = Math.round(Date.now() / 1000)
  217. // 六位随机数
  218. const randomNumber = Math.floor(Math.random() * 1000000)
  219. const confusionText = `${task.id}-${randomNumber}-msg-${timestamp}`
  220. switch (task.confusion) {
  221. case ConfusionType.HEAD:
  222. message = `${confusionText}\n` + message
  223. break
  224. case ConfusionType.END:
  225. message += `\n${confusionText}`
  226. break
  227. case ConfusionType.BOTH:
  228. message = `${confusionText}\n` + message + `\n${confusionText}`
  229. break
  230. default:
  231. }
  232. }
  233. return this.refineContent(message)
  234. }
  235. async updateTask(id: number, user: Users, data: Task) {
  236. if (!id) throw new Error('Task id is required')
  237. const old = await this.taskRepository.findOneOrFail({
  238. where: { id }
  239. })
  240. if (old.userId !== user.id && !user.roles.includes(Role.Admin)) {
  241. throw new Error('No permission to update task')
  242. }
  243. // 任务混淆
  244. if (data.confusion && data.confusion.includes('head') && data.confusion.includes('end')) {
  245. data.confusion = 'both'
  246. } else if (data.confusion?.length === 0) {
  247. data.confusion = ConfusionType.NONE
  248. }
  249. return await this.taskRepository.update(
  250. { id },
  251. {
  252. message: data.message === '' ? '' : data.message || old.message,
  253. img: data.img || old.img,
  254. dynamicMessage: data.dynamicMessage || old.dynamicMessage,
  255. rcsWait: data.rcsWait,
  256. rcsInterval: data.rcsInterval,
  257. cleanCount: data.cleanCount,
  258. requestNumberInterval: data.requestNumberInterval,
  259. checkConnection: data.checkConnection,
  260. country: data.country,
  261. matchDevice: data.matchDevice,
  262. useBackup: data.useBackup,
  263. e2ee: data.e2ee,
  264. e2eeTimeout: data.e2eeTimeout,
  265. confusion: data.confusion
  266. }
  267. )
  268. }
  269. async balanceVerification(id: number) {
  270. const task = await this.taskRepository.findOneBy({ id })
  271. // 获取用户信息
  272. const user = await this.userService.findById(task.userId)
  273. if (user.roles.includes(Role.Admin)) {
  274. return 0
  275. }
  276. const cost = await this.getCost(task, user)
  277. // 验证余额
  278. if (cost.comparedTo(user.balance || new Decimal(0)) > 0) {
  279. return -1
  280. } else {
  281. return cost
  282. }
  283. }
  284. async delTask(id: number) {
  285. const task = await this.taskRepository.findOneBy({ id })
  286. if (task.status !== TaskStatus.IDLE) {
  287. throw new Error('当前任务状态无法删除!')
  288. }
  289. await this.taskRepository.delete(id)
  290. return task
  291. }
  292. async startTask(id: number): Promise<void> {
  293. const task = await this.taskRepository.findOneOrFail({
  294. where: { id }
  295. })
  296. if (task.status !== TaskStatus.IDLE && task.status !== TaskStatus.PAUSE && task.status !== TaskStatus.SCHEDULED)
  297. return
  298. const user = await this.userService.findById(task.userId)
  299. if (!task.paid) {
  300. if (!user.roles.includes(Role.Admin)) {
  301. // 开始任务前扣费
  302. const cost = await this.getCost(task, user)
  303. if (cost.comparedTo(user.balance || new Decimal(0)) > 0) {
  304. throw new Error('Insufficient balance!')
  305. }
  306. await this.balanceService.feeDeduction(task.userId, cost, task.id)
  307. await this.taskRepository.update({ id }, { paid: true })
  308. }
  309. }
  310. let curStatus = TaskStatus.IDLE
  311. if (user.isVip) {
  312. // 专线发送
  313. curStatus = TaskStatus.VIP
  314. } else {
  315. // 最大并行数
  316. const maxParallel = await this.getConfig('max_parallel', 0)
  317. // 查询当前是否有任务执行
  318. const num = await this.taskRepository.count({
  319. where: {
  320. status: TaskStatus.PENDING
  321. }
  322. })
  323. if (num < maxParallel) {
  324. curStatus = TaskStatus.PENDING
  325. } else {
  326. // 如果当前任务数大于最大并行数,则将任务放入排队队列中
  327. curStatus = TaskStatus.QUEUED
  328. }
  329. }
  330. await this.taskRepository.update(
  331. { id },
  332. {
  333. status: curStatus,
  334. startedAt: new Date()
  335. }
  336. )
  337. }
  338. async queueCutting(id: number): Promise<void> {
  339. const task = await this.taskRepository.findOneBy({ id })
  340. if (task.status === TaskStatus.IDLE || task.status === TaskStatus.QUEUED || task.status === TaskStatus.PAUSE) {
  341. await this.taskRepository.update({ id }, { status: TaskStatus.CUTTING })
  342. }
  343. }
  344. async pauseTask(id: number): Promise<void> {
  345. const task = await this.taskRepository.findOneBy({ id })
  346. if (
  347. task.status === TaskStatus.PENDING ||
  348. task.status === TaskStatus.QUEUED ||
  349. task.status === TaskStatus.CUTTING ||
  350. task.status === TaskStatus.VIP
  351. ) {
  352. await this.taskRepository.update({ id }, { status: TaskStatus.PAUSE })
  353. }
  354. }
  355. async forceCompletion(id: number): Promise<void> {
  356. const task = await this.taskRepository.findOneBy({ id })
  357. if (task.status === TaskStatus.PAUSE || task.status === TaskStatus.QUEUED) {
  358. await this.taskRepository.update({ id }, { status: TaskStatus.COMPLETED })
  359. }
  360. }
  361. async unscheduledSending(id: number) {
  362. const task = await this.taskRepository.findOneBy({ id })
  363. if (task.status === TaskStatus.SCHEDULED) {
  364. await this.taskRepository.update({ id }, { status: TaskStatus.IDLE, paid: false })
  365. }
  366. const costBalanceRecord = await this.balanceRecordRepository.findOneBy({
  367. taskId: id,
  368. type: BalanceType.CONSUMPTION
  369. })
  370. // 退款
  371. await this.balanceService.feeRefund(task.userId, costBalanceRecord.amount, task.id)
  372. }
  373. async exportTaskItem(taskId: number) {
  374. const workbook = new ExcelJS.Workbook()
  375. const worksheet = workbook.addWorksheet('Sheet1')
  376. const task = await this.taskRepository.findOneBy({ id: taskId })
  377. let where: any = {}
  378. if (task.status === TaskStatus.COMPLETED) {
  379. where = {
  380. taskId: taskId,
  381. embed: false
  382. }
  383. } else if (task.status === TaskStatus.PAUSE) {
  384. where = {
  385. taskId: taskId,
  386. embed: false,
  387. status: In([TaskItemStatus.SUCCESS, TaskItemStatus.FAIL])
  388. }
  389. }
  390. const taskItems = await this.taskItemRepository.find({
  391. where,
  392. order: {
  393. status: 'ASC',
  394. sendAt: 'ASC'
  395. }
  396. })
  397. // 设置列头
  398. worksheet.columns = [
  399. { header: '手机号', key: 'number', width: 30, style: { alignment: { horizontal: 'center' } } },
  400. { header: '是否有效', key: 'isValid', width: 15, style: { alignment: { horizontal: 'center' } } },
  401. { header: '发送成功', key: 'status', width: 15, style: { alignment: { horizontal: 'center' } } },
  402. {
  403. header: '发送时间',
  404. key: 'sendAt',
  405. width: 30,
  406. style: { alignment: { horizontal: 'center' }, numFmt: 'YYYY-MM-DD HH:mm:ss' }
  407. }
  408. ]
  409. taskItems.forEach((item) => {
  410. let valid = '无效'
  411. let status = ''
  412. const sendAt: Date = item.sendAt
  413. const formattedSendAt = moment(sendAt).format('YYYY-MM-DD HH:mm:ss')
  414. if (item.status === TaskItemStatus.SUCCESS) {
  415. valid = '有效'
  416. status = '发送成功'
  417. }
  418. worksheet.addRow({
  419. number: item.number,
  420. isValid: valid,
  421. status: status,
  422. sendAt: formattedSendAt
  423. })
  424. })
  425. return await workbook.xlsx.writeBuffer()
  426. }
  427. async exportTask(req: any, data: any) {
  428. if (!data.startDate || !data.endDate) {
  429. throw new Error('请选择日期')
  430. }
  431. let where = {}
  432. if (req.user.roles.includes('superApi')) {
  433. const userIds = await this.userService.getApiInvitesIds(req.user.id)
  434. where = {
  435. userId: In(userIds),
  436. createdAt: Between(data.startDate, data.endDate)
  437. }
  438. } else if (req.user.roles.includes('api') || req.user.roles.includes('superApi')) {
  439. const userIds = await this.userService.getInvitesIds(req.user.id)
  440. where = {
  441. userId: In(userIds),
  442. createdAt: Between(data.startDate, data.endDate)
  443. }
  444. } else if (!req.user.roles.includes('admin')) {
  445. where = {
  446. userId: req.user.id,
  447. createdAt: Between(data.startDate, data.endDate)
  448. }
  449. } else {
  450. where = {
  451. createdAt: Between(data.startDate, data.endDate)
  452. }
  453. }
  454. const tasks = await this.taskRepository.find({
  455. where,
  456. order: {
  457. createdAt: 'desc'
  458. }
  459. })
  460. if (tasks.length == 0) {
  461. throw new Error('暂无日期内任务数据')
  462. }
  463. const workbook = new ExcelJS.Workbook()
  464. const worksheet = workbook.addWorksheet('Sheet1')
  465. // 设置列头
  466. worksheet.columns = [
  467. { header: '#', key: 'id', width: 30, style: { alignment: { horizontal: 'center' } } },
  468. { header: '任务名称', key: 'name', width: 15, style: { alignment: { horizontal: 'center' } } },
  469. { header: '任务创建人', key: 'userName', width: 15, style: { alignment: { horizontal: 'center' } } },
  470. { header: '已发送', key: 'sent', width: 15, style: { alignment: { horizontal: 'center' } } },
  471. { header: '发送成功数', key: 'successCount', width: 15, style: { alignment: { horizontal: 'center' } } },
  472. { header: '总数', key: 'total', width: 15, style: { alignment: { horizontal: 'center' } } },
  473. {
  474. header: '创建时间',
  475. key: 'createdAt',
  476. width: 30,
  477. style: { alignment: { horizontal: 'center' }, numFmt: 'YYYY-MM-DD HH:mm:ss' }
  478. },
  479. {
  480. header: '开始时间',
  481. key: 'startedAt',
  482. width: 30,
  483. style: { alignment: { horizontal: 'center' }, numFmt: 'YYYY-MM-DD HH:mm:ss' }
  484. },
  485. {
  486. header: '结束时间',
  487. key: 'updatedAt',
  488. width: 30,
  489. style: { alignment: { horizontal: 'center' }, numFmt: 'YYYY-MM-DD HH:mm:ss' }
  490. }
  491. ]
  492. const userIds = tasks.map((item) => item.userId)
  493. const users = await this.userRepository.findBy({
  494. id: In(userIds)
  495. })
  496. tasks.forEach((task) => {
  497. const user = users.find((user) => user.id === task.userId)
  498. worksheet.addRow({
  499. id: task.id,
  500. name: task.name,
  501. userName: user.username,
  502. sent: task.sent,
  503. successCount: task.successCount,
  504. total: task.total,
  505. createdAt: task.createdAt ? moment(task.createdAt).format('YYYY-MM-DD HH:mm:ss') : '',
  506. startedAt: task.startedAt ? moment(task.startedAt).format('YYYY-MM-DD HH:mm:ss') : '',
  507. updatedAt: task.updatedAt ? moment(task.updatedAt).format('YYYY-MM-DD HH:mm:ss') : ''
  508. })
  509. })
  510. return await workbook.xlsx.writeBuffer()
  511. }
  512. async homeStatistics(req: any) {
  513. let where = {}
  514. const res = {
  515. xData: [],
  516. sentData: [],
  517. successData: [],
  518. totalData: [],
  519. todayData: {
  520. sent: '0',
  521. success: '0',
  522. total: '0',
  523. code: '0'
  524. }
  525. }
  526. const sixDaysAgo = new Date()
  527. sixDaysAgo.setDate(sixDaysAgo.getDate() - 6)
  528. sixDaysAgo.setHours(0, 0, 0, 0)
  529. if (req.user.roles.includes('superApi')) {
  530. const userIds = await this.userService.getApiInvitesIds(req.user.id)
  531. where = {
  532. userId: In(userIds),
  533. createdAt: Between(sixDaysAgo, new Date())
  534. }
  535. } else if (req.user.roles.includes('api') || req.user.roles.includes('superApi')) {
  536. const userIds = await this.userService.getInvitesIds(req.user.id)
  537. where = {
  538. userId: In(userIds),
  539. createdAt: Between(sixDaysAgo, new Date())
  540. }
  541. } else if (!req.user.roles.includes('admin')) {
  542. where = {
  543. userId: req.user.id,
  544. createdAt: Between(sixDaysAgo, new Date())
  545. }
  546. } else {
  547. where = {
  548. // userId: Not(1),
  549. createdAt: Between(sixDaysAgo, new Date())
  550. }
  551. }
  552. if (req.user.roles.includes('admin') || req.user.roles.includes('superApi')) {
  553. res.todayData.code = await this.rcsNumberRepository
  554. .createQueryBuilder()
  555. .select('count(1) as sum')
  556. .where('status = :status', { status: 'success' })
  557. .andWhere('createdAt between :start and :end', {
  558. start: startOfDay(new Date()),
  559. end: endOfDay(new Date())
  560. })
  561. .getRawOne()
  562. .then((data) => {
  563. return data.sum
  564. })
  565. }
  566. return await this.taskRepository
  567. .createQueryBuilder()
  568. .select([
  569. 'DATE(createdAt) as day',
  570. 'SUM(sent) as sent',
  571. 'SUM(successCount) as success',
  572. 'SUM(total) as total'
  573. ])
  574. .where(where)
  575. .groupBy('day')
  576. .orderBy('day', 'ASC')
  577. .getRawMany()
  578. .then((rows) => {
  579. if (rows.length > 0) {
  580. rows.forEach((item) => {
  581. const day = moment(item.day).format('MM-DD')
  582. res.xData.push(day)
  583. res.sentData.push(item.sent)
  584. res.successData.push(item.success)
  585. res.totalData.push(item.total)
  586. if (moment(new Date()).format('MM-DD').includes(day)) {
  587. res.todayData.sent = item.sent
  588. res.todayData.success = item.success
  589. res.todayData.total = item.total
  590. }
  591. })
  592. }
  593. return res
  594. })
  595. }
  596. async codeStatistics() {
  597. const res = await this.rcsNumberRepository
  598. .createQueryBuilder('rcsNumber')
  599. .select(['rcsNumber.`from` as channel', 'DATE(rcsNumber.createdAt) as day', 'COUNT(1) as sum'])
  600. .where('rcsNumber.status = :status', { status: 'success' })
  601. .andWhere('createdAt between :start and :end', {
  602. start: startOfDay(addDays(new Date(), -1)),
  603. end: endOfDay(new Date())
  604. })
  605. .groupBy('DATE(rcsNumber.createdAt), rcsNumber.from')
  606. .orderBy('rcsNumber.from', 'ASC')
  607. .getRawMany()
  608. const cur = moment(new Date()).format('MM-DD')
  609. const groupedData = res.reduce((acc, item) => {
  610. const day = moment(item.day).format('MM-DD')
  611. const channel = item.channel
  612. if (!acc[channel]) {
  613. acc[channel] = {
  614. channel: channel,
  615. todayData: 0,
  616. yesterdayData: 0
  617. }
  618. }
  619. if (day === cur) {
  620. acc[channel].todayData = item.sum
  621. } else {
  622. acc[channel].yesterdayData = item.sum
  623. }
  624. return acc
  625. }, {})
  626. return Object.values(groupedData)
  627. }
  628. async balanceStatistics() {
  629. const res = {
  630. durian: 0,
  631. cloud033: 0,
  632. cloud034: 0,
  633. cloud037: 0,
  634. cloud041: 0,
  635. cloud050: 0,
  636. xyz: 0,
  637. cowboy: 0,
  638. usapanel: 0,
  639. dashboard: 0,
  640. smspva: 0
  641. }
  642. const cloudInstance = axios.create({
  643. baseURL: 'http://52.77.17.214:9001/api/'
  644. })
  645. const xyzInstance = axios.create({
  646. baseURL: 'http://113.28.178.155:8003/api/'
  647. })
  648. const panelInstance = axios.create({
  649. baseURL: 'https://panel.hellomeetyou.com/api/'
  650. })
  651. const dashboardInstance = axios.create({
  652. baseURL: 'https://code.smscodes.io/api/sms/'
  653. })
  654. const smspvaInstance = axios.create({
  655. baseURL: 'https://api.smspva.com/activation/'
  656. })
  657. await Promise.all([
  658. (async () => {
  659. try {
  660. const durianRes = await axios
  661. .create({
  662. baseURL: 'http://8.218.211.187/out/ext_api/',
  663. headers: {
  664. uhost: 'api.durianrcs.com',
  665. uprotocol: 'http'
  666. }
  667. })
  668. .get('getUserInfo', {
  669. params: {
  670. name: 'unsnap3094',
  671. ApiKey: 'U3Jma1hkbUxXblEyL0ZYai9WWFVvdz09'
  672. }
  673. })
  674. if (durianRes.data.code === 200) {
  675. res.durian = durianRes.data.data.score
  676. }
  677. } catch (e) {}
  678. })(),
  679. (async () => {
  680. try {
  681. const cowboyRes = await axios
  682. .create({
  683. baseURL: 'http://8.218.211.187/',
  684. headers: {
  685. uhost: 'api.cowboymsg.com',
  686. uprotocol: 'http'
  687. }
  688. })
  689. .get('getUserInfo', {
  690. params: {
  691. name: 'launch',
  692. ApiKey: 'NUdVcVMxelBQTXlpcTBwbk1XQUhzQT09'
  693. }
  694. })
  695. if (cowboyRes.data.code === 200) {
  696. res.cowboy = cowboyRes.data.data.score
  697. }
  698. } catch (e) {}
  699. })(),
  700. (async () => {
  701. try {
  702. const xyz = await xyzInstance.get('v1', {
  703. params: {
  704. act: 'myinfo',
  705. token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVpZCI6MjUsInJvbGVfaWQiOjF9fQ.VU1tvG72YaXooUT-FUaQj-YWVXnVrYBad1AsoWUT4pw'
  706. }
  707. })
  708. const parts = xyz.data.split('|').map((part) => part.trim())
  709. if (parts[0] === '0') {
  710. res.xyz = parts[1] < 0 ? 0 : parts[1]
  711. }
  712. } catch (e) {}
  713. })(),
  714. (async () => {
  715. try {
  716. const cloud033Res = await cloudInstance.get('userBalance', {
  717. params: {
  718. userid: '100033',
  719. token: '1e40ca9795b1fc038db76512175d59b5'
  720. }
  721. })
  722. if (cloud033Res.data.code === '1001') {
  723. res.cloud033 = cloud033Res.data.data.integral
  724. }
  725. } catch (e) {}
  726. })(),
  727. (async () => {
  728. try {
  729. const cloud034Res = await cloudInstance.get('userBalance', {
  730. params: {
  731. userid: '100034',
  732. token: '54bdd0d9dd6707b2b40d8deb5edb1385'
  733. }
  734. })
  735. if (cloud034Res.data.code === '1001') {
  736. res.cloud034 = cloud034Res.data.data.integral
  737. }
  738. } catch (e) {}
  739. })(),
  740. (async () => {
  741. try {
  742. const cloud037Res = await cloudInstance.get('userBalance', {
  743. params: {
  744. userid: '100037',
  745. token: 'aaec6c21e54dc53b92e472df21a95bb7'
  746. }
  747. })
  748. if (cloud037Res.data.code === '1001') {
  749. res.cloud037 = cloud037Res.data.data.integral
  750. }
  751. } catch (e) {}
  752. })(),
  753. (async () => {
  754. try {
  755. const cloud041Res = await cloudInstance.get('userBalance', {
  756. params: {
  757. userid: '100041',
  758. token: '8174f3107605645d17fd6c5edc0bfb7d'
  759. }
  760. })
  761. if (cloud041Res.data.code === '1001') {
  762. res.cloud041 = cloud041Res.data.data.integral
  763. }
  764. } catch (e) {}
  765. })(),
  766. (async () => {
  767. try {
  768. const cloud050Res = await cloudInstance.get('userBalance', {
  769. params: {
  770. userid: '100050',
  771. token: '6c0f25c802b82d2a5c78f01fb627be2c'
  772. }
  773. })
  774. if (cloud050Res.data.code === '1001') {
  775. res.cloud050 = cloud050Res.data.data.integral
  776. }
  777. } catch (e) {}
  778. })(),
  779. (async () => {
  780. try {
  781. const panelRes = await panelInstance.get('account', {
  782. headers: {
  783. 'Content-Type': 'application/json',
  784. Accept: 'application/json',
  785. 'X-API-Key': 'wpJohESEZsjW1LtlyoGwZw53'
  786. }
  787. })
  788. if (panelRes.data) {
  789. res.usapanel = panelRes.data.balance
  790. }
  791. } catch (e) {}
  792. })(),
  793. (async () => {
  794. try {
  795. const dashboardRes = await dashboardInstance.get('GetBalance', {
  796. params: {
  797. key: '6619f084-363c-4518-b5b8-57b5e01a9c95'
  798. }
  799. })
  800. if (dashboardRes.data.Status === 'Success') {
  801. res.dashboard = dashboardRes.data.Balance
  802. }
  803. } catch (e) {}
  804. })(),
  805. (async () => {
  806. try {
  807. const smspvaRes = await smspvaInstance.get('balance', {
  808. headers: {
  809. apikey: 'uNW56fGr0zstfs87Xn0e1l2gCYVnb1'
  810. }
  811. })
  812. if (smspvaRes.data.statusCode === 200) {
  813. res.smspva = smspvaRes.data.data.balance
  814. }
  815. } catch (e) {}
  816. })()
  817. ])
  818. return res
  819. }
  820. async hourSentStatistics() {
  821. const twelveHoursAgo = new Date()
  822. twelveHoursAgo.setHours(twelveHoursAgo.getHours() - 25)
  823. return await this.taskItemRepository
  824. .createQueryBuilder()
  825. .select(['COUNT(*) AS sent', "DATE_FORMAT(sendAt, '%Y-%m-%d %H:00:00') AS hour"])
  826. .where('sendAt BETWEEN :start AND :end', { start: twelveHoursAgo, end: new Date() })
  827. .groupBy('hour')
  828. .orderBy('hour', 'DESC')
  829. .getRawMany()
  830. }
  831. async numStatistics() {
  832. const res = {
  833. totalHoursYesterday: 0,
  834. totalHoursToday: 0,
  835. orderCountYesterday: 0,
  836. orderCountToday: 0,
  837. hourData: 0
  838. }
  839. await Promise.all([
  840. (async () => {
  841. try {
  842. // 查询昨日订单数
  843. const yesterdayOrderCount = await this.taskRepository
  844. .createQueryBuilder()
  845. .select('COUNT(1)', 'sum')
  846. .where('task.startedAt >= CURDATE() - INTERVAL 1 DAY')
  847. .andWhere('task.startedAt < CURDATE()')
  848. .getRawOne()
  849. res.orderCountYesterday = yesterdayOrderCount.sum
  850. } catch (e) {}
  851. })(),
  852. (async () => {
  853. try {
  854. // 查询今日订单数
  855. const todayOrderCount = await this.taskRepository
  856. .createQueryBuilder()
  857. .select('COUNT(1)', 'sum')
  858. .where('task.startedAt >= CURDATE()')
  859. .andWhere('task.startedAt < CURDATE() + INTERVAL 1 DAY')
  860. .getRawOne()
  861. res.orderCountToday = todayOrderCount.sum
  862. } catch (e) {}
  863. })(),
  864. (async () => {
  865. try {
  866. res.hourData = await this.sentHourStatistics()
  867. } catch (e) {}
  868. })()
  869. ])
  870. return res
  871. }
  872. async sentHourStatistics() {
  873. const result = await this.taskItemRepository.query(`
  874. SELECT SUM(c) / 60 AS hour
  875. FROM (
  876. SELECT 1 AS c, UNIX_TIMESTAMP(sendAt) DIV 60 * 60 AS "time"
  877. FROM task_item
  878. WHERE sendAt >= CURDATE() - INTERVAL 1 DAY
  879. AND sendAt < CURDATE()
  880. AND status != 'idle'
  881. GROUP BY time
  882. ) tmp
  883. `)
  884. return Number(result[0]?.hour) || 0
  885. }
  886. async sentCountryStatistics(req: any) {
  887. let where = {}
  888. if (req.user.roles.includes('api') || req.user.roles.includes('superApi')) {
  889. const userIds = await this.userService.getInvitesIds(req.user.id)
  890. where = {
  891. userId: In(userIds)
  892. }
  893. } else if (!req.user.roles.includes('admin')) {
  894. where = {
  895. userId: req.user.id
  896. }
  897. }
  898. // 昨天
  899. const yesterday = new Date()
  900. yesterday.setDate(yesterday.getDate() - 1)
  901. yesterday.setHours(0, 0, 0, 0)
  902. const res = await this.taskRepository
  903. .createQueryBuilder()
  904. .select(['sum(total) as value', 'country as name'])
  905. .where(where)
  906. .andWhere('country is not null')
  907. .andWhere('status = :status', { status: TaskStatus.COMPLETED })
  908. .andWhere('createdAt between :start and :end', {
  909. start: startOfDay(yesterday),
  910. end: endOfDay(yesterday)
  911. })
  912. .groupBy('country')
  913. .orderBy('value', 'DESC')
  914. .getRawMany()
  915. const sixDaysAgo = new Date()
  916. sixDaysAgo.setDate(sixDaysAgo.getDate() - 7)
  917. sixDaysAgo.setHours(0, 0, 0, 0)
  918. const totalRes = await this.taskRepository
  919. .createQueryBuilder()
  920. .select(['sum(total) as value', 'country as name'])
  921. .where(where)
  922. .andWhere('country is not null')
  923. .andWhere('createdAt between :start and :end', {
  924. start: sixDaysAgo,
  925. end: new Date()
  926. })
  927. .groupBy('country')
  928. .orderBy('value', 'DESC')
  929. .getRawMany()
  930. return {
  931. completedData: res,
  932. totalData: totalRes
  933. }
  934. }
  935. async backupStatistics() {
  936. const res = {
  937. todayData: null,
  938. totalData: null,
  939. yesterdayData: null
  940. }
  941. await Promise.all([
  942. (async () => {
  943. try {
  944. // 总备份数量,剩余未使用备份数量
  945. res.totalData = await this.rcsNumberRepository
  946. .createQueryBuilder()
  947. .select('COUNT(1)', 'count')
  948. .addSelect('stockFlag', 'stockFlag')
  949. .where('stockFlag IN (:...stockFlags)', { stockFlags: [1, 2, 3] })
  950. .groupBy('stockFlag')
  951. .getRawMany()
  952. } catch (e) {
  953. console.log(e)
  954. }
  955. })(),
  956. (async () => {
  957. try {
  958. // 昨日备份使用成功数量,失败数量
  959. res.yesterdayData = await this.rcsNumberRepository
  960. .createQueryBuilder()
  961. .select('COUNT(1)', 'count')
  962. .addSelect('stockFlag', 'stockFlag')
  963. .where('createdAt >= CURDATE() - INTERVAL 1 DAY')
  964. .andWhere('createdAt < CURDATE()')
  965. .andWhere('stockFlag IN (:...stockFlags)', { stockFlags: [1, 2, 3] })
  966. .groupBy('stockFlag')
  967. .getRawMany()
  968. } catch (e) {
  969. console.log(e)
  970. }
  971. })(),
  972. (async () => {
  973. try {
  974. // 当天备份数量
  975. res.todayData = await this.rcsNumberRepository
  976. .createQueryBuilder()
  977. .select('COUNT(1)', 'count')
  978. .addSelect('stockFlag', 'stockFlag')
  979. .where('createdAt >= CURDATE()')
  980. .andWhere('stockFlag IN (:...stockFlags)', { stockFlags: [1, 2, 3] })
  981. .groupBy('stockFlag')
  982. .getRawMany()
  983. } catch (e) {
  984. console.log(e)
  985. }
  986. })()
  987. ])
  988. return [
  989. {
  990. flag: '剩余备份',
  991. yesterday: res.yesterdayData?.find((item) => item.stockFlag === 1)?.count ?? 0,
  992. today: res.todayData?.find((item) => item.stockFlag === 1)?.count ?? 0,
  993. total: res.totalData?.find((item) => item.stockFlag === 1)?.count ?? 0
  994. },
  995. {
  996. flag: '使用成功',
  997. yesterday: res.yesterdayData?.find((item) => item.stockFlag === 2)?.count ?? 0,
  998. today: res.todayData?.find((item) => item.stockFlag === 2)?.count ?? 0,
  999. total: res.totalData?.find((item) => item.stockFlag === 2)?.count ?? 0
  1000. },
  1001. {
  1002. flag: '使用失败',
  1003. yesterday: res.yesterdayData?.find((item) => item.stockFlag === 3)?.count ?? 0,
  1004. today: res.todayData?.find((item) => item.stockFlag === 3)?.count ?? 0,
  1005. total: res.totalData?.find((item) => item.stockFlag === 3)?.count ?? 0
  1006. }
  1007. ]
  1008. }
  1009. async getSuccessNum(id: number) {
  1010. return await this.taskItemRepository.count({
  1011. where: {
  1012. taskId: id,
  1013. status: TaskItemStatus.SUCCESS,
  1014. embed: false
  1015. }
  1016. })
  1017. }
  1018. async getToBeSentNum(id: number) {
  1019. let total = 0
  1020. const maxParallel = await this.getConfig('max_parallel', 1)
  1021. const pendingNum = await this.taskRepository.count({
  1022. where: {
  1023. status: TaskStatus.PENDING
  1024. }
  1025. })
  1026. if (pendingNum < maxParallel) {
  1027. return total
  1028. } else {
  1029. // 任务队列
  1030. const tasks = await this.taskRepository.find({
  1031. where: {
  1032. status: TaskStatus.QUEUED
  1033. },
  1034. order: {
  1035. startedAt: 'ASC'
  1036. }
  1037. })
  1038. if (tasks.length > 0) {
  1039. let list = []
  1040. for (let i = 0; i < tasks.length; i++) {
  1041. if (tasks[i].id === id) {
  1042. break
  1043. } else {
  1044. list.push(tasks[i].listId)
  1045. }
  1046. }
  1047. if (list.length > 0) {
  1048. // 队列中当前任务之前剩余任务发送数
  1049. const number = await this.phoneRepository.countBy({
  1050. listId: In(list)
  1051. })
  1052. total += number
  1053. }
  1054. }
  1055. // 正在执行的任务
  1056. const curTasks = await this.taskRepository.find({
  1057. where: {
  1058. status: TaskStatus.PENDING
  1059. }
  1060. })
  1061. if (curTasks.length > 0) {
  1062. const ids = curTasks.map((task) => task.id)
  1063. const number = await this.taskItemRepository.countBy({
  1064. taskId: In(ids),
  1065. status: TaskItemStatus.IDLE,
  1066. embed: false
  1067. })
  1068. total += number
  1069. }
  1070. return total
  1071. }
  1072. }
  1073. async getConfig(name, defValue) {
  1074. try {
  1075. return await this.sysConfigService.getNumber(name, defValue)
  1076. } catch (e) {
  1077. Logger.error('Error getting rcs wait time', e.stack, this.TAG)
  1078. }
  1079. return defValue
  1080. }
  1081. async updateTaskItemStatus(taskIds: number[], status: TaskItemStatus) {
  1082. await this.taskItemRepository.update(
  1083. {
  1084. id: In(taskIds)
  1085. },
  1086. {
  1087. status: status
  1088. }
  1089. )
  1090. }
  1091. async updateTaskItemStatusAndSendAt(taskIds: number[], status: TaskItemStatus) {
  1092. await this.taskItemRepository.update(
  1093. {
  1094. id: In(taskIds)
  1095. },
  1096. {
  1097. status: status,
  1098. sendAt: new Date()
  1099. }
  1100. )
  1101. }
  1102. async getCost(task: Task, user: Users) {
  1103. const number: number = await this.phoneListService.findCountByListId(task.listId)
  1104. // 费用 = 费率*需要发送量
  1105. // const rate = new Decimal(String(user.rate))
  1106. // const num = new Decimal(String(number))
  1107. // const cost = rate.mul(num)
  1108. return new Decimal(number)
  1109. }
  1110. @Interval(2000)
  1111. async scheduleTask() {
  1112. this.lock
  1113. .acquire(
  1114. 'dispatchTask',
  1115. async () => {
  1116. const maxParallel = await this.getConfig('max_parallel', 1)
  1117. const batchSize = 200
  1118. let tasks = await this.taskRepository.find({
  1119. where: {
  1120. status: TaskStatus.PENDING
  1121. },
  1122. order: {
  1123. startedAt: 'ASC'
  1124. },
  1125. take: maxParallel
  1126. })
  1127. // 少补
  1128. if (tasks.length < maxParallel) {
  1129. const nextTasks = await this.taskRepository.find({
  1130. where: {
  1131. status: TaskStatus.QUEUED
  1132. },
  1133. order: {
  1134. startedAt: 'ASC',
  1135. id: 'ASC'
  1136. }
  1137. })
  1138. if (nextTasks.length > 0) {
  1139. const userIdMap = new Map()
  1140. tasks.forEach((task) => {
  1141. userIdMap.set(task.userId, (userIdMap.get(task.userId) || 0) + 1)
  1142. })
  1143. // nextTasks筛选,从排队任务中筛选出最多2个同用户下的任务
  1144. let filteredTasks = []
  1145. const userIds = {}
  1146. const limit = maxParallel - tasks.length
  1147. for (const task of nextTasks) {
  1148. if (!userIds[task.userId]) {
  1149. userIds[task.userId] = 0
  1150. }
  1151. if ((userIdMap.get(task.userId) || 0) + userIds[task.userId] < 2) {
  1152. filteredTasks.push(task)
  1153. userIds[task.userId]++
  1154. }
  1155. if (filteredTasks.length >= limit) {
  1156. break
  1157. }
  1158. }
  1159. const nextTasksIds = filteredTasks.map((t) => t.id)
  1160. if (nextTasksIds.length === 0) {
  1161. nextTasksIds.push(...nextTasks.map((t) => t.id).slice(0, limit))
  1162. }
  1163. await this.taskRepository.update({ id: In(nextTasksIds) }, { status: TaskStatus.PENDING })
  1164. tasks.push(...filteredTasks)
  1165. }
  1166. }
  1167. // 专线任务,插队任务
  1168. const cuttingTasks = await this.taskRepository.find({
  1169. where: {
  1170. status: In([TaskStatus.CUTTING, TaskStatus.VIP])
  1171. }
  1172. })
  1173. if (cuttingTasks.length > 0) {
  1174. tasks.push(...cuttingTasks)
  1175. }
  1176. if (tasks.length === 0) return
  1177. tasks = tasks.sort(() => Math.random() - 0.5)
  1178. const devices = await this.deviceService.findAllAvailableDevices()
  1179. if (devices.length === 0) return
  1180. const countryMapping = await this.countryConfigService.getAllDestConfig(tasks.map((t) => t.country))
  1181. const res = tasks.map((task) => {
  1182. return {
  1183. task,
  1184. useCountry: countryMapping.find((c) => c.id === task.country)?.useCountry || ['any'],
  1185. exclude: countryMapping.find((c) => c.id === task.country)?.exclude || [],
  1186. devices: []
  1187. }
  1188. })
  1189. devices.forEach((device) => {
  1190. let candidateTasks = res.filter(
  1191. (r) => Math.ceil((r.task.total - r.task.sent) / 5) > r.devices.length
  1192. )
  1193. if (device.matchCountry && device.pinCountry) {
  1194. candidateTasks = candidateTasks.filter((r) => {
  1195. return (
  1196. r.useCountry.includes(device.pinCountry.toUpperCase()) &&
  1197. !r.exclude.includes(device.pinCountry.toUpperCase())
  1198. )
  1199. })
  1200. } else {
  1201. candidateTasks = candidateTasks.filter((r) => {
  1202. return (
  1203. (r.useCountry.includes('any') ||
  1204. r.useCountry.includes(device.currentCountry?.toUpperCase())) &&
  1205. !r.exclude.includes(device.currentCountry?.toUpperCase())
  1206. )
  1207. })
  1208. }
  1209. if (candidateTasks.length > 0) {
  1210. candidateTasks.sort((a, b) => {
  1211. return a.devices.length - b.devices.length
  1212. })
  1213. candidateTasks[0].devices.push(device)
  1214. }
  1215. })
  1216. for (let r of res) {
  1217. if (r.devices.length > 0) {
  1218. await this.dispatchTask(r.task, r.devices)
  1219. }
  1220. }
  1221. },
  1222. {
  1223. timeout: 1
  1224. }
  1225. )
  1226. .catch((e) => {
  1227. if (e.message.includes('timed out')) return
  1228. Logger.error('Error dispatchTask', e.stack, this.TAG)
  1229. })
  1230. }
  1231. refineContent(content: string) {
  1232. const regex =
  1233. /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g
  1234. const urls = []
  1235. let match
  1236. while ((match = regex.exec(content)) !== null) {
  1237. if (!urls.includes(match[0])) {
  1238. urls.push(match[0])
  1239. }
  1240. }
  1241. urls.forEach((url) => {
  1242. try {
  1243. const u = new URL(url)
  1244. u.searchParams.append(randomstring.generate(6), randomstring.generate(6))
  1245. content = content.replaceAll(url, u.toString())
  1246. } catch (error) {
  1247. Logger.error('Error parsing url', error.stack, this.TAG)
  1248. }
  1249. })
  1250. return content
  1251. }
  1252. async dispatchTask(task: Task, devices: Device[]) {
  1253. const taskItems = await this.taskItemRepository.find({
  1254. where: {
  1255. taskId: task.id,
  1256. status: TaskItemStatus.IDLE
  1257. },
  1258. take: devices.length * 5
  1259. })
  1260. if (taskItems.length === 0) {
  1261. return
  1262. }
  1263. if (devices.length === 0) {
  1264. return
  1265. }
  1266. devices = devices.splice(0, Math.ceil(taskItems.length / 5))
  1267. const taskConfig = {
  1268. rcsWait: task.rcsWait || (await this.getConfig('rcs_wait', 2000)),
  1269. rcsInterval: task.rcsInterval || (await this.getConfig('rcs_interval', 3000)),
  1270. cleanCount: task.cleanCount || (await this.getConfig('clean_count', 20)),
  1271. requestNumberInterval: task.requestNumberInterval || (await this.getConfig('request_number_interval', 100)),
  1272. checkConnection: task.checkConnection,
  1273. useBackup: task.useBackup,
  1274. e2ee: task.e2ee,
  1275. e2eeTimeout: task.e2eeTimeout || (await this.getConfig('e2ee_timeout', 5000))
  1276. }
  1277. await this.updateTaskItemStatusAndSendAt(
  1278. taskItems.map((i) => i.id),
  1279. TaskItemStatus.PENDING
  1280. )
  1281. await this.deviceService.updateDevice(
  1282. devices.map((d) => d.id),
  1283. { busy: true }
  1284. )
  1285. Promise.all(
  1286. devices.map(async (device, i) => {
  1287. const items = taskItems
  1288. .slice(i * 5, i * 5 + 5)
  1289. .map((item) => ({ ...item, message: this.getMessage(task), img: task.img }))
  1290. if (items.length === 0) return
  1291. try {
  1292. const res: any = await this.eventsGateway.sendForResult(
  1293. {
  1294. id: randomUUID(),
  1295. action: 'task',
  1296. data: {
  1297. config: { ...taskConfig, ...(device.configOverrides || {}) },
  1298. tasks: items,
  1299. taskId: task.id
  1300. }
  1301. },
  1302. device.socketId
  1303. )
  1304. Logger.log(`task result: ${JSON.stringify(res)}`, this.TAG)
  1305. if (res.success?.length > 0) {
  1306. await this.updateTaskItemStatus(res.success, TaskItemStatus.SUCCESS)
  1307. }
  1308. if (res.fail?.length > 0) {
  1309. await this.updateTaskItemStatus(res.fail, TaskItemStatus.FAIL)
  1310. }
  1311. if (res.retry?.length > 0) {
  1312. await this.updateTaskItemStatus(res.retry, TaskItemStatus.IDLE)
  1313. }
  1314. if (res instanceof Array) {
  1315. const results = res as TaskResult[]
  1316. for (let result of results) {
  1317. await this.taskItemRepository.update(
  1318. { id: result.id },
  1319. {
  1320. status: result.sent ? TaskItemStatus.SUCCESS : TaskItemStatus.FAIL,
  1321. delivery: result.delivery,
  1322. numberId: result.numberId
  1323. }
  1324. )
  1325. }
  1326. }
  1327. } catch (e) {
  1328. Logger.error('Error running task 3', e.stack, this.TAG)
  1329. await this.updateTaskItemStatus(
  1330. items.map((i) => i.id),
  1331. TaskItemStatus.IDLE
  1332. )
  1333. }
  1334. })
  1335. ).then(async () => {
  1336. const counts = (
  1337. await this.taskItemRepository.manager.query(
  1338. `select status, count(*) as count
  1339. from task_item
  1340. where taskId = ${task.id} and embed = 0
  1341. group by status`
  1342. )
  1343. ).reduce((acc, item) => {
  1344. acc[item.status] = parseInt(item.count)
  1345. return acc
  1346. }, {})
  1347. Logger.log('Task counts', JSON.stringify(counts), this.TAG)
  1348. const successCount = counts.success || 0
  1349. const failCount = counts.fail || 0
  1350. const pendingCount = counts.pending || 0
  1351. const idleCount = counts.idle || 0
  1352. const finish = pendingCount === 0 && idleCount === 0
  1353. // 送达率
  1354. const deliveryCount = await this.taskItemRepository.countBy({ taskId: task.id, delivery: 1 })
  1355. const data: Partial<Task> = {
  1356. sent: successCount + failCount,
  1357. successCount: successCount,
  1358. successRate:
  1359. successCount + failCount > 0
  1360. ? ((successCount / (successCount + failCount)) * 100).toFixed(1) + '%'
  1361. : '0%',
  1362. deliveryCount: deliveryCount,
  1363. deliveryRate:
  1364. deliveryCount > 0 ? ((deliveryCount / (successCount + failCount)) * 100).toFixed(1) + '%' : '0%'
  1365. }
  1366. if (finish) {
  1367. data.status = TaskStatus.COMPLETED
  1368. await this.updateSend(task.userId)
  1369. }
  1370. await this.taskRepository.update({ id: task.id }, data)
  1371. })
  1372. }
  1373. async checkPendingTaskNum() {
  1374. return await this.taskRepository.countBy({
  1375. status: TaskStatus.PENDING
  1376. })
  1377. }
  1378. @Interval(10000)
  1379. async fixDeadTask() {
  1380. const tasks = await this.taskRepository.findBy({
  1381. status: In([TaskStatus.PENDING, TaskStatus.CUTTING, TaskStatus.VIP])
  1382. })
  1383. for (let task of tasks) {
  1384. const items = await this.taskItemRepository.findBy({
  1385. taskId: task.id,
  1386. status: TaskItemStatus.PENDING,
  1387. sendAt: LessThan(addMinutes(new Date(), -10))
  1388. })
  1389. if (items?.length > 0) {
  1390. await this.updateTaskItemStatus(
  1391. items.map((i) => i.id),
  1392. TaskItemStatus.IDLE
  1393. )
  1394. }
  1395. }
  1396. }
  1397. @Interval(60000)
  1398. async changeCheckAvailabilityNumbers() {
  1399. let config: SysConfig
  1400. try {
  1401. config = await this.sysConfigService.findByName('check_availability_numbers')
  1402. } catch (e) {
  1403. Logger.error('Error getting check_availability_numbers', e.stack, this.TAG)
  1404. config = new SysConfig()
  1405. config.name = 'check_availability_numbers'
  1406. }
  1407. for (let i = 0; i < 99; i++) {
  1408. const items = await this.taskItemRepository
  1409. .createQueryBuilder()
  1410. .select()
  1411. .where('status = :status', { status: TaskItemStatus.SUCCESS })
  1412. .andWhere('sendAt > :sendAt', { sendAt: addHours(new Date(), -6 * (i + 1)) })
  1413. .orderBy('RAND()')
  1414. .limit(5)
  1415. .getMany()
  1416. if (5 == items.length) {
  1417. config.value = items.map((i) => i.number).join(',')
  1418. break
  1419. }
  1420. }
  1421. await this.sysConfigService.save(config)
  1422. }
  1423. public async updateSend(id: number) {
  1424. const sum = await this.balanceService.sumAmount(id, BalanceType.CONSUMPTION)
  1425. const user = await this.userRepository.findOneBy({ id })
  1426. user.send = sum.toNumber()
  1427. return await this.userRepository.save(user)
  1428. }
  1429. @Cron('0 0,30 * * * *')
  1430. async scheduledTaskExecution() {
  1431. console.log('The scheduled task starts,', new Date())
  1432. const tasks = await this.taskRepository.findBy({
  1433. status: TaskStatus.SCHEDULED,
  1434. startedAt: LessThanOrEqual(new Date())
  1435. })
  1436. for (const task of tasks) {
  1437. try {
  1438. await this.startTask(task.id)
  1439. console.log(`Task ${task.id} started successfully.`)
  1440. } catch (error) {
  1441. console.error(`Error starting task ${task.id}:`, error)
  1442. }
  1443. }
  1444. console.log('The scheduled task is executed.')
  1445. return tasks
  1446. }
  1447. }