|
|
@@ -2,7 +2,8 @@ const path = require('path')
|
|
|
const axios = require('axios')
|
|
|
const OSS = require('ali-oss')
|
|
|
const mq = require('../../plugin/mq')
|
|
|
-const { assertRunforgeTaskIngress } = require('../../plugin/mq/runforgeTaskMq')
|
|
|
+const { mq: mqName } = require('../../plugin/mq/mqPrefix')
|
|
|
+const { assertRunforgeTaskIngress, TASK_QUEUE } = require('../../plugin/mq/runforgeTaskMq')
|
|
|
const db = require('../../plugin/DataBase/db')
|
|
|
const Redis = require('../../plugin/DataBase/Redis')
|
|
|
const EmailTemplate = require('../../plugin/Email/emailTemplate')
|
|
|
@@ -17,6 +18,7 @@ const {
|
|
|
dataDecrypt,
|
|
|
dataSign
|
|
|
} = require('../../plugin/Lepao/Crypto')
|
|
|
+const generateGyrFromPath = require('../../plugin/Lepao/generateGyrFromPath')
|
|
|
|
|
|
const Logger = require('../Logger')
|
|
|
|
|
|
@@ -32,10 +34,10 @@ class Worker {
|
|
|
|
|
|
this.baseUrl = 'https://lepao.ctbu.edu.cn/v3/api.php'
|
|
|
|
|
|
- this.taskQueue = 'runforge_task_queue'
|
|
|
- this.resultQueue = 'runforge_task_result_queue'
|
|
|
- this.deadQueue = 'runforge_task_dead_queue'
|
|
|
- this.noticeQueue = 'runforge_message_queue'
|
|
|
+ this.taskQueue = TASK_QUEUE
|
|
|
+ this.resultQueue = mqName('runforge_task_result_queue')
|
|
|
+ this.deadQueue = mqName('runforge_task_dead_queue')
|
|
|
+ this.noticeQueue = mqName('runforge_message_queue')
|
|
|
|
|
|
this.channelName = 'lepao_worker'
|
|
|
|
|
|
@@ -426,7 +428,10 @@ class Worker {
|
|
|
const maxPathRetry = 20 // 自动获取路径失败最大重试次数
|
|
|
let pathRetry = 0
|
|
|
let pointData = null
|
|
|
+ let pathData = null
|
|
|
+ let newPathData = null
|
|
|
let ossPath = null
|
|
|
+ let ossSts = null
|
|
|
let userData = null
|
|
|
let pathId = null
|
|
|
let runZoneId = 0
|
|
|
@@ -440,6 +445,7 @@ class Worker {
|
|
|
|
|
|
userData = await this.handlers['lepao.getUserData'](req, ctx)
|
|
|
|
|
|
+
|
|
|
// 立刻合并账号凭证,保证后续任意 throw 时 finally 里 syncRunCount 不会用空 token 调 getRecord
|
|
|
req = {
|
|
|
...req,
|
|
|
@@ -455,6 +461,16 @@ class Worker {
|
|
|
}
|
|
|
await Redis.set(progressKey, req.account, { EX: 1800 })
|
|
|
|
|
|
+ ossSts = await this.handlers['lepao.getOssSts'](req, ctx)
|
|
|
+ if (!ossSts?.bucket || !ossSts?.AccessKeyId || !ossSts?.AccessKeySecret || !ossSts?.SecurityToken) {
|
|
|
+ throw new Error('获取 OSS 凭证失败,请联系客服或稍后再试')
|
|
|
+ }
|
|
|
+
|
|
|
+ req = {
|
|
|
+ ...req,
|
|
|
+ ossSts
|
|
|
+ }
|
|
|
+
|
|
|
// 晚上10点后提前
|
|
|
let run_end_time = Math.floor(Date.now() / 1000) - 300 // 提前5分钟
|
|
|
let hour = new Date().getHours()
|
|
|
@@ -482,14 +498,16 @@ class Worker {
|
|
|
try {
|
|
|
// 2️⃣ 获取路径(仅路径选择失败时重试)
|
|
|
const pathRes = await this.handlers['lepao.getPath'](req, ctx)
|
|
|
- pathId = pathRes.path_id
|
|
|
+ pathData = pathRes.pathData
|
|
|
+ pathId = pathData?.id || null
|
|
|
+ newPathData = getPathData(pathData.data, req.run_end_time, pathData.time)
|
|
|
|
|
|
// 3️⃣ 切换跑区
|
|
|
- const zoneRes = await this.handlers['lepao.setZone']({ ...req, random_id: pathId }, ctx)
|
|
|
+ const zoneRes = await this.handlers['lepao.setZone']({ ...req, pathData }, ctx)
|
|
|
runZoneId = zoneRes?.run_zone_id || 0
|
|
|
|
|
|
// 4️⃣ 上传 OSS 文件、生成打卡点
|
|
|
- const uploadRes = await this.handlers['lepao.uploadOssFile']({ ...req, random_id: pathId }, ctx)
|
|
|
+ const uploadRes = await this.handlers['lepao.uploadOssFile']({ ...req, pathData, newPathData }, ctx)
|
|
|
ossPath = uploadRes.oss_path
|
|
|
pointData = uploadRes.point_data
|
|
|
|
|
|
@@ -518,7 +536,7 @@ class Worker {
|
|
|
// 5️⃣ 提交跑步数据
|
|
|
bindRes = await this.handlers['lepao.bindData']({
|
|
|
...req,
|
|
|
- random_id: pathId,
|
|
|
+ pathData,
|
|
|
run_zone_id: runZoneId,
|
|
|
record_file: ossPath,
|
|
|
point_data: pointData
|
|
|
@@ -536,6 +554,14 @@ class Worker {
|
|
|
if (!runResult.ok) {
|
|
|
throw new Error(runResult.reason)
|
|
|
}
|
|
|
+ if (bindRes && bindRes.data && bindRes.data.record_id) {
|
|
|
+ const gyrRes = await this.handlers['lepao.uploadGyrOssFile']({ ...req, newPathData, record_id: bindRes.data.record_id }, ctx)
|
|
|
+ if (gyrRes?.status === 1) {
|
|
|
+ this.logger.info(`${req.account}上传加速度数据成功!`)
|
|
|
+ } else {
|
|
|
+ this.logger.error(`${req.account}上传加速度数据失败!原因:${gyrRes.info || '未知错误'}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
// 同步乐跑次数(通知里要带 total_num / term_num,与 getRecord 一致)
|
|
|
const syncResult = await this.syncRunCount(req, ctx)
|
|
|
@@ -585,19 +611,6 @@ class Worker {
|
|
|
}, { id: `${traceId}:notice:fail` })
|
|
|
}
|
|
|
|
|
|
- // 将失败消息发送到结果队列或死信队列
|
|
|
- if (ctx.channel) {
|
|
|
- await this.sendResult(ctx.channel, {
|
|
|
- id: req.taskId,
|
|
|
- success: false,
|
|
|
- error: err.message
|
|
|
- })
|
|
|
- await ctx.channel.sendToQueue(
|
|
|
- this.deadQueue,
|
|
|
- Buffer.from(JSON.stringify({ ...req, error: err.message })),
|
|
|
- { persistent: true }
|
|
|
- )
|
|
|
- }
|
|
|
throw err
|
|
|
} finally {
|
|
|
await Redis.del(`lepaoProgress:${req.account}`)
|
|
|
@@ -860,7 +873,7 @@ class Worker {
|
|
|
|
|
|
this.logger.info(`${account}路径参数: area=${area ?? '随机'}, max_distance=${max}, min_distance=${min}`)
|
|
|
|
|
|
- let pathSql = 'SELECT id FROM path_data WHERE state = 1 AND distance < ? AND distance > ? '
|
|
|
+ let pathSql = 'SELECT * FROM path_data WHERE state = 1 AND distance < ? AND distance > ? '
|
|
|
const pathParams = [max, min]
|
|
|
|
|
|
if (area) {
|
|
|
@@ -886,7 +899,7 @@ class Worker {
|
|
|
|
|
|
this.logger.info(`${account}路径选中id=${randomPath.id},计数加1成功`)
|
|
|
|
|
|
- return { path_id: randomPath.id }
|
|
|
+ return { pathData: randomPath }
|
|
|
})
|
|
|
|
|
|
/* ---------------- 获取跑步记录 ---------------- */
|
|
|
@@ -930,15 +943,9 @@ class Worker {
|
|
|
'重庆工商大学茶园校区': 6
|
|
|
}
|
|
|
|
|
|
- const record = await db.query(
|
|
|
- 'SELECT run_zone_name FROM path_data WHERE id = ?',
|
|
|
- [req.random_id]
|
|
|
- )
|
|
|
- if (!record || record.length === 0) {
|
|
|
- throw new Error('跑区不存在')
|
|
|
- }
|
|
|
+ const { pathData } = req
|
|
|
|
|
|
- const runZoneId = runZoneMap[record[0].run_zone_name]
|
|
|
+ const runZoneId = runZoneMap[pathData.run_zone_name]
|
|
|
if (!runZoneId) throw new Error('跑区不存在')
|
|
|
|
|
|
const raw = {
|
|
|
@@ -997,17 +1004,9 @@ class Worker {
|
|
|
|
|
|
/* ---------------- 上传 OSS 文件 ---------------- */
|
|
|
this.register('lepao.uploadOssFile', async (req, ctx) => {
|
|
|
- const pathRow = await db.query(
|
|
|
- 'SELECT * FROM path_data WHERE id=?',
|
|
|
- [req.random_id]
|
|
|
- )
|
|
|
- if (!pathRow || pathRow.length === 0) {
|
|
|
- throw new Error('路径数据不存在')
|
|
|
- }
|
|
|
- const pathData = pathRow[0]
|
|
|
+ const { pathData, newPathData, ossSts: sts } = req
|
|
|
|
|
|
// 处理跑步路径
|
|
|
- const newPathData = getPathData(pathData.data, req.run_end_time, pathData.time)
|
|
|
const pathResult = dataEncrypt(JSON.stringify(newPathData))
|
|
|
|
|
|
// 获取跑步规则参数
|
|
|
@@ -1034,11 +1033,6 @@ class Worker {
|
|
|
throw err
|
|
|
}
|
|
|
|
|
|
- const sts = await this.handlers['lepao.getOssSts'](req, ctx)
|
|
|
- if (!sts?.bucket || !sts?.AccessKeyId || !sts?.AccessKeySecret || !sts?.SecurityToken) {
|
|
|
- throw new Error('获取 OSS STS 失败')
|
|
|
- }
|
|
|
-
|
|
|
const now = new Date()
|
|
|
const yyyy = now.getFullYear()
|
|
|
const mm = String(now.getMonth() + 1).padStart(2, '0')
|
|
|
@@ -1060,20 +1054,65 @@ class Worker {
|
|
|
return { oss_path: ossPath, point_data: point_data }
|
|
|
})
|
|
|
|
|
|
- /* ---------------- 提交跑步数据 ---------------- */
|
|
|
- this.register('lepao.bindData', async (req, ctx) => {
|
|
|
- if (req?.random_id === undefined || req?.random_id === null || req?.random_id === '') {
|
|
|
- throw new Error('提交跑步数据失败:缺少 random_id')
|
|
|
+ this.register('lepao.uploadGyrOssFile', async (req, ctx) => {
|
|
|
+ const { newPathData, ossSts: sts, record_id } = req
|
|
|
+
|
|
|
+ // 生成加速度数据
|
|
|
+ const gyrData = generateGyrFromPath(newPathData)
|
|
|
+ if (!Array.isArray(gyrData) || gyrData.length === 0) {
|
|
|
+ this.logger.error('生成加速度数据失败')
|
|
|
+ return { status: 0, info: '生成加速度数据失败' }
|
|
|
}
|
|
|
|
|
|
- const pathRow = await db.query(
|
|
|
- 'SELECT * FROM path_data WHERE id=?',
|
|
|
- [req.random_id]
|
|
|
- )
|
|
|
- if (!pathRow || pathRow.length === 0) {
|
|
|
- throw new Error(`提交跑步数据失败:未找到路径数据(random_id=${req.random_id})`)
|
|
|
+ const now = new Date()
|
|
|
+ const yyyy = now.getFullYear()
|
|
|
+ const mm = String(now.getMonth() + 1).padStart(2, '0')
|
|
|
+ const dd = String(now.getDate()).padStart(2, '0')
|
|
|
+ const formattedToday = `${yyyy}-${mm}-${dd}`
|
|
|
+ const boundary = String(Date.now())
|
|
|
+ const timestamp = String(Date.now())
|
|
|
+ const ossPath = `Public/Upload/file/run_gyroscope/${boundary.slice(-3)}/${formattedToday}/${timestamp}-${Math.floor(Math.random() * 150)}.txt`
|
|
|
+ const client = new OSS({
|
|
|
+ bucket: sts.bucket,
|
|
|
+ region: sts.region || 'oss-cn-hangzhou',
|
|
|
+ accessKeyId: sts.AccessKeyId,
|
|
|
+ accessKeySecret: sts.AccessKeySecret,
|
|
|
+ stsToken: sts.SecurityToken,
|
|
|
+ secure: true
|
|
|
+ })
|
|
|
+
|
|
|
+ await client.put(ossPath, Buffer.from(JSON.stringify(gyrData), 'utf-8'))
|
|
|
+
|
|
|
+ const data = {
|
|
|
+ uid: req.uid,
|
|
|
+ token: req.token,
|
|
|
+ school_id: req.school_id,
|
|
|
+ term_id: 0,
|
|
|
+ course_id: 0,
|
|
|
+ class_id: 0,
|
|
|
+ student_num: req.student_id,
|
|
|
+ card_id: req.student_id,
|
|
|
+ timestamp: this.lepaoTimestamp(),
|
|
|
+ version: 1,
|
|
|
+ nonce: String(Math.floor(Math.random() * 900000 + 100000)),
|
|
|
+ ostype: 5,
|
|
|
+ record_id: record_id,
|
|
|
+ gyroscope_file: ossPath
|
|
|
}
|
|
|
- const pathData = pathRow[0]
|
|
|
+
|
|
|
+ data.sign = dataSign(data)
|
|
|
+
|
|
|
+ return this.request(
|
|
|
+ ctx.traceId,
|
|
|
+ 'bindData',
|
|
|
+ this.api('/Run2/gyroscope'),
|
|
|
+ data
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ /* ---------------- 提交跑步数据 ---------------- */
|
|
|
+ this.register('lepao.bindData', async (req, ctx) => {
|
|
|
+ const { pathData } = req
|
|
|
|
|
|
const distance = Number(Number(pathData.distance || 0).toFixed(2))
|
|
|
const stepData = generateCadence(distance, pathData.time)
|