callFwhFile.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. package jobs
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "fwh-toolbox-server/cache"
  8. "fwh-toolbox-server/domain"
  9. "fwh-toolbox-server/logger"
  10. "fwh-toolbox-server/repositories"
  11. "path"
  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. var success bool
  108. var failReason string
  109. // ✅ defer 确保无论走哪条路径,最终结果一定被更新
  110. defer func() {
  111. if success {
  112. repositories.UpdateBatchDownloadCallFileResult(mainTaskId.Hex(), taskId, 1, "")
  113. // ack stream
  114. ackTask(CallFileTaskStreamInfo{
  115. Ctx: callFwhFileCtx,
  116. StreamName: streamName,
  117. GroupName: groupName,
  118. ID: taskId,
  119. }, true, nil)
  120. } else {
  121. repositories.UpdateBatchDownloadCallFileResult(mainTaskId.Hex(), taskId, -1, failReason)
  122. // ack stream
  123. ackTask(CallFileTaskStreamInfo{
  124. Ctx: callFwhFileCtx,
  125. StreamName: streamName,
  126. GroupName: groupName,
  127. ID: taskId,
  128. }, false, errors.New(failReason))
  129. }
  130. }()
  131. for count := 0; count < maxCount; count++ {
  132. // 请求召唤
  133. switch info.FileType {
  134. case FileTypeEmsLog: // 召唤EMS日志
  135. fmt.Printf("callEmsLog SN-%s Date-%s Type-%s\n", info.Sn, info.Date, info.FileSupType)
  136. if err := callEmsLog(info.Sn, info.Date, info.FileSupType); err != nil {
  137. success = false
  138. failReason = fmt.Sprintf("handle task CallEmsLog error - %s", err.Error())
  139. return
  140. }
  141. ok, err := waitUntilDone(func() (bool, error) {
  142. // 查询召唤结果
  143. for count := 0; count < maxCount; count++ {
  144. fmt.Printf("getEmsLogUrl SN-%s Date-%s Type-%s\n", info.Sn, info.Date, info.FileSupType)
  145. recordInfo, err := getEmsLogUrl(info.Sn, info.Date, info.FileSupType)
  146. if err != nil {
  147. zap.L().Error("getEmsLogUrl读取EMS日志地址失败")
  148. continue
  149. }
  150. if recordInfo.Status == -1 {
  151. zap.L().Error("EMS日志召唤失败")
  152. return false, fmt.Errorf("EMS日志召唤失败")
  153. } else if recordInfo.Status == 1 {
  154. // 下载文件
  155. var downloadCount int = 0
  156. for downloadCount = 0; downloadCount < maxCount; downloadCount++ {
  157. dataBytes, err := repositories.QueryDownloadEmsLogFile(recordInfo.FileUrl)
  158. if err != nil {
  159. zap.L().Error("EMS日志文件下载失败", zap.String("err", err.Error()))
  160. continue
  161. }
  162. // 提取文件后缀
  163. baseStr := path.Base(recordInfo.FileUrl) // 取最后的文件名部分
  164. if baseStr == "." || baseStr == "/" {
  165. baseStr = ""
  166. }
  167. // 加载加州时区(Pacific Time)
  168. loc, err := time.LoadLocation("America/Los_Angeles")
  169. if err != nil {
  170. zap.L().Error("Ems日志LoadLocation设置失败", zap.String("callTime", recordInfo.CallTime), zap.String("err", err.Error()))
  171. return false, err
  172. }
  173. // 按指定时区解析时间,生效召唤时间的时间戳
  174. callTime, err := time.ParseInLocation(time.DateTime, recordInfo.CallTime, loc)
  175. if err != nil {
  176. zap.L().Error("Ems日志CallTime解析失败", zap.String("callTime", recordInfo.CallTime), zap.String("err", err.Error()))
  177. return false, err
  178. }
  179. // 保存数据到文件服务器
  180. objName, err := SaveBatchDownloadEmsLog(info.Sn, dataBytes, baseStr, info.FileSupType, info.Date, callTime)
  181. if err != nil {
  182. zap.L().Error("Ems日志文件保存失败", zap.String("devType", strconv.Itoa(info.DevType)), zap.String("sn", info.Sn), zap.String("err", err.Error()))
  183. return false, fmt.Errorf("Ems日志文件保存失败")
  184. }
  185. if err := repositories.InsertBatchDownloadPathEmsLogs(mainTaskId, info.Sn, info.Date, info.FileSupType, objName); err != nil {
  186. zap.L().Error("BatchDownload - EmsLog文件地址写入失败", zap.String("sn", info.Sn), zap.String("date", info.Date), zap.String("objName", objName), zap.String("err", err.Error()))
  187. return false, fmt.Errorf("BatchDownload - EmsLog文件地址写入失败")
  188. }
  189. break
  190. }
  191. // 超过最大尝试次数
  192. if downloadCount >= maxCount {
  193. return false, fmt.Errorf("EMS日志文件下载失败,超过最大尝试次数:%d", maxCount)
  194. }
  195. return true, nil
  196. }
  197. return false, nil
  198. }
  199. return false, fmt.Errorf("读取EMS日志地址请求失败")
  200. }, time.Minute, 30*time.Minute) // 每分钟查看,半小时超时
  201. if err != nil {
  202. success = false
  203. failReason = err.Error() // 保存失败原因
  204. }
  205. if ok {
  206. // 召唤成功
  207. success = true
  208. failReason = ""
  209. return
  210. } else {
  211. zap.L().Warn("Call FWH file失败, 再次尝试", zap.String("count", strconv.Itoa(count)))
  212. }
  213. case FileTypeFaultRecord: // 召唤故障录波
  214. timeRange := strings.Split(info.Date, ",")
  215. if len(timeRange) != 2 {
  216. // 召唤失败
  217. success = false
  218. failReason = fmt.Sprintf("handle task CallFaultRecord timeRange error - timeRange: %s", info.Date)
  219. return
  220. }
  221. // 提取下载记录的indexList
  222. indexList := []int{}
  223. indexStrList := strings.Split(info.FileSupType, ",")
  224. for _, indexStr := range indexStrList {
  225. idx, err := strconv.Atoi(indexStr)
  226. if err != nil {
  227. // 召唤失败
  228. success = false
  229. failReason = fmt.Sprintf("handle task CallFaultRecord indexStrList error - indexStrList: %s - err: %s", info.FileSupType, err.Error())
  230. return
  231. }
  232. indexList = append(indexList, idx)
  233. }
  234. // 召唤故障录波数据
  235. err := callFaultRecord(info.DevType, info.Sn, timeRange[0], timeRange[1], indexList)
  236. if err != nil {
  237. // 召唤失败
  238. success = false
  239. failReason = fmt.Sprintf("handle task CallFaultRecord error - err: %s", err.Error())
  240. return
  241. }
  242. ok, err := waitUntilDone(func() (bool, error) {
  243. // 查询召唤结果
  244. for count := 0; count < maxCount; count++ {
  245. status, faultId, reasonStr, err := getFaultRecordUrl(info.DevType, info.Sn, timeRange[0], timeRange[1])
  246. if err != nil {
  247. zap.L().Error("getFaultRecordUrl读取故障录波地址失败", zap.String("err", err.Error()))
  248. continue
  249. }
  250. if status == -1 {
  251. zap.L().Error("故障录波召唤失败", zap.String("reason", reasonStr))
  252. return false, fmt.Errorf("故障录波召唤失败 - %s", reasonStr)
  253. } else if status == 2 {
  254. // 查询子记录
  255. subRecordList, err := repositories.QueryFaultRecordSubList(faultId)
  256. if err != nil {
  257. zap.L().Error("故障录波子记录查询失败", zap.String("err", err.Error()))
  258. return false, fmt.Errorf("故障录波子记录查询失败 - %w", err)
  259. }
  260. // 下载所有自己录中的文件
  261. for _, subRecord := range subRecordList {
  262. var downloadCount int = 0
  263. for downloadCount = 0; downloadCount < maxCount; downloadCount++ {
  264. _, dataBytes, err := repositories.QueryDownloadFaultRecordData(faultId, subRecord.Id)
  265. if err != nil {
  266. 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()))
  267. continue
  268. }
  269. // 保存数据到文件服务器
  270. objName, err := SaveBatchDownloadFaultRecord(subRecord.GatewayId, dataBytes, subRecord.FileName+".xls")
  271. if err != nil {
  272. 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()))
  273. return false, fmt.Errorf("故障录波记录文件保存失败")
  274. }
  275. if err := repositories.InsertBatchDownloadPathFaultRecord(mainTaskId, subRecord.GatewayId, subRecord.FileName, objName); err != nil {
  276. zap.L().Error("BatchDownload - FaultRecord文件地址写入失败", zap.String("sn", subRecord.GatewayId), zap.String("fileName", subRecord.FileName), zap.String("objName", objName), zap.String("err", err.Error()))
  277. return false, fmt.Errorf("BatchDownload - FaultRecord文件地址写入失败")
  278. }
  279. break
  280. }
  281. // 超过最大尝试次数
  282. if downloadCount >= maxCount {
  283. return false, fmt.Errorf("故障录波记录文件下载失败,超过最大尝试次数:%d", maxCount)
  284. }
  285. }
  286. return true, nil
  287. }
  288. return false, nil
  289. }
  290. return false, fmt.Errorf("读取故障录波地址请求失败")
  291. }, time.Minute, 30*time.Minute) // 每分钟查看,半小时超时
  292. if err != nil {
  293. success = false
  294. failReason = err.Error() // 保存失败原因
  295. }
  296. if ok {
  297. // 召唤成功
  298. success = true
  299. failReason = ""
  300. return
  301. } else {
  302. zap.L().Warn("Call FWH file失败, 再次尝试", zap.String("count", strconv.Itoa(count)))
  303. }
  304. default:
  305. logger.Std.Println("Unknown call file type:", info.FileType)
  306. }
  307. }
  308. }
  309. /************** 任务轮询 **************/
  310. func waitUntilDone(check func() (bool, error), interval time.Duration, timeout time.Duration) (bool, error) {
  311. ticker := time.NewTicker(interval)
  312. defer ticker.Stop()
  313. timeoutChan := time.After(timeout)
  314. for {
  315. select {
  316. case <-ticker.C:
  317. ok, err := check()
  318. if err != nil {
  319. zap.L().Debug("❌ 轮询任务失败", zap.String("err", err.Error()))
  320. return false, err
  321. } else if ok {
  322. zap.L().Debug("✅ 轮询任务完成")
  323. return true, nil
  324. } else {
  325. zap.L().Debug("⏳ 任务轮询中...")
  326. }
  327. case <-timeoutChan:
  328. zap.L().Debug("❌ 轮询超时")
  329. return false, fmt.Errorf("召唤结果轮询超时")
  330. }
  331. }
  332. }
  333. /************** 召唤文件任务队列 **************/
  334. func startTaskQueueWorker(sn string) {
  335. taskQueueMutexMu.Lock()
  336. taskMutex, ok := taskQueueMutex[sn]
  337. if !ok || taskMutex == nil {
  338. taskMutex = &TaskQueueMutex{
  339. running: false,
  340. mu: sync.Mutex{},
  341. }
  342. taskQueueMutex[sn] = taskMutex
  343. }
  344. taskQueueMutexMu.Unlock()
  345. taskMutex.mu.Lock()
  346. if taskMutex.running {
  347. taskMutex.mu.Unlock()
  348. zap.L().Info("任务已在运行,跳过执行")
  349. return
  350. }
  351. taskMutex.running = true
  352. taskMutex.mu.Unlock()
  353. streamName := streamNamePrefix + sn
  354. streamsMap := make(map[string]string)
  355. streamsMap[streamName] = ">"
  356. for {
  357. // 处理新任务
  358. res, err := cache.XReadGroup(callFwhFileCtx, groupName, consumerID, streamsMap, 1) // 等待任务
  359. if err != nil {
  360. if err == redis.Nil {
  361. // 未读取到新消息 - 退出
  362. logger.Std.Println("未读取到新消息: ", sn)
  363. taskMutex.mu.Lock()
  364. taskMutex.running = false
  365. taskMutex.mu.Unlock()
  366. cache.Delete(callFwhFileCtx, streamNamePrefix+sn)
  367. return
  368. }
  369. // Redis异常 - 退出
  370. logger.Std.Println("XReadGroup error:", err)
  371. taskMutex.mu.Lock()
  372. taskMutex.running = false
  373. taskMutex.mu.Unlock()
  374. return
  375. }
  376. for _, stream := range res {
  377. for _, message := range stream.Messages {
  378. logger.Std.Println("XReadGroup read message:", message)
  379. handleTaskQueueMsg(sn, message)
  380. }
  381. }
  382. }
  383. }
  384. // 处理单条消息
  385. func handleTaskQueueMsg(sn string, message redis.XMessage) {
  386. id := message.ID
  387. rawPayload := message.Values["payload"].(string)
  388. rawTaskInfo := message.Values["taskInfo"].(string)
  389. streamName := streamNamePrefix + sn
  390. // 反序列化payload
  391. var payload domain.BatchDownloadFileTaskInfo
  392. if err := json.Unmarshal([]byte(rawPayload), &payload); err != nil {
  393. ackTask(CallFileTaskStreamInfo{
  394. Ctx: callFwhFileCtx,
  395. StreamName: streamName,
  396. GroupName: groupName,
  397. ID: id,
  398. }, false, fmt.Errorf("unmarshal payload error - %s", err.Error()))
  399. return
  400. }
  401. // 反序列化taskInfo
  402. var taskInfo CallFileTaskInfo
  403. if err := json.Unmarshal([]byte(rawTaskInfo), &taskInfo); err != nil {
  404. ackTask(CallFileTaskStreamInfo{
  405. Ctx: callFwhFileCtx,
  406. StreamName: streamName,
  407. GroupName: groupName,
  408. ID: id,
  409. }, false, fmt.Errorf("unmarshal taskInfo error - %s", err.Error()))
  410. return
  411. }
  412. logger.Std.Printf("Processing task %s: %+v\n", id, payload)
  413. // 异步执行召唤文件任务
  414. callFwhFile(taskInfo.MainTaskId, id, payload)
  415. // switch payload.FileType {
  416. // case FileTypeEmsLog: // 召唤EMS日志
  417. // if err := handleTaskSiteAnalysis(ctx, payload.Payload); err != nil {
  418. // ackTask(CallFileTaskStreamInfo{
  419. // Ctx: callFwhFileCtx,
  420. // StreamName: streamName,
  421. // GroupName: groupName,
  422. // ID: id,
  423. // }, false, fmt.Errorf("handle task SiteAnalysis error - %s", err.Error()))
  424. // return
  425. // }
  426. // case FileTypeFaultRecord: // 召唤故障录波
  427. // if err := handleTaskShutdownStatistics(ctx, payload.Payload); err != nil {
  428. // ackTask(CallFileTaskStreamInfo{
  429. // Ctx: callFwhFileCtx,
  430. // StreamName: streamName,
  431. // GroupName: groupName,
  432. // ID: id,
  433. // }, false, fmt.Errorf("handle task SiteAnalysis error - %s", err.Error()))
  434. // return
  435. // }
  436. // default:
  437. // logger.Std.Println("Unknown task type:", payload.FileType)
  438. // }
  439. // 任务完成
  440. // ackTask(CallFileTaskStreamInfo{
  441. // Ctx: callFwhFileCtx,
  442. // StreamName: streamName,
  443. // GroupName: groupName,
  444. // ID: id,
  445. // }, true, nil)
  446. }
  447. func ackTask(streamInfo CallFileTaskStreamInfo, success bool, err error) {
  448. // 删除任务
  449. if delErr := cache.XAckDel(streamInfo.Ctx, streamInfo.StreamName, streamInfo.GroupName, streamInfo.ID); delErr != nil {
  450. logger.Std.Println("XAck error:", delErr)
  451. }
  452. if success {
  453. // TODO 确认任务完成
  454. } else {
  455. // TODO 任务失败
  456. logger.Std.Println("Task queue execution failed, error:", err)
  457. }
  458. if err != nil {
  459. zap.L().Error("taskQueue ackTask error.", zap.String("err", err.Error()), zap.String("StreamName", streamInfo.StreamName), zap.String("GroupName", streamInfo.GroupName), zap.String("ID", streamInfo.ID))
  460. }
  461. }
  462. func enqueueTask(sn string, taskInfo CallFileTaskInfo, payload domain.BatchDownloadFileTaskInfo) (string, error) {
  463. payloadBytes, err := json.Marshal(payload)
  464. if err != nil {
  465. return "", err
  466. }
  467. taskInfoBytes, err := json.Marshal(taskInfo)
  468. if err != nil {
  469. return "", err
  470. }
  471. return cache.XAdd(callFwhFileCtx, streamNamePrefix+sn, map[string]interface{}{"payload": string(payloadBytes), "taskInfo": string(taskInfoBytes)})
  472. }
  473. /************* 文件召唤&查询&下载接口 *************/
  474. // 召唤EMS日志
  475. func callEmsLog(sn string, date string, logType string) error {
  476. logTypeNum, ok := mapEmsLogType[logType]
  477. if !ok {
  478. zap.L().Error("logType不存在", zap.String("logType", logType))
  479. return fmt.Errorf("logType不存在,logType=%s", logType)
  480. }
  481. err := repositories.QueryCallEmsLog(sn, date, logTypeNum)
  482. if err != nil {
  483. zap.L().Error("QueryCallEmsLog请求失败", zap.String("err", err.Error()))
  484. return err
  485. }
  486. return nil
  487. }
  488. // 获取日志下载地址
  489. func getEmsLogUrl(sn string, date string, logType string) (*domain.EmsLogRecordInfo, error) {
  490. logTypeNum, ok := mapEmsLogType[logType]
  491. if !ok {
  492. zap.L().Error("logType不存在", zap.String("logType", logType))
  493. return nil, fmt.Errorf("logType不存在,logType=%s", logType)
  494. }
  495. recordInfo, err := repositories.QueryEmsLogCallRecord(sn, date, logTypeNum)
  496. if err != nil {
  497. zap.L().Error("GetEmsLogUrl请求失败", zap.String("err", err.Error()))
  498. return nil, err
  499. }
  500. return recordInfo, nil
  501. }
  502. // 召唤故障录波
  503. func callFaultRecord(devType int, sn string, startTime string, endTime string, indexList []int) error {
  504. // 查询故障录波列表
  505. err := repositories.QueryCellFaultRecord(devType, sn, indexList)
  506. if err != nil {
  507. return err
  508. }
  509. return nil
  510. }
  511. // 获取日志下载地址
  512. // return (status, faultId, faultReason, error)
  513. // status: -1-失败 0-召唤中 2-成功
  514. func getFaultRecordUrl(devType int, sn string, startTime string, endTime string) (int, int64, string, error) {
  515. // 查询召唤记录
  516. recordList, err := repositories.QueryFaultRecordCallRecordList()
  517. if err != nil {
  518. return 0, 0, "", err
  519. }
  520. for _, record := range recordList {
  521. // // 解析时间
  522. // t, err := time.Parse(time.DateTime, record.CellTime)
  523. // if err != nil {
  524. // return 0, nil, err
  525. // }
  526. // // 加 8 小时
  527. // newTime := t.Add(8 * time.Hour)
  528. // // 转回字符串
  529. // callTime := newTime.Format(time.DateTime)
  530. // // 判断记录是否当天召唤
  531. // currTime := time.Now().Format(time.DateOnly)
  532. // if !strings.HasPrefix(callTime, currTime) {
  533. // // 之后的非当天召唤的数据
  534. // break
  535. // }
  536. // 匹配符合条件的第一个数据
  537. if record.DeviceType == devType {
  538. var devSn string = ""
  539. switch devType {
  540. case 0: // aPower
  541. devSn = *record.ApowerId
  542. case 1: // aGate
  543. devSn = record.GatewayId
  544. case 2: // MPPT
  545. devSn = *record.ApowerId
  546. case 3: // aHub
  547. devSn = *record.PowerlinkId
  548. default:
  549. continue
  550. }
  551. if devSn == sn {
  552. // 匹配到的站点
  553. if *record.Status == -1 {
  554. return *record.Status, record.Id, *record.FaultReason, nil
  555. }
  556. return *record.Status, record.Id, "", nil
  557. }
  558. }
  559. }
  560. return 0, 0, "", fmt.Errorf("未找到召唤匹配记录(devType=%d,sn=%s,startTime=%s,endTime=%s)", devType, sn, startTime, endTime)
  561. }