task.service.ts 64 KB

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