callFwhFile.go 20 KB

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