getAlarmSnapshot.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package jobs
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "fwh-toolbox-server/domain"
  8. "fwh-toolbox-server/dto"
  9. function "fwh-toolbox-server/function/SnapFile"
  10. "fwh-toolbox-server/logger"
  11. "fwh-toolbox-server/repositories"
  12. "fwh-toolbox-server/utils/files"
  13. "os"
  14. "path/filepath"
  15. "regexp"
  16. "strconv"
  17. "strings"
  18. "time"
  19. "go.uber.org/zap"
  20. "gorm.io/gorm"
  21. )
  22. type AlarmSnapshotListResponse = domain.FwhPageResponse[domain.AlarmSnapshotInfo]
  23. // 获取告警快照
  24. // 从FHW后端请求快照文件后,处理并放入数据库
  25. // logTime 格式 YYYY-MM-DD
  26. func GetAlarmSnapshot(ctx context.Context, sn, logTime string) error {
  27. // 请求快照信息列表
  28. snapshotList, err := repositories.QueryAlarmSnapshotInfo(sn, logTime)
  29. if err != nil {
  30. return fmt.Errorf("[GetAlarmSnapshot] %s", err.Error())
  31. }
  32. // 创建临时目录存储下载和解压数据
  33. tempDir, err := os.MkdirTemp("", "fwhtb-alarmSnapshot-*")
  34. if err != nil {
  35. logger.Std.Printf("创建临时目录失败: %v\n", err)
  36. return err
  37. }
  38. defer os.RemoveAll(tempDir) // 程序结束时清理临时目录
  39. for _, info := range snapshotList {
  40. // 判断文件是否已经导入
  41. _, err := repositories.QuerySnapshotFileRecord(ctx, info.FileName)
  42. if errors.Is(err, gorm.ErrRecordNotFound) {
  43. // 不存在 - 继续运行
  44. } else if err != nil {
  45. // 查询出错
  46. logger.Std.Printf("数据库快照文件记录查询失败: %v\n", err)
  47. return err
  48. } else {
  49. // 存在 record
  50. continue
  51. }
  52. // 获取站点信息,获取设备时区信息
  53. res, err := repositories.QuerySiteInfoList(dto.SiteInfoPara{
  54. SN: &info.LinkId,
  55. PageSize: 1,
  56. PageNum: 1,
  57. })
  58. if err != nil {
  59. zap.L().Error("站点信息查询失败。", zap.String("err", err.Error()))
  60. return err
  61. }
  62. // 提取ZoneInfo
  63. if len(res.Result) != 1 {
  64. zap.L().Error("站点信息查询失败,未查询待站点数据。", zap.Any("res", res))
  65. return fmt.Errorf("站点信息查询失败,未查询待站点数据。")
  66. }
  67. zoneInfo := res.Result[0].ZoneInfo
  68. snapFileName := filepath.Join(tempDir, info.FileName+".tgz")
  69. // 下载快照文件
  70. fileUrl := "https://energy.franklinwh.com/hes-upload/v1/file/download/" + info.FileUrl
  71. if err = files.DownloadFile(fileUrl, snapFileName); err != nil {
  72. zap.L().Error("快照文件下载失败。", zap.String("err", err.Error()))
  73. return err
  74. }
  75. // 创建解压目标文件夹
  76. snapDir := filepath.Join(tempDir, fmt.Sprintf("AlarmSnapshot_%d", info.Id))
  77. if err = files.CheckAndCreateDir(snapDir); err != nil {
  78. zap.L().Error("文件夹创建失败", zap.String("err", err.Error()))
  79. return err
  80. }
  81. // 解压包文件
  82. if err = files.AutoExtract(snapFileName, snapDir); err != nil {
  83. zap.L().Error("解压快照包失败", zap.String("err", err.Error()))
  84. return err
  85. }
  86. startTime, err := time.Parse(time.DateTime, info.StartTime)
  87. if err != nil {
  88. zap.L().Error("StartTime格式错误", zap.String("err", err.Error()))
  89. return err
  90. }
  91. endTime, err := time.Parse(time.DateTime, info.EndTime)
  92. if err != nil {
  93. zap.L().Error("EndTime格式错误", zap.String("err", err.Error()))
  94. return err
  95. }
  96. completeTime, err := time.Parse(time.DateTime, info.CompleteTime)
  97. if err != nil {
  98. zap.L().Error("CompleteTime格式错误", zap.String("err", err.Error()))
  99. return err
  100. }
  101. logTime, err := time.Parse(time.DateOnly, info.LogTime)
  102. if err != nil {
  103. zap.L().Error("LogTime格式错误", zap.String("err", err.Error()))
  104. return err
  105. }
  106. fileRecord := domain.SiteAlarmSnapshotFileRecord{
  107. CodeType: info.CodeType,
  108. FileName: info.FileName,
  109. FileURL: info.FileUrl,
  110. Sn: info.LinkId,
  111. LogTime: logTime,
  112. StartTime: startTime,
  113. EndTime: endTime,
  114. CompleteTime: completeTime,
  115. State: 0,
  116. }
  117. // 创建文件解析记录(冲突时覆盖)
  118. err = repositories.InsertSnapshotFileRecord(ctx, &fileRecord)
  119. if err != nil {
  120. return err
  121. }
  122. // 遍历文件
  123. var fileList []string
  124. if fileList, err = files.ListFiles(snapDir, true); err != nil {
  125. zap.L().Error("遍历文件失败", zap.String("err", err.Error()))
  126. return err
  127. }
  128. // 包文件有两个版本:
  129. // 1 - 包中文件内容为字符串,直接读取即可
  130. // 2 - 包中数据为16进制数据,须配合VID/PID表解析
  131. // 解析文件并写入数据库
  132. if err = handleAlarmSnapshotFiles(ctx, fileRecord, fileList, zoneInfo); err != nil {
  133. zap.L().Error("解析文件或写入数据库失败", zap.String("err", err.Error()))
  134. return err
  135. }
  136. }
  137. return nil
  138. }
  139. // handleAlarmSnapshotFiles 遍历文件列表并处理符合格式的文件
  140. func handleAlarmSnapshotFiles(ctx context.Context, record domain.SiteAlarmSnapshotFileRecord, fileList []string, zoneInfo string) error {
  141. // 正则规则
  142. reDigits10 := regexp.MustCompile(`^\d{10}$`)
  143. reZip := regexp.MustCompile(`^\d{8}_\d{6}_\d{3}\.zip$`)
  144. for _, file := range fileList {
  145. // 获取文件名(去掉路径)
  146. name := filepath.Base(file)
  147. // 处理纯数字长度为10
  148. if reDigits10.MatchString(name) {
  149. if err := parserOldAlarmSnapshotFile(ctx, record, file, zoneInfo); err != nil {
  150. zap.L().Error("解析OLD告警快照文件失败", zap.String("err", err.Error()))
  151. }
  152. continue
  153. }
  154. // 处理 zip 包 (数字+下划线组合)
  155. if strings.HasSuffix(name, ".zip") && reZip.MatchString(name) {
  156. parserNewAlarmSnapshotFile(ctx, record, file, zoneInfo)
  157. continue
  158. }
  159. // 其他文件忽略
  160. zap.L().Warn("忽略文件", zap.String("fileName", name), zap.String("filePath", file))
  161. }
  162. return nil
  163. }
  164. func parserOldAlarmSnapshotFile(ctx context.Context, record domain.SiteAlarmSnapshotFileRecord, filePath string, zoneInfo string) error {
  165. // 获取文件名(去掉路径)
  166. name := filepath.Base(filePath)
  167. // 读取文件内容
  168. data, err := os.ReadFile(filePath)
  169. if err != nil {
  170. return fmt.Errorf("读取文件失败 %s: %s", filePath, err.Error())
  171. }
  172. if len(data) == 0 {
  173. return fmt.Errorf("读取文件内容为空")
  174. }
  175. // 校验 JSON 格式
  176. var js json.RawMessage
  177. if err := json.Unmarshal(data, &js); err != nil {
  178. return fmt.Errorf("解析 JSON 失败 %s: %s", filePath, err.Error())
  179. }
  180. // 获取设备时区,计算日志记录时间
  181. tsInt, err := strconv.ParseInt(name, 10, 64)
  182. if err != nil {
  183. return fmt.Errorf("解析 LogTime 失败 %s: %s", filePath, err.Error())
  184. }
  185. t := time.Unix(tsInt, 0) // 秒级
  186. // 存入数据库
  187. siteAlarmSnapshotData := domain.SiteAlarmSnapshotData{
  188. Sn: record.Sn,
  189. FileRecordID: record.ID,
  190. LogTime: t,
  191. JSONData: string(js),
  192. ZoneInfo: zoneInfo,
  193. }
  194. err = repositories.InsertSnapshotData(ctx, &siteAlarmSnapshotData)
  195. if err != nil {
  196. return err
  197. }
  198. return nil
  199. }
  200. func parserNewAlarmSnapshotFile(ctx context.Context, record domain.SiteAlarmSnapshotFileRecord, filePath string, zoneInfo string) error {
  201. // 获取文件名(去掉路径)
  202. name := filepath.Base(filePath)
  203. // 生成文件夹路径
  204. destPath := strings.TrimSuffix(filePath, filepath.Ext(name))
  205. if err := files.Unzip(filePath, destPath); err != nil {
  206. zap.L().Error("zip文件解压失败", zap.String("err", err.Error()))
  207. return err
  208. }
  209. // 遍历文件
  210. fileList, err := files.ListFiles(destPath, true)
  211. if err != nil {
  212. zap.L().Error("遍历文件失败", zap.String("err", err.Error()))
  213. return err
  214. }
  215. if len(fileList) != 1 {
  216. zap.L().Error("zip文件内容不正确", zap.String("err", err.Error()))
  217. return err
  218. }
  219. snapFilePath := fileList[0]
  220. parser := function.NewParserAlarmSnapshot(snapFilePath)
  221. if err := parser.ParserSnapFile(); err != nil {
  222. zap.L().Error("告警快照文件解析失败", zap.String("err", err.Error()), zap.String("filePath", snapFilePath))
  223. return err
  224. }
  225. for _, d := range parser.SnapJson {
  226. // 转 JSON 字符串
  227. dataStr, err := json.Marshal(d)
  228. if err != nil {
  229. return fmt.Errorf("告警快照数据转 JSON 失败 %s: %s", filePath, err.Error())
  230. }
  231. // 存入数据库
  232. siteAlarmSnapshotData := domain.SiteAlarmSnapshotData{
  233. Sn: record.Sn,
  234. FileRecordID: record.ID,
  235. LogTime: time.Unix(int64(d.TimeStamp), 0),
  236. JSONData: string(dataStr),
  237. ZoneInfo: zoneInfo,
  238. }
  239. err = repositories.InsertSnapshotData(ctx, &siteAlarmSnapshotData)
  240. if err != nil {
  241. return err
  242. }
  243. }
  244. return nil
  245. }