callFwhFile.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. package jobs
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "fwh-toolbox-server/cache"
  7. "fwh-toolbox-server/domain"
  8. "fwh-toolbox-server/logger"
  9. "fwh-toolbox-server/repositories"
  10. "path"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. "github.com/redis/go-redis/v9"
  17. "go.mongodb.org/mongo-driver/bson/primitive"
  18. "go.uber.org/zap"
  19. )
  20. type TaskQueueMutex struct {
  21. running bool
  22. mu sync.Mutex
  23. }
  24. type EmsLogType = string
  25. var (
  26. EmsRunLog = "EmsRunLog" // 4-IBG运行日志
  27. EmsBattLog = "EmsBattLog" // 7-电池日志
  28. EmsMonitorLog = "EmsMonitorLog" // 9-设备系统资源监控日志
  29. Ems4gLog = "Ems4gLog" // 10-4G日志
  30. EmsAwsLog = "EmsAwsLog" // 11-AWS日志
  31. EmsMainLog = "EmsMainLog" // 12-主进程日志
  32. )
  33. // EMS日志类型map
  34. var mapEmsLogType = map[string]int{
  35. "EmsRunLog": 4,
  36. "EmsBattLog": 7,
  37. "EmsMonitorLog": 9,
  38. "Ems4gLog": 10,
  39. "EmsAwsLog": 11,
  40. "EmsMainLog": 12,
  41. }
  42. var (
  43. callFwhFileCtx = context.Background()
  44. streamNamePrefix = "fwhtoolbox:CallFwhFile:"
  45. groupName = "task_queue"
  46. consumerID = "worker-taskQueue"
  47. // 防止队列任务函数重复启动
  48. taskQueueMutex = map[string]*TaskQueueMutex{}
  49. taskQueueMutexMu sync.RWMutex // 用于保护 map
  50. // 最大召唤次数
  51. maxCount = 3
  52. )
  53. // 召唤文件类型
  54. var (
  55. FileTypeEmsLog = "EmsLog"
  56. FileTypeFaultRecord = "FaultRecord"
  57. )
  58. type CallFileTaskStreamInfo struct {
  59. Ctx context.Context
  60. StreamName string
  61. GroupName string
  62. ID string
  63. }
  64. type CallFileTaskInfo struct {
  65. MainTaskId primitive.ObjectID `bson:"mainTaskId" json:"mainTaskId"`
  66. }
  67. func CreateCallFwhFileTask(mainTaskId primitive.ObjectID, taskList []domain.BatchDownloadFileTaskInfo) error {
  68. // 筛选SN,不同SN可并行执行
  69. sortTaskList := map[string][]domain.BatchDownloadFileTaskInfo{}
  70. for _, task := range taskList {
  71. sortTaskList[task.Sn] = append(sortTaskList[task.Sn], task)
  72. }
  73. taskRecordList := map[string]domain.BatchDownloadFileTaskInfo{}
  74. for sn, payloadList := range sortTaskList {
  75. for _, payload := range payloadList {
  76. // 确保消费组存在(忽略已存在错误)
  77. _ = cache.XGroupCreate(callFwhFileCtx, streamNamePrefix+sn, groupName, "$")
  78. id, err := enqueueTask(sn, CallFileTaskInfo{
  79. MainTaskId: mainTaskId,
  80. }, payload)
  81. if err != nil {
  82. zap.L().Error("创建召唤FWH文件任务队列失败")
  83. return err
  84. }
  85. taskRecordList[id] = payload
  86. }
  87. }
  88. if err := repositories.CreateBatchDownloadCallFileTaskList(mainTaskId.Hex(), taskRecordList); err != nil {
  89. zap.L().Error("插入召唤FWH文件任务队列记录列表失败")
  90. return err
  91. }
  92. // 提取SN list
  93. snList := make([]string, 0, len(sortTaskList))
  94. for sn := range sortTaskList {
  95. snList = append(snList, sn)
  96. }
  97. // 启动任务队列
  98. for _, sn := range snList {
  99. go startTaskQueueWorker(sn)
  100. time.Sleep(time.Second) // 任务队列启动延时,避免过多请求同时发出,将任务错开运行
  101. }
  102. return nil
  103. }
  104. func callFwhFile(mainTaskId primitive.ObjectID, taskId string, info domain.BatchDownloadFileTaskInfo) {
  105. var reason string
  106. streamName := streamNamePrefix + info.Sn
  107. for count := 0; count < maxCount; count++ {
  108. // 请求召唤
  109. switch info.FileType {
  110. case FileTypeEmsLog: // 召唤EMS日志
  111. fmt.Printf("callEmsLog SN-%s Date-%s Type-%s\n", info.Sn, info.Date, info.FileSupType)
  112. if err := callEmsLog(info.Sn, info.Date, info.FileSupType); err != nil {
  113. ackTask(CallFileTaskStreamInfo{
  114. Ctx: callFwhFileCtx,
  115. StreamName: streamName,
  116. GroupName: groupName,
  117. ID: taskId,
  118. }, false, fmt.Errorf("handle task CallEmsLog error - %s", err.Error()))
  119. return
  120. }
  121. ok, err := waitUntilDone(func() (bool, error) {
  122. // 查询召唤结果
  123. for count := 0; count < maxCount; count++ {
  124. fmt.Printf("getEmsLogUrl SN-%s Date-%s Type-%s\n", info.Sn, info.Date, info.FileSupType)
  125. recordInfo, err := getEmsLogUrl(info.Sn, info.Date, info.FileSupType)
  126. if err != nil {
  127. zap.L().Error("getEmsLogUrl读取EMS日志地址失败")
  128. continue
  129. }
  130. if recordInfo.Status == -1 {
  131. zap.L().Error("EMS日志召唤失败")
  132. return false, fmt.Errorf("EMS日志召唤失败")
  133. } else if recordInfo.Status == 1 {
  134. // 下载文件
  135. var downloadCount int = 0
  136. for downloadCount = 0; downloadCount < maxCount; downloadCount++ {
  137. dataBytes, err := repositories.QueryDownloadEmsLogFile(recordInfo.FileUrl)
  138. if err != nil {
  139. zap.L().Error("EMS日志文件下载失败")
  140. continue
  141. }
  142. // 提取文件后缀
  143. baseStr := path.Base(recordInfo.FileUrl) // 取最后的文件名部分
  144. if baseStr == "." || baseStr == "/" {
  145. baseStr = ""
  146. }
  147. // 使用正则匹配多级后缀,例如 .tar.bz2, .tar.gz, .min.js 等
  148. re := regexp.MustCompile(`(\.[a-zA-Z0-9]+)+$`)
  149. matchSuffix := re.FindString(baseStr)
  150. // 加载加州时区(Pacific Time)
  151. loc, err := time.LoadLocation("America/Los_Angeles")
  152. if err != nil {
  153. zap.L().Error("Ems日志LoadLocation设置失败", zap.String("callTime", recordInfo.CallTime), zap.String("err", err.Error()))
  154. return false, err
  155. }
  156. // 按指定时区解析时间,生效召唤时间的时间戳
  157. callTime, err := time.ParseInLocation(time.DateTime, recordInfo.CallTime, loc)
  158. if err != nil {
  159. zap.L().Error("Ems日志CallTime解析失败", zap.String("callTime", recordInfo.CallTime), zap.String("err", err.Error()))
  160. return false, err
  161. }
  162. // 生成文件名 Format "EmsLog_[SN]_[logType]_[logDate]_[cellTimestamp].[Suffix]"
  163. fileName := fmt.Sprintf("EmsLog_%s_%s_%s_%s%s", info.Sn, info.FileSupType, strings.ReplaceAll(info.Date, "-", "_"), strconv.FormatInt(callTime.Unix(), 10), matchSuffix)
  164. // 保存数据到文件服务器
  165. objName, err := SaveBatchDownloadEmsLog(info.Sn, dataBytes, fileName, "application/x-bzip2")
  166. if err != nil {
  167. zap.L().Error("Ems日志文件保存失败", zap.String("devType", strconv.Itoa(info.DevType)), zap.String("sn", info.Sn), zap.String("err", err.Error()))
  168. return false, fmt.Errorf("Ems日志文件保存失败")
  169. }
  170. if err := repositories.InsertBatchDownloadPathEmsLogs(mainTaskId, info.Sn, info.Date, info.FileSupType, objName); err != nil {
  171. zap.L().Error("BatchDownload - EmsLog文件地址写入失败", zap.String("sn", info.Sn), zap.String("date", info.Date), zap.String("objName", objName), zap.String("err", err.Error()))
  172. return false, fmt.Errorf("BatchDownload - EmsLog文件地址写入失败")
  173. }
  174. break
  175. }
  176. // 超过最大尝试次数
  177. if downloadCount >= maxCount {
  178. return false, fmt.Errorf("EMS日志文件下载失败,超过最大尝试次数:%d", maxCount)
  179. }
  180. return true, nil
  181. }
  182. return false, nil
  183. }
  184. return false, fmt.Errorf("读取EMS日志地址请求失败")
  185. }, time.Minute, 30*time.Minute) // 每分钟查看,半小时超时
  186. if err != nil {
  187. reason = err.Error() // 保存失败原因
  188. }
  189. if ok {
  190. // 召唤成功
  191. repositories.UpdateBatchDownloadCallFileResult(mainTaskId.Hex(), taskId, 1, "")
  192. return
  193. } else {
  194. zap.L().Warn("Call FWH file失败, 再次尝试", zap.String("count", strconv.Itoa(count)))
  195. }
  196. case FileTypeFaultRecord: // 召唤故障录波
  197. timeRange := strings.Split(info.Date, ",")
  198. if len(timeRange) != 2 {
  199. ackTask(CallFileTaskStreamInfo{
  200. Ctx: callFwhFileCtx,
  201. StreamName: streamName,
  202. GroupName: groupName,
  203. ID: taskId,
  204. }, false, fmt.Errorf("handle task CallFaultRecord timeRange error - timeRange: %s", info.Date))
  205. return
  206. }
  207. // 提取下载记录的indexList
  208. indexList := []int{}
  209. indexStrList := strings.Split(info.FileSupType, ",")
  210. for _, indexStr := range indexStrList {
  211. idx, err := strconv.Atoi(indexStr)
  212. if err != nil {
  213. ackTask(CallFileTaskStreamInfo{
  214. Ctx: callFwhFileCtx,
  215. StreamName: streamName,
  216. GroupName: groupName,
  217. ID: taskId,
  218. }, false, fmt.Errorf("handle task CallFaultRecord indexStrList error - indexStrList: %s - err: %s", info.FileSupType, err.Error()))
  219. return
  220. }
  221. indexList = append(indexList, idx)
  222. }
  223. // 召唤故障录波数据
  224. err := callFaultRecord(info.DevType, info.Sn, timeRange[0], timeRange[1], indexList)
  225. if err != nil {
  226. ackTask(CallFileTaskStreamInfo{
  227. Ctx: callFwhFileCtx,
  228. StreamName: streamName,
  229. GroupName: groupName,
  230. ID: taskId,
  231. }, false, fmt.Errorf("handle task CallFaultRecord error - err: %s", err.Error()))
  232. return
  233. }
  234. ok, err := waitUntilDone(func() (bool, error) {
  235. // 查询召唤结果
  236. for count := 0; count < maxCount; count++ {
  237. status, faultId, reasonStr, err := getFaultRecordUrl(info.DevType, info.Sn, timeRange[0], timeRange[1])
  238. if err != nil {
  239. zap.L().Error("getFaultRecordUrl读取故障录波地址失败", zap.String("err", err.Error()))
  240. continue
  241. }
  242. if status == -1 {
  243. zap.L().Error("故障录波召唤失败", zap.String("reason", reasonStr))
  244. return false, fmt.Errorf("故障录波召唤失败 - %s", reasonStr)
  245. } else if status == 2 {
  246. // 查询子记录
  247. subRecordList, err := repositories.QueryFaultRecordSubList(faultId)
  248. if err != nil {
  249. zap.L().Error("故障录波子记录查询失败", zap.String("err", err.Error()))
  250. return false, fmt.Errorf("故障录波子记录查询失败 - %w", err)
  251. }
  252. // 下载所有自己录中的文件
  253. for _, subRecord := range subRecordList {
  254. var downloadCount int = 0
  255. for downloadCount = 0; downloadCount < maxCount; downloadCount++ {
  256. _, dataBytes, err := repositories.QueryDownloadFaultRecordData(faultId, subRecord.Id)
  257. if err != nil {
  258. zap.L().Error("故障录波记录文件下载失败", zap.String("devType", strconv.Itoa(subRecord.DeviceType)), zap.String("sn", subRecord.GatewayId), zap.String("faultId", strconv.FormatInt(subRecord.FaultId, 10)), zap.String("id", strconv.FormatInt(subRecord.Id, 10)), zap.String("err", err.Error()))
  259. continue
  260. }
  261. // 保存数据到文件服务器
  262. objName, err := SaveBatchDownloadFaultRecord(subRecord.GatewayId, dataBytes, subRecord.FileName+".xls", "application/vnd.ms-excel;charset=gb2312")
  263. if err != nil {
  264. zap.L().Error("故障录波记录文件保存失败", zap.String("devType", strconv.Itoa(subRecord.DeviceType)), zap.String("sn", subRecord.GatewayId), zap.String("faultId", strconv.FormatInt(subRecord.FaultId, 10)), zap.String("id", strconv.FormatInt(subRecord.Id, 10)), zap.String("err", err.Error()))
  265. return false, fmt.Errorf("故障录波记录文件保存失败")
  266. }
  267. if err := repositories.InsertBatchDownloadPathFaultRecord(mainTaskId, subRecord.GatewayId, subRecord.FileName, objName); err != nil {
  268. zap.L().Error("BatchDownload - FaultRecord文件地址写入失败", zap.String("sn", subRecord.GatewayId), zap.String("fileName", subRecord.FileName), zap.String("objName", objName), zap.String("err", err.Error()))
  269. return false, fmt.Errorf("BatchDownload - FaultRecord文件地址写入失败")
  270. }
  271. break
  272. }
  273. // 超过最大尝试次数
  274. if downloadCount >= maxCount {
  275. return false, fmt.Errorf("故障录波记录文件下载失败,超过最大尝试次数:%d", maxCount)
  276. }
  277. }
  278. return true, nil
  279. }
  280. return false, nil
  281. }
  282. return false, fmt.Errorf("读取故障录波地址请求失败")
  283. }, time.Minute, 30*time.Minute) // 每分钟查看,半小时超时
  284. if err != nil {
  285. reason = err.Error() // 保存失败原因
  286. }
  287. if ok {
  288. // 召唤成功
  289. repositories.UpdateBatchDownloadCallFileResult(mainTaskId.Hex(), taskId, 1, "")
  290. return
  291. } else {
  292. zap.L().Warn("Call FWH file失败, 再次尝试", zap.String("count", strconv.Itoa(count)))
  293. }
  294. default:
  295. logger.Std.Println("Unknown call file type:", info.FileType)
  296. }
  297. }
  298. // 召唤失败
  299. repositories.UpdateBatchDownloadCallFileResult(mainTaskId.Hex(), taskId, -1, reason)
  300. }
  301. /************** 任务轮询 **************/
  302. func waitUntilDone(check func() (bool, error), interval time.Duration, timeout time.Duration) (bool, error) {
  303. ticker := time.NewTicker(interval)
  304. defer ticker.Stop()
  305. timeoutChan := time.After(timeout)
  306. for {
  307. select {
  308. case <-ticker.C:
  309. ok, err := check()
  310. if err != nil {
  311. zap.L().Debug("❌ 轮询任务失败", zap.String("err", err.Error()))
  312. return false, err
  313. } else if ok {
  314. zap.L().Debug("✅ 轮询任务完成")
  315. return true, nil
  316. } else {
  317. zap.L().Debug("⏳ 任务轮询中...")
  318. }
  319. case <-timeoutChan:
  320. zap.L().Debug("❌ 轮询超时")
  321. return false, fmt.Errorf("召唤结果轮询超时")
  322. }
  323. }
  324. }
  325. /************** 召唤文件任务队列 **************/
  326. func startTaskQueueWorker(sn string) {
  327. taskQueueMutexMu.Lock()
  328. taskMutex, ok := taskQueueMutex[sn]
  329. if !ok || taskMutex == nil {
  330. taskMutex = &TaskQueueMutex{
  331. running: false,
  332. mu: sync.Mutex{},
  333. }
  334. taskQueueMutex[sn] = taskMutex
  335. }
  336. taskQueueMutexMu.Unlock()
  337. taskMutex.mu.Lock()
  338. if taskMutex.running {
  339. taskMutex.mu.Unlock()
  340. zap.L().Info("任务已在运行,跳过执行")
  341. return
  342. }
  343. taskMutex.running = true
  344. taskMutex.mu.Unlock()
  345. streamName := streamNamePrefix + sn
  346. streamsMap := make(map[string]string)
  347. streamsMap[streamName] = ">"
  348. for {
  349. // 处理新任务
  350. res, err := cache.XReadGroup(callFwhFileCtx, groupName, consumerID, streamsMap, 1) // 等待任务
  351. if err != nil {
  352. if err == redis.Nil {
  353. // 未读取到新消息 - 退出
  354. logger.Std.Println("未读取到新消息: ", sn)
  355. taskMutex.mu.Lock()
  356. taskMutex.running = false
  357. taskMutex.mu.Unlock()
  358. cache.Delete(callFwhFileCtx, streamNamePrefix+sn)
  359. return
  360. }
  361. // Redis异常 - 退出
  362. logger.Std.Println("XReadGroup error:", err)
  363. taskMutex.mu.Lock()
  364. taskMutex.running = false
  365. taskMutex.mu.Unlock()
  366. return
  367. }
  368. for _, stream := range res {
  369. for _, message := range stream.Messages {
  370. logger.Std.Println("XReadGroup read message:", message)
  371. handleTaskQueueMsg(sn, message)
  372. }
  373. }
  374. }
  375. }
  376. // 处理单条消息
  377. func handleTaskQueueMsg(sn string, message redis.XMessage) {
  378. id := message.ID
  379. rawPayload := message.Values["payload"].(string)
  380. rawTaskInfo := message.Values["taskInfo"].(string)
  381. streamName := streamNamePrefix + sn
  382. // 反序列化payload
  383. var payload domain.BatchDownloadFileTaskInfo
  384. if err := json.Unmarshal([]byte(rawPayload), &payload); err != nil {
  385. ackTask(CallFileTaskStreamInfo{
  386. Ctx: callFwhFileCtx,
  387. StreamName: streamName,
  388. GroupName: groupName,
  389. ID: id,
  390. }, false, fmt.Errorf("unmarshal payload error - %s", err.Error()))
  391. return
  392. }
  393. // 反序列化taskInfo
  394. var taskInfo CallFileTaskInfo
  395. if err := json.Unmarshal([]byte(rawTaskInfo), &taskInfo); err != nil {
  396. ackTask(CallFileTaskStreamInfo{
  397. Ctx: callFwhFileCtx,
  398. StreamName: streamName,
  399. GroupName: groupName,
  400. ID: id,
  401. }, false, fmt.Errorf("unmarshal taskInfo error - %s", err.Error()))
  402. return
  403. }
  404. logger.Std.Printf("Processing task %s: %+v\n", id, payload)
  405. // 异步执行召唤文件任务
  406. callFwhFile(taskInfo.MainTaskId, id, payload)
  407. // switch payload.FileType {
  408. // case FileTypeEmsLog: // 召唤EMS日志
  409. // if err := handleTaskSiteAnalysis(ctx, payload.Payload); err != nil {
  410. // ackTask(CallFileTaskStreamInfo{
  411. // Ctx: callFwhFileCtx,
  412. // StreamName: streamName,
  413. // GroupName: groupName,
  414. // ID: id,
  415. // }, false, fmt.Errorf("handle task SiteAnalysis error - %s", err.Error()))
  416. // return
  417. // }
  418. // case FileTypeFaultRecord: // 召唤故障录波
  419. // if err := handleTaskAlarmStatistics(ctx, payload.Payload); err != nil {
  420. // ackTask(CallFileTaskStreamInfo{
  421. // Ctx: callFwhFileCtx,
  422. // StreamName: streamName,
  423. // GroupName: groupName,
  424. // ID: id,
  425. // }, false, fmt.Errorf("handle task SiteAnalysis error - %s", err.Error()))
  426. // return
  427. // }
  428. // default:
  429. // logger.Std.Println("Unknown task type:", payload.FileType)
  430. // }
  431. // 任务完成
  432. ackTask(CallFileTaskStreamInfo{
  433. Ctx: callFwhFileCtx,
  434. StreamName: streamName,
  435. GroupName: groupName,
  436. ID: id,
  437. }, true, nil)
  438. }
  439. func ackTask(streamInfo CallFileTaskStreamInfo, success bool, err error) {
  440. // 删除任务
  441. if err := cache.XAckDel(streamInfo.Ctx, streamInfo.StreamName, streamInfo.GroupName, streamInfo.ID); err != nil {
  442. logger.Std.Println("XAck error:", err)
  443. }
  444. if success {
  445. // TODO 确认任务完成
  446. } else {
  447. // TODO 任务失败
  448. logger.Std.Println("Task queue execution failed, error:", err)
  449. }
  450. if err != nil {
  451. zap.L().Error("taskQueue ackTask error.", zap.String("err", err.Error()))
  452. }
  453. }
  454. func enqueueTask(sn string, taskInfo CallFileTaskInfo, payload domain.BatchDownloadFileTaskInfo) (string, error) {
  455. payloadBytes, err := json.Marshal(payload)
  456. if err != nil {
  457. return "", err
  458. }
  459. taskInfoBytes, err := json.Marshal(taskInfo)
  460. if err != nil {
  461. return "", err
  462. }
  463. return cache.XAdd(callFwhFileCtx, streamNamePrefix+sn, map[string]interface{}{"payload": string(payloadBytes), "taskInfo": string(taskInfoBytes)})
  464. }
  465. /************* 文件召唤&查询&下载接口 *************/
  466. // 召唤EMS日志
  467. func callEmsLog(sn string, date string, logType string) error {
  468. logTypeNum, ok := mapEmsLogType[logType]
  469. if !ok {
  470. zap.L().Error("logType不存在", zap.String("logType", logType))
  471. return fmt.Errorf("logType不存在,logType=%s", logType)
  472. }
  473. err := repositories.QueryCallEmsLog(sn, date, logTypeNum)
  474. if err != nil {
  475. zap.L().Error("QueryCallEmsLog请求失败", zap.String("err", err.Error()))
  476. return err
  477. }
  478. return nil
  479. }
  480. // 获取日志下载地址
  481. func getEmsLogUrl(sn string, date string, logType string) (*domain.EmsLogRecordInfo, error) {
  482. logTypeNum, ok := mapEmsLogType[logType]
  483. if !ok {
  484. zap.L().Error("logType不存在", zap.String("logType", logType))
  485. return nil, fmt.Errorf("logType不存在,logType=%s", logType)
  486. }
  487. recordInfo, err := repositories.QueryEmsLogCallRecord(sn, date, logTypeNum)
  488. if err != nil {
  489. zap.L().Error("GetEmsLogUrl请求失败", zap.String("err", err.Error()))
  490. return nil, err
  491. }
  492. return recordInfo, nil
  493. }
  494. // 召唤故障录波
  495. func callFaultRecord(devType int, sn string, startTime string, endTime string, indexList []int) error {
  496. // 查询故障录波列表
  497. err := repositories.QueryCellFaultRecord(devType, sn, indexList)
  498. if err != nil {
  499. return err
  500. }
  501. return nil
  502. }
  503. // 获取日志下载地址
  504. // return (status, faultId, faultReason, error)
  505. // status: -1-失败 0-召唤中 2-成功
  506. func getFaultRecordUrl(devType int, sn string, startTime string, endTime string) (int, int64, string, error) {
  507. // 查询召唤记录
  508. recordList, err := repositories.QueryFaultRecordCallRecordList()
  509. if err != nil {
  510. return 0, 0, "", err
  511. }
  512. for _, record := range recordList {
  513. // // 解析时间
  514. // t, err := time.Parse(time.DateTime, record.CellTime)
  515. // if err != nil {
  516. // return 0, nil, err
  517. // }
  518. // // 加 8 小时
  519. // newTime := t.Add(8 * time.Hour)
  520. // // 转回字符串
  521. // callTime := newTime.Format(time.DateTime)
  522. // // 判断记录是否当天召唤
  523. // currTime := time.Now().Format(time.DateOnly)
  524. // if !strings.HasPrefix(callTime, currTime) {
  525. // // 之后的非当天召唤的数据
  526. // break
  527. // }
  528. // 匹配符合条件的第一个数据
  529. if record.DeviceType == devType {
  530. var devSn string = ""
  531. switch devType {
  532. case 0: // aPower
  533. devSn = *record.ApowerId
  534. case 1: // aGate
  535. devSn = record.GatewayId
  536. case 2: // MPPT
  537. devSn = *record.ApowerId
  538. case 3: // aHub
  539. devSn = *record.PowerlinkId
  540. default:
  541. continue
  542. }
  543. if devSn == sn {
  544. // 匹配到的站点
  545. if *record.Status == -1 {
  546. return *record.Status, record.Id, *record.FaultReason, nil
  547. }
  548. return *record.Status, record.Id, "", nil
  549. }
  550. }
  551. }
  552. return 0, 0, "", fmt.Errorf("未找到召唤匹配记录(devType=%d,sn=%s,startTime=%s,endTime=%s)", devType, sn, startTime, endTime)
  553. }