chore: 初始开源发布
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user