package jobs import ( "bytes" "fmt" "fwh-toolbox-server/domain" "fwh-toolbox-server/dto" "fwh-toolbox-server/utils/echart" "fwh-toolbox-server/utils/minio" "os" "path/filepath" "strconv" "strings" "time" "github.com/go-echarts/go-echarts/v2/opts" "github.com/xuri/excelize/v2" "go.uber.org/zap" ) /* 文件服务器存储结构 * BatchDownloadData/ * └── `SN`/ * ├── PowerFlow/ 功率流图文件 * ├── MQTT/ MQTT通讯数据文件 * ├── EventAlarm/ 事件日志与告警日志文件 * ├── AlarmSnapshot 告警快照 * ├── EmsLogs/ EMS日志文件 * ├── FaultRecord/ 故障录波文件 * ├── UpdateLog/ 升级日志数据文件 * └── Settings/ 设置数据文件 * * BatchDownloadPackage/ 任务打包数据 */ var ( mainPathName string = "BatchDownloadData" subPowerFlowPathName string = "PowerFlow" subMqttPathName string = "MQTT" subEventAlarmPathName string = "EventAlarm" subAlarmSnapshotPathName string = "AlarmSnapshot" subEmsLogsPathName string = "EmsLogs" subFaultRecordPathName string = "FaultRecord" subUpdateLogPathName string = "UpdateLog" subSettingsPathName string = "Settings" ) func stringPtr(s string) *string { return &s } // 创建临时保存路径 func createTempPath(pathType string) (string, error) { // 创建临时目录存储下载和解压数据 tempDirName := fmt.Sprintf("fwhtb-batchDownload-%s-*", pathType) tempDir, err := os.MkdirTemp("", tempDirName) if err != nil { zap.L().Error("创建临时目录失败", zap.String("pathName", tempDirName), zap.String("err", err.Error())) return "", err } return tempDir, nil } // Excel 指定行-列写入数据, 行列从1开始 func excelSetCellValue(f *excelize.File, sheetName string, col int, row int, val interface{}) error { colName, err := excelize.ColumnNumberToName(col) if err != nil { zap.L().Error("Excel 列标转换失败", zap.String("col", strconv.Itoa(col)), zap.String("err", err.Error())) return err } return f.SetCellValue(sheetName, fmt.Sprintf("%s%d", colName, row), val) } // 保存功率流图到文件服务器 // return objName func SaveBatchDownloadPowerFlow(sn string, dateStr string, powerFlowData dto.SitePowerFlow) (string, error) { // 创建临时保存文件路径 tempPath, err := createTempPath("powerFlow") if err != nil { return "", err } defer os.RemoveAll(tempPath) // 程序结束时清理临时目录 fileName := fmt.Sprintf("PowerFlow_%s.html", strings.ReplaceAll(dateStr, "-", "")) filePath := filepath.Join(tempPath, fileName) // 数据结构转换 pvData := []opts.LineData{} genData := []opts.LineData{} gridData := []opts.LineData{} aPowerData := []opts.LineData{} loadData := []opts.LineData{} socData := []opts.LineData{} for i, t := range powerFlowData.DeviceTimeArray { var pvValue []interface{} if i < len(powerFlowData.PowerSunArray) { pvValue = []interface{}{ t, powerFlowData.PowerSunArray[i], } } else { pvValue = []interface{}{ t, nil, } } pvData = append(pvData, opts.LineData{ Value: pvValue, }) var genValue []interface{} if i < len(powerFlowData.PowerGenArray) { genValue = []interface{}{ t, powerFlowData.PowerGenArray[i], } } else { genValue = []interface{}{ t, nil, } } genData = append(genData, opts.LineData{ Value: genValue, }) var gridValue []interface{} if i < len(powerFlowData.PowerUtiArray) { gridValue = []interface{}{ t, powerFlowData.PowerUtiArray[i], } } else { gridValue = []interface{}{ t, nil, } } gridData = append(gridData, opts.LineData{ Value: gridValue, }) var aPowerValue []interface{} if i < len(powerFlowData.PowerFhpArray) { aPowerValue = []interface{}{ t, powerFlowData.PowerFhpArray[i], } } else { aPowerValue = []interface{}{ t, nil, } } aPowerData = append(aPowerData, opts.LineData{ Value: aPowerValue, }) var loadValue []interface{} if i < len(powerFlowData.PowerLoadArray) { loadValue = []interface{}{ t, powerFlowData.PowerLoadArray[i], } } else { loadValue = []interface{}{ t, nil, } } loadData = append(loadData, opts.LineData{ Value: loadValue, }) // SOC var socValue []interface{} if i < len(powerFlowData.SocArray) { socValue = []interface{}{ t, powerFlowData.SocArray[i], } } else { socValue = []interface{}{ t, nil, } } socData = append(socData, opts.LineData{ Value: socValue, }) } // 生成功率流图 var pfChartData []echart.PowerFlowChartData = []echart.PowerFlowChartData{} pfChartData = append(pfChartData, echart.PowerFlowChartData{ Legend: "光伏", Color: stringPtr("#F1A502"), Data: pvData, }) pfChartData = append(pfChartData, echart.PowerFlowChartData{ Legend: "发电机", Color: stringPtr("#5398FF"), Data: genData, }) pfChartData = append(pfChartData, echart.PowerFlowChartData{ Legend: "市电", Color: stringPtr("#BB56FD"), Data: gridData, }) pfChartData = append(pfChartData, echart.PowerFlowChartData{ Legend: "aPower", Color: stringPtr("#41D187"), Data: aPowerData, }) pfChartData = append(pfChartData, echart.PowerFlowChartData{ Legend: "家庭负载", Color: stringPtr("#0BF5FF"), Data: loadData, }) var socChartData echart.SocChartData = echart.SocChartData{ Color: stringPtr("#3E7B4D"), Data: socData, } lineOpt := echart.LineChartOpt{ Global: echart.ChartOpt{ Title: "功率流图", }, Smooth: true, } line := echart.CreatePowerFlowChart(lineOpt, pfChartData, socChartData) f, _ := os.Create(filePath) defer f.Close() line.Render(f) // 上传到文件服务器 objectName := fmt.Sprintf("%s/%s/%s/%s", mainPathName, sn, subPowerFlowPathName, fileName) if err = minio.UploadFile(filePath, objectName); err != nil { zap.L().Error("上传PowerFlow文件失败", zap.String("path", filePath), zap.String("objectName", objectName), zap.String("err", err.Error())) return "", err } return objectName, nil } // 保存MqttLog excel到文件服务器 // return objName func SaveBatchDownloadMqttLog(sn string, cmdType string, dateStr string, data []domain.MqttLogItem) (objectName string, err error) { err = nil f := excelize.NewFile() defer func() { closeErr := f.Close() if closeErr != nil { zap.L().Error("Excel 文件close失败", zap.String("err", err.Error())) if err == nil { err = closeErr } } }() // 写标题行 f.SetCellValue("Sheet1", "A1", "Type") f.SetCellValue("Sheet1", "B1", "Time") f.SetCellValue("Sheet1", "C1", "Data") // 写入数据 for i, d := range data { f.SetCellValue("Sheet1", "A"+strconv.Itoa(i+2), d.CmdType) f.SetCellValue("Sheet1", "B"+strconv.Itoa(i+2), d.LogTime) f.SetCellValue("Sheet1", "C"+strconv.Itoa(i+2), d.LogData) } // 创建临时保存文件路径 tempPath, err := createTempPath("mqtt_" + cmdType) if err != nil { return } defer os.RemoveAll(tempPath) // 程序结束时清理临时目录 fileName := fmt.Sprintf("MqttLog_%s_%s.xlsx", cmdType, strings.ReplaceAll(dateStr, "-", "")) filePath := filepath.Join(tempPath, fileName) // 保存到文件 err = f.SaveAs(filePath) if err != nil { zap.L().Error("Excel文件save失败", zap.String("path", filePath), zap.String("err", err.Error())) return } // 上传到文件服务器 objectName = fmt.Sprintf("%s/%s/%s/%s", mainPathName, sn, subMqttPathName, fileName) if err = minio.UploadFile(filePath, objectName); err != nil { zap.L().Error("上传Excel文件失败", zap.String("path", filePath), zap.String("objectName", objectName), zap.String("err", err.Error())) return } return } // 保存EventAlarm excel到文件服务器 // return objName func SaveBatchDownloadEventAlarm(sn string, dateStr string, eventAlarmData []domain.EventAlarmItem) (objectName string, err error) { err = nil f := excelize.NewFile() defer func() { closeErr := f.Close() if closeErr != nil { zap.L().Error("Excel 文件close失败", zap.String("err", err.Error())) if err == nil { err = closeErr } } }() // 写标题行 f.SetCellValue("Sheet1", "A1", "ID") f.SetCellValue("Sheet1", "B1", "Time") f.SetCellValue("Sheet1", "C1", "Type") f.SetCellValue("Sheet1", "D1", "AlarmCode") f.SetCellValue("Sheet1", "E1", "Name") f.SetCellValue("Sheet1", "F1", "StartTime") f.SetCellValue("Sheet1", "G1", "EndTime") f.SetCellValue("Sheet1", "H1", "Data") for i, data := range eventAlarmData { var id string if data.ID == nil || *data.ID == "" { id = strconv.Itoa(i + 1) } else { id = *data.ID } f.SetCellValue("Sheet1", "A"+strconv.Itoa(i+2), id) f.SetCellValue("Sheet1", "B"+strconv.Itoa(i+2), data.LogTime) f.SetCellValue("Sheet1", "C"+strconv.Itoa(i+2), data.LogType) f.SetCellValue("Sheet1", "D"+strconv.Itoa(i+2), data.AlarmCode) f.SetCellValue("Sheet1", "E"+strconv.Itoa(i+2), data.LogName) f.SetCellValue("Sheet1", "F"+strconv.Itoa(i+2), data.StartTime) f.SetCellValue("Sheet1", "G"+strconv.Itoa(i+2), data.EndTime) f.SetCellValue("Sheet1", "H"+strconv.Itoa(i+2), data.LogData) } // 创建临时保存文件路径 tempPath, err := createTempPath("eventAlarm") if err != nil { return } defer os.RemoveAll(tempPath) // 程序结束时清理临时目录 fileName := fmt.Sprintf("EventAlarm_%s.xlsx", strings.ReplaceAll(dateStr, "-", "")) filePath := filepath.Join(tempPath, fileName) // 保存到文件 err = f.SaveAs(filePath) if err != nil { zap.L().Error("Excel文件save失败", zap.String("path", filePath), zap.String("err", err.Error())) return } // 上传到文件服务器 objectName = fmt.Sprintf("%s/%s/%s/%s", mainPathName, sn, subEventAlarmPathName, fileName) if err = minio.UploadFile(filePath, objectName); err != nil { zap.L().Error("上传Excel文件失败", zap.String("path", filePath), zap.String("objectName", objectName), zap.String("err", err.Error())) return } return } // 保存AlarmSnapshot文件到文件服务器 // return objName func SaveBatchDownloadAlarmSnapshot(sn string, dateStr string, alarmSnapshotData []*domain.SiteAlarmSnapshotData) (objectName string, err error) { err = nil f := excelize.NewFile() defer func() { closeErr := f.Close() if closeErr != nil { zap.L().Error("Excel 文件close失败", zap.String("err", err.Error())) if err == nil { err = closeErr } } }() // 写标题行 f.SetCellValue("Sheet1", "A1", "ID") f.SetCellValue("Sheet1", "B1", "Time") f.SetCellValue("Sheet1", "C1", "Data") for i, data := range alarmSnapshotData { var logTime string loc, e := time.LoadLocation(data.ZoneInfo) if e != nil { zap.L().Warn("本地时间加载失败", zap.String("ZoneInfo", data.ZoneInfo), zap.String("err", err.Error())) logTime = data.LogTime.Format(time.DateTime) } else { logTime = data.LogTime.In(loc).Format(time.DateTime) } f.SetCellValue("Sheet1", "A"+strconv.Itoa(i+2), data.ID) f.SetCellValue("Sheet1", "B"+strconv.Itoa(i+2), logTime) f.SetCellValue("Sheet1", "C"+strconv.Itoa(i+2), data.JSONData) } // 创建临时保存文件路径 tempPath, err := createTempPath("alarmSnapshot") if err != nil { return } defer os.RemoveAll(tempPath) // 程序结束时清理临时目录 fileName := fmt.Sprintf("EventAlarm_%s.xlsx", strings.ReplaceAll(dateStr, "-", "")) filePath := filepath.Join(tempPath, fileName) // 保存到文件 err = f.SaveAs(filePath) if err != nil { zap.L().Error("Excel文件save失败", zap.String("path", filePath), zap.String("err", err.Error())) return } // 上传到文件服务器 objectName = fmt.Sprintf("%s/%s/%s/%s", mainPathName, sn, subAlarmSnapshotPathName, fileName) if err = minio.UploadFile(filePath, objectName); err != nil { zap.L().Error("上传Excel文件失败", zap.String("path", filePath), zap.String("objectName", objectName), zap.String("err", err.Error())) return } return } // 保存EmsLog文件到文件服务器 // return objName func SaveBatchDownloadEmsLog(sn string, dataBytes []byte, emsLogFileName string, contentType string) (string, error) { // 将 []byte 封装成 io.Reader dataReader := bytes.NewReader(dataBytes) // 保存到minio objectName := fmt.Sprintf("%s/%s/%s/%s", mainPathName, sn, subEmsLogsPathName, emsLogFileName) return objectName, minio.UploadFromReader(dataReader, int64(len(dataBytes)), objectName, contentType) } // 保存FaultRecord文件到文件服务器 // return objName func SaveBatchDownloadFaultRecord(sn string, dataBytes []byte, faultRecordFileName string, contentType string) (string, error) { // 将 []byte 封装成 io.Reader dataReader := bytes.NewReader(dataBytes) // 保存到minio objectName := fmt.Sprintf("%s/%s/%s/%s", mainPathName, sn, subFaultRecordPathName, faultRecordFileName) return objectName, minio.UploadFromReader(dataReader, int64(len(dataBytes)), objectName, contentType) } // 保存UpdateLog excel到文件服务器 // return objName func SaveBatchDownloadUpdateLog(sn string, systemUpdateLogData []domain.BatchDownloadSystemUpdateLogInfo, aHubUpdateLogData []domain.BatchDownloadAHubUpdateLogInfo) (objectName string, err error) { err = nil f := excelize.NewFile() defer func() { closeErr := f.Close() if closeErr != nil { zap.L().Error("Excel 文件close失败", zap.String("err", err.Error())) if err == nil { err = closeErr } } }() systemUpdateSheetName := "SystemUpdate" aHubUpdateSheetName := "aHubUpdate" err = f.SetSheetName("Sheet1", systemUpdateSheetName) if err != nil { zap.L().Error("Excel Sheet1更名失败", zap.String("sheetName", systemUpdateSheetName), zap.String("err", err.Error())) return } _, err = f.NewSheet(aHubUpdateSheetName) if err != nil { zap.L().Error("Excel 新增Sheet失败", zap.String("sheetName", aHubUpdateSheetName), zap.String("err", err.Error())) return } // 样式集 // 加粗 // 创建加粗样式 styleBold, err := f.NewStyle(&excelize.Style{ Font: &excelize.Font{ Bold: true, }, }) if err != nil { zap.L().Error("Excel 新增Sheet失败", zap.String("err", err.Error())) return } var rowIndex int = 1 // 写入系统升级日志 for _, updateItem := range systemUpdateLogData { // 写标题行 f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("A%d", rowIndex), "OrderNo") f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("B%d", rowIndex), "GateSn") f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("C%d", rowIndex), "TriggerType") f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("D%d", rowIndex), "FileName") f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("E%d", rowIndex), "UpdateTime") f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("F%d", rowIndex), "Result") f.SetCellStyle(systemUpdateSheetName, fmt.Sprintf("A%d", rowIndex), fmt.Sprintf("F%d", rowIndex), styleBold) rowIndex++ // 写入项目信息内容 f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("A%d", rowIndex), updateItem.OrderNo) f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("B%d", rowIndex), updateItem.GateSn) f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("C%d", rowIndex), updateItem.TriggerType) f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("D%d", rowIndex), updateItem.FileName) f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("E%d", rowIndex), updateItem.UpdateTime) f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("F%d", rowIndex), updateItem.Result) rowIndex = rowIndex + 2 // Gate版本信息 f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("A%d", rowIndex), "GateVer") f.SetCellStyle(systemUpdateSheetName, fmt.Sprintf("A%d", rowIndex), fmt.Sprintf("A%d", rowIndex), styleBold) rowIndex++ for idx, modeVer := range updateItem.GateVerList { excelSetCellValue(f, systemUpdateSheetName, (3*idx)+1, rowIndex, "Name") excelSetCellValue(f, systemUpdateSheetName, (3*idx)+1, rowIndex+1, "OldVer") excelSetCellValue(f, systemUpdateSheetName, (3*idx)+1, rowIndex+2, "NewVer") excelSetCellValue(f, systemUpdateSheetName, (3*idx)+2, rowIndex, modeVer.Name) excelSetCellValue(f, systemUpdateSheetName, (3*idx)+2, rowIndex+1, modeVer.OldVer) excelSetCellValue(f, systemUpdateSheetName, (3*idx)+2, rowIndex+2, modeVer.NewVer) } rowIndex = rowIndex + 2 // SubDev版本信息 f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("A%d", rowIndex), "SubVer") f.SetCellStyle(systemUpdateSheetName, fmt.Sprintf("A%d", rowIndex), fmt.Sprintf("A%d", rowIndex), styleBold) rowIndex++ for _, subVer := range updateItem.SubDevList { f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("A%d", rowIndex), "SubSn") f.SetCellValue(systemUpdateSheetName, fmt.Sprintf("B%d", rowIndex), subVer.DevSn) rowIndex++ for idx, modeVer := range subVer.DevVerList { excelSetCellValue(f, systemUpdateSheetName, (3*idx)+1, rowIndex, "Name") excelSetCellValue(f, systemUpdateSheetName, (3*idx)+1, rowIndex+1, "OldVer") excelSetCellValue(f, systemUpdateSheetName, (3*idx)+1, rowIndex+2, "NewVer") excelSetCellValue(f, systemUpdateSheetName, (3*idx)+2, rowIndex, modeVer.Name) excelSetCellValue(f, systemUpdateSheetName, (3*idx)+2, rowIndex+1, modeVer.OldVer) excelSetCellValue(f, systemUpdateSheetName, (3*idx)+2, rowIndex+2, modeVer.NewVer) } rowIndex = rowIndex + 3 } rowIndex++ } rowIndex = 1 // 写入aHub升级日志 for _, aHubUpdateItem := range aHubUpdateLogData { // 写标题行 f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("A%d", rowIndex), "OrderNo") f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("B%d", rowIndex), "GateSn") f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("C%d", rowIndex), "TriggerType") f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("D%d", rowIndex), "AHubSn") f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("E%d", rowIndex), "EndTime") f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("F%d", rowIndex), "Result") f.SetCellStyle(aHubUpdateSheetName, fmt.Sprintf("A%d", rowIndex), fmt.Sprintf("F%d", rowIndex), styleBold) rowIndex++ // 写入项目信息内容 f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("A%d", rowIndex), aHubUpdateItem.OrderNo) f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("B%d", rowIndex), aHubUpdateItem.GateSn) f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("C%d", rowIndex), aHubUpdateItem.TriggerType) f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("D%d", rowIndex), aHubUpdateItem.AHubSn) f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("E%d", rowIndex), aHubUpdateItem.EndTime) f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("F%d", rowIndex), aHubUpdateItem.Result) rowIndex = rowIndex + 2 // aHub版本信息 f.SetCellValue(aHubUpdateSheetName, fmt.Sprintf("A%d", rowIndex), "aHubVer") f.SetCellStyle(aHubUpdateSheetName, fmt.Sprintf("A%d", rowIndex), fmt.Sprintf("A%d", rowIndex), styleBold) rowIndex++ for idx, modeVer := range aHubUpdateItem.DetailList { excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+1, rowIndex, "FileName") excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+1, rowIndex+1, "FileOrder") excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+1, rowIndex+2, "ResultDesc") excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+1, rowIndex+3, "OldVer") excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+1, rowIndex+4, "NewVer") excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+2, rowIndex, modeVer.FileName) excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+2, rowIndex+1, modeVer.FileOrder) excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+2, rowIndex+2, modeVer.ResultDesc) excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+2, rowIndex+3, modeVer.OldVer) excelSetCellValue(f, aHubUpdateSheetName, (3*idx)+2, rowIndex+4, modeVer.NewVer) } rowIndex = rowIndex + 6 } // 创建临时保存文件路径 tempPath, err := createTempPath("updateLog") if err != nil { return } defer os.RemoveAll(tempPath) // 程序结束时清理临时目录 fileName := fmt.Sprintf("UpdateLog_%s_%s.xlsx", sn, strings.ReplaceAll(time.Now().Format(time.DateTime), " ", "_")) filePath := filepath.Join(tempPath, fileName) // 保存到文件 err = f.SaveAs(filePath) if err != nil { zap.L().Error("Excel文件save失败", zap.String("path", filePath), zap.String("err", err.Error())) return } // 上传到文件服务器 objectName = fmt.Sprintf("%s/%s/%s/%s", mainPathName, sn, subUpdateLogPathName, fileName) if err = minio.UploadFile(filePath, objectName); err != nil { zap.L().Error("上传Excel文件失败", zap.String("path", filePath), zap.String("objectName", objectName), zap.String("err", err.Error())) return } return } // 保存Settings excel到文件服务器 // return objName // func SaveBatchDownloadSettings(sn string, dateStr string, settingsData []domain.SiteSettings) (string, error) { // }