package app import ( "context" "fmt" "os" "strings" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-app/internal/service" "git.5iot.com/iotlcm-public/iotlcm-app/utility/hash" "github.com/gogf/gf/v2/crypto/gmd5" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" "github.com/xuri/excelize/v2" ) type appAllocateBatchLogFiles struct { ImportOssId int64 ImportFileName string ResultOssId int64 DownloadId int64 } func (s *sAppCard) ensureAppAllocateBatchLogFiles(ctx context.Context, tx gdb.TX, user *entity.SysUser, batch *appAllocateBatchContext, existingInfo *gjson.Json, batchNo string, sourceBatchNo string, remark string, now *gtime.Time) (*appAllocateBatchLogFiles, error) { batchNo = strings.TrimSpace(batchNo) if batchNo == "" { return nil, gerror.New("划拨批次号不能为空") } if user == nil { return nil, gerror.New("当前用户不存在") } if batch == nil || batch.Target == nil { return nil, gerror.New("划拨批次日志文件信息不完整") } files := appAllocateBatchLogFilesFromInfo(existingInfo) existingDownload, err := appAllocateBatchResultDownload(ctx, tx, batchNo) if err != nil { return nil, err } if existingDownload != nil { files.ResultOssId = existingDownload.ResultOssId files.DownloadId = existingDownload.DownloadId } if files.ImportOssId <= 0 { importContent, err := appBuildAllocateBatchImportExcel(batch, sourceBatchNo, remark) if err != nil { return nil, gerror.Wrap(err, "生成划拨批次源文件失败") } importUpload, importOssId, err := appSaveAllocateBatchOss(ctx, tx, user, user.DeptId, importContent, batchNo+"-import.xlsx", consts.SimCardAllocateSubDirImportExcel, now) if err != nil { return nil, gerror.Wrap(err, "保存划拨批次源文件失败") } files.ImportOssId = importOssId files.ImportFileName = strings.TrimSpace(importUpload.Name) } if strings.TrimSpace(files.ImportFileName) == "" { files.ImportFileName = appAllocateBatchImportFileName(ctx, tx, files.ImportOssId, batchNo+"-import.xlsx") } if existingDownload != nil { return files, nil } resultContent, err := appBuildAllocateBatchResultExcel(batch, files, batchNo, sourceBatchNo, now, user.UserId, 0) if err != nil { return nil, gerror.Wrap(err, "生成划拨批次结果文件失败") } resultUpload, resultOssId, err := appSaveAllocateBatchOss(ctx, tx, user, user.DeptId, resultContent, batchNo+"-result.xlsx", consts.SimCardAllocateSubDirResultExcel, now) if err != nil { return nil, gerror.Wrap(err, "保存划拨批次结果文件失败") } files.ResultOssId = resultOssId if err = appFillAllocateResultOssId(ctx, tx, resultOssId, resultUpload.Path, user.UserId, now); err != nil { return nil, err } refreshedResult, err := appLoadAllocateOssUpload(ctx, tx, resultOssId, resultUpload) if err != nil { return nil, err } quantity := len(batch.Cards) downloadId, err := dao.TResourceBatchDownload.Ctx(ctx).TX(tx).Data(do.TResourceBatchDownload{ BatchNo: batchNo, TaskName: consts.ResourceBatchDownloadTaskSimCardAllocate, ResourceType: consts.ResourceTypeExcel, ExportScene: consts.ResourceExportSceneAllocate, TotalCount: quantity, SuccessCount: quantity, FailCount: 0, DownloadStatus: consts.DownloadStatusGenerated, OssId: resultOssId, FileName: refreshedResult.Name, FileExt: refreshedResult.Ext, FileUrl: refreshedResult.FullPath, FilePath: refreshedResult.Path, FileSize: refreshedResult.Size, FileMd5: refreshedResult.Md5, DownloadCount: 0, MaxDownloadCount: 0, DeptId: user.DeptId, StartTime: now, FinishTime: now, TenantId: user.TenantId, CreatedDept: user.DeptId, CreatedBy: user.UserId, CreatedAt: now, UpdatedBy: user.UserId, UpdatedAt: now, }).InsertAndGetId() if err != nil { return nil, gerror.Wrap(err, "写入划拨批次结果下载记录失败") } files.DownloadId = downloadId return files, nil } func appAllocateBatchLogFilesFromInfo(info *gjson.Json) *appAllocateBatchLogFiles { files := &appAllocateBatchLogFiles{} if info == nil { return files } files.ImportOssId = info.Get("importFileOssId").Int64() files.ResultOssId = info.Get("resultFileOssId").Int64() files.DownloadId = info.Get("resultDownloadId").Int64() return files } func appAllocateBatchResultDownload(ctx context.Context, tx gdb.TX, batchNo string) (*appAllocateBatchLogFiles, error) { var downloads []*entity.TResourceBatchDownload columns := dao.TResourceBatchDownload.Columns() err := dao.TResourceBatchDownload.Ctx(ctx).TX(tx). Fields(columns.Id, columns.OssId). Where(columns.BatchNo, strings.TrimSpace(batchNo)). OrderDesc(columns.Id). Limit(1). Scan(&downloads) if err != nil { return nil, gerror.Wrap(err, "查询划拨批次结果下载记录失败") } if len(downloads) == 0 || downloads[0] == nil || downloads[0].Id == 0 { return nil, nil } return &appAllocateBatchLogFiles{ DownloadId: downloads[0].Id, ResultOssId: downloads[0].OssId, }, nil } func appAllocateBatchInfoWithLogFiles(info *gjson.Json, files *appAllocateBatchLogFiles) *gjson.Json { data := map[string]any{} if info != nil { for key, value := range info.Map() { data[key] = value } } if files != nil { if files.ImportOssId > 0 { data["importFileOssId"] = files.ImportOssId } if files.ResultOssId > 0 { data["resultFileOssId"] = files.ResultOssId } if files.DownloadId > 0 { data["resultDownloadId"] = files.DownloadId } } return gjson.New(data) } func appBuildAllocateBatchImportExcel(batch *appAllocateBatchContext, sourceBatchNo string, remark string) ([]byte, error) { f := excelize.NewFile() defer f.Close() sheet := "Sheet1" headers := []string{"ICCID", "MSISDN", "IMSI", "sourceBatchNo", "sourceDeptId", "targetDeptId", "targetDeptName", "remark"} if err := f.SetSheetRow(sheet, "A1", &headers); err != nil { return nil, err } if err := appApplyHeaderColumnsTextStyle(f, sheet); err != nil { return nil, err } for index, item := range batch.Cards { row := []interface{}{ strings.TrimSpace(item.Card.Iccid), strings.TrimSpace(item.Card.Msisdn), strings.TrimSpace(item.Card.Imsi), strings.TrimSpace(sourceBatchNo), item.SourceDeptId, batch.Target.DeptId, strings.TrimSpace(batch.Target.DeptName), strings.TrimSpace(remark), } cell, err := excelize.CoordinatesToCellName(1, index+2) if err != nil { return nil, err } if err = f.SetSheetRow(sheet, cell, &row); err != nil { return nil, err } } _ = f.SetColWidth(sheet, "A", "A", 24) _ = f.SetColWidth(sheet, "B", "C", 18) _ = f.SetColWidth(sheet, "D", "H", 18) return appExcelBytes(f) } func appBuildAllocateBatchResultExcel(batch *appAllocateBatchContext, files *appAllocateBatchLogFiles, batchNo string, sourceBatchNo string, now *gtime.Time, userId int64, resultOssId int64) ([]byte, error) { f := excelize.NewFile() defer f.Close() leftStyleID, err := f.NewStyle(&excelize.Style{ Alignment: &excelize.Alignment{ Horizontal: "left", Vertical: "center", }, }) if err != nil { return nil, err } resultSheet := "导入结果" if err = f.SetSheetName("Sheet1", resultSheet); err != nil { return nil, err } headers := []string{"源文件行号", "item_id", "处理状态", "异常原因"} if err = f.SetSheetRow(resultSheet, "A1", &headers); err != nil { return nil, err } for index, item := range batch.Cards { row := []interface{}{ item.RowNo, strings.TrimSpace(item.Card.Iccid), "成功", "", } cell, err := excelize.CoordinatesToCellName(1, index+2) if err != nil { return nil, err } if err = f.SetSheetRow(resultSheet, cell, &row); err != nil { return nil, err } } resultEndCell, err := excelize.CoordinatesToCellName(4, len(batch.Cards)+1) if err != nil { return nil, err } if err = f.SetCellStyle(resultSheet, "A1", resultEndCell, leftStyleID); err != nil { return nil, err } infoSheet := "批次信息" if _, err = f.NewSheet(infoSheet); err != nil { return nil, err } infoHeaders := []string{"字段", "值"} if err = f.SetSheetRow(infoSheet, "A1", &infoHeaders); err != nil { return nil, err } infoRows := appAllocateBatchResultInfoRows(batch, files, batchNo, sourceBatchNo, now, userId, resultOssId) for index, item := range infoRows { row := []interface{}{item[0], item[1]} cell, err := excelize.CoordinatesToCellName(1, index+2) if err != nil { return nil, err } if err = f.SetSheetRow(infoSheet, cell, &row); err != nil { return nil, err } } infoEndCell, err := excelize.CoordinatesToCellName(2, len(infoRows)+1) if err != nil { return nil, err } if err = f.SetCellStyle(infoSheet, "A1", infoEndCell, leftStyleID); err != nil { return nil, err } _ = f.SetColWidth(resultSheet, "A", "A", 14) _ = f.SetColWidth(resultSheet, "B", "B", 24) _ = f.SetColWidth(resultSheet, "C", "C", 12) _ = f.SetColWidth(resultSheet, "D", "D", 40) _ = f.SetColWidth(infoSheet, "A", "B", 24) f.SetActiveSheet(0) return appExcelBytes(f) } func appAllocateBatchResultInfoRows(batch *appAllocateBatchContext, files *appAllocateBatchLogFiles, batchNo string, sourceBatchNo string, now *gtime.Time, userId int64, resultOssId int64) [][2]string { importFileName := "" importOssId := "" if files != nil { importFileName = strings.TrimSpace(files.ImportFileName) if files.ImportOssId > 0 { importOssId = fmt.Sprintf("%d", files.ImportOssId) } } if importFileName == "" { importFileName = strings.TrimSpace(batchNo) + "-import.xlsx" } resultFileOssId := "" if resultOssId > 0 { resultFileOssId = fmt.Sprintf("%d", resultOssId) } return [][2]string{ {"批次号", strings.TrimSpace(batchNo)}, {"批次类型", "卡划拨"}, {"处理结果", "成功"}, {"总行数", fmt.Sprintf("%d", len(batch.Cards))}, {"校验通过行数", fmt.Sprintf("%d", len(batch.Cards))}, {"失败行数", "0"}, {"实际写入行数", fmt.Sprintf("%d", len(batch.Cards))}, {"源批次号", strings.TrimSpace(sourceBatchNo)}, {"目标机构", fmt.Sprintf("%d %s", batch.Target.DeptId, strings.TrimSpace(batch.Target.DeptName))}, {"原始文件名", importFileName}, {"原始文件OSS ID", importOssId}, {"结果文件OSS ID", resultFileOssId}, {"操作人", fmt.Sprintf("%d", userId)}, {"开始时间", appRenewFormatTime(now)}, {"结束时间", appRenewFormatTime(now)}, {"备注", "导入成功"}, } } func appSaveAllocateBatchOss(ctx context.Context, tx gdb.TX, user *entity.SysUser, deptId int64, content []byte, fileName string, subDirName string, now *gtime.Time) (model.UploadResponse, int64, error) { upload, err := service.SysOss().SaveContentLocal(ctx, content, consts.SysUploadNewFileTypeUserFile, fileName, subDirName, 0) if err != nil { return upload, 0, err } ossId, err := dao.SysOss.Ctx(ctx).TX(tx).Data(do.SysOss{ TenantId: user.TenantId, FileName: upload.Name, OriginalName: upload.OriginalName, FileSuffix: upload.Ext, Path: upload.Path, Url: upload.FullPath, CreatedDept: deptId, CreatedAt: now, CreatedBy: user.UserId, UpdatedAt: now, UpdatedBy: user.UserId, Service: "local", Md5: upload.Md5, FileSize: upload.Size, FileCrc16: upload.Crc16, FileSum: upload.Sum16, }).InsertAndGetId() if err != nil { return upload, 0, gerror.Wrap(err, "写入划拨批次OSS记录失败") } return upload, ossId, nil } func appFillAllocateResultOssId(ctx context.Context, tx gdb.TX, ossId int64, filePath string, userId int64, now *gtime.Time) error { if ossId <= 0 || strings.TrimSpace(filePath) == "" { return gerror.New("划拨结果文件OSS信息不完整") } f, err := excelize.OpenFile(filePath) if err != nil { return gerror.Wrap(err, "打开划拨结果文件失败") } defer f.Close() rows, err := f.GetRows("批次信息") if err != nil { return gerror.Wrap(err, "读取划拨结果批次信息失败") } found := false for index, row := range rows { if len(row) == 0 || strings.TrimSpace(row[0]) != "结果文件OSS ID" { continue } cell, err := excelize.CoordinatesToCellName(2, index+1) if err != nil { return err } if err = f.SetCellStr("批次信息", cell, fmt.Sprintf("%d", ossId)); err != nil { return err } found = true break } if !found { return gerror.New("划拨结果文件缺少结果文件OSS ID字段") } if err = f.SaveAs(filePath); err != nil { return gerror.Wrap(err, "回写划拨结果文件OSS ID失败") } return appRefreshAllocateOssFileInfo(ctx, tx, ossId, filePath, userId, now) } func appRefreshAllocateOssFileInfo(ctx context.Context, tx gdb.TX, ossId int64, filePath string, userId int64, now *gtime.Time) error { fileInfo, err := os.Stat(filePath) if err != nil { return gerror.Wrap(err, "读取划拨结果文件信息失败") } md5, _ := gmd5.EncryptFile(filePath) crc16 := hash.Crc16ChecksumFile(filePath) sum16 := hash.Sum16ChecksumFile(filePath) _, err = dao.SysOss.Ctx(ctx).TX(tx). Where(dao.SysOss.Columns().OssId, ossId). Data(do.SysOss{ Md5: md5, FileSize: fileInfo.Size(), FileCrc16: crc16, FileSum: sum16, UpdatedAt: now, UpdatedBy: userId, }). Update() if err != nil { return gerror.Wrap(err, "更新划拨结果文件信息失败") } return nil } func appLoadAllocateOssUpload(ctx context.Context, tx gdb.TX, ossId int64, fallback model.UploadResponse) (model.UploadResponse, error) { if ossId <= 0 { return fallback, nil } var oss *entity.SysOss err := dao.SysOss.Ctx(ctx).TX(tx). Where(dao.SysOss.Columns().OssId, ossId). Limit(1). Scan(&oss) if err != nil { return fallback, gerror.Wrap(err, "读取划拨结果文件OSS记录失败") } if oss == nil || oss.OssId == 0 { return fallback, nil } fallback.Name = oss.FileName fallback.OriginalName = oss.OriginalName fallback.Ext = oss.FileSuffix fallback.Path = oss.Path fallback.FullPath = oss.Url fallback.Size = int64(oss.FileSize) fallback.Md5 = oss.Md5 return fallback, nil } func appAllocateBatchImportFileName(ctx context.Context, tx gdb.TX, ossId int64, sourceName string) string { sourceName = strings.TrimSpace(sourceName) if ossId <= 0 { return sourceName } var oss *entity.SysOss if err := dao.SysOss.Ctx(ctx).TX(tx). Fields(dao.SysOss.Columns().FileName). Where(dao.SysOss.Columns().OssId, ossId). Limit(1). Scan(&oss); err != nil { return sourceName } if oss != nil && strings.TrimSpace(oss.FileName) != "" { return strings.TrimSpace(oss.FileName) } return sourceName }