统一导出任务
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m4s

This commit is contained in:
2026-04-27 09:47:03 +08:00
parent 531de3c760
commit 60debb7505
29 changed files with 2580 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
package dto
import "time"
// CreateExportTaskRequest 创建导出任务请求。
type CreateExportTaskRequest struct {
Scene string `json:"scene" validate:"required,oneof=device iot_card" required:"true" description:"导出场景 (device:设备, iot_card:IoT卡)"`
Format string `json:"format" validate:"required,oneof=xlsx csv" required:"true" description:"导出格式 (xlsx:Excel, csv:CSV)"`
Query map[string]interface{} `json:"query,omitempty" description:"导出筛选参数(JSON对象可选)"`
}
// CreateExportTaskResponse 创建导出任务响应。
type CreateExportTaskResponse struct {
TaskID uint `json:"task_id" description:"导出任务ID"`
TaskNo string `json:"task_no" description:"任务编号"`
Status int `json:"status" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:已失败, 5:已取消)"`
StatusName string `json:"status_name" description:"任务状态名称(中文)"`
Message string `json:"message" description:"提示信息"`
}
// ListExportTaskRequest 导出任务列表请求。
type ListExportTaskRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
Scene string `json:"scene" query:"scene" validate:"omitempty,oneof=device iot_card" description:"导出场景 (device:设备, iot_card:IoT卡)"`
Status *int `json:"status" query:"status" validate:"omitempty,min=1,max=5" minimum:"1" maximum:"5" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:已失败, 5:已取消)"`
StartTime *time.Time `json:"start_time" query:"start_time" description:"创建时间起始"`
EndTime *time.Time `json:"end_time" query:"end_time" description:"创建时间结束"`
}
// ExportTaskItem 导出任务列表项。
type ExportTaskItem struct {
ID uint `json:"id" description:"任务ID"`
TaskNo string `json:"task_no" description:"任务编号"`
Scene string `json:"scene" description:"导出场景 (device:设备, iot_card:IoT卡)"`
Format string `json:"format" description:"导出格式 (xlsx:Excel, csv:CSV)"`
Status int `json:"status" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:已失败, 5:已取消)"`
StatusName string `json:"status_name" description:"任务状态名称(中文)"`
Progress int `json:"progress" description:"任务进度(0-100)"`
TotalRows int `json:"total_rows" description:"总行数"`
ProcessedRows int `json:"processed_rows" description:"已处理行数"`
TotalShards int `json:"total_shards" description:"总分片数"`
SuccessShards int `json:"success_shards" description:"成功分片数"`
FailedShards int `json:"failed_shards" description:"失败分片数"`
CancelRequested bool `json:"cancel_requested" description:"是否已请求取消"`
FileKey string `json:"file_key,omitempty" description:"导出文件Key"`
ErrorMessage string `json:"error_message,omitempty" description:"错误信息"`
CreatedAt time.Time `json:"created_at" description:"创建时间"`
StartedAt *time.Time `json:"started_at,omitempty" description:"开始处理时间"`
CompletedAt *time.Time `json:"completed_at,omitempty" description:"完成时间"`
CreatorUserID uint `json:"creator_user_id" description:"创建人用户ID"`
CreatorUserType int `json:"creator_user_type" description:"创建人用户类型 (2:平台, 3:代理, 4:企业)"`
CreatorShopID *uint `json:"creator_shop_id,omitempty" description:"创建人店铺ID"`
CreatorEnterpriseID *uint `json:"creator_enterprise_id,omitempty" description:"创建人企业ID"`
}
// ListExportTaskResponse 导出任务列表响应。
type ListExportTaskResponse struct {
List []*ExportTaskItem `json:"items" description:"任务列表"`
Total int64 `json:"total" description:"总数"`
Page int `json:"page" description:"当前页码"`
PageSize int `json:"size" description:"每页数量"`
}
// GetExportTaskRequest 导出任务详情请求。
type GetExportTaskRequest struct {
ID uint `path:"id" description:"任务ID" required:"true"`
}
// ExportTaskDetailResponse 导出任务详情响应。
type ExportTaskDetailResponse struct {
ExportTaskItem
DownloadURL string `json:"download_url,omitempty" description:"下载链接(仅已完成任务返回)"`
DownloadExpiresAt *time.Time `json:"download_expires_at,omitempty" description:"下载链接过期时间"`
}
// CancelExportTaskRequest 取消导出任务请求。
type CancelExportTaskRequest struct {
ID uint `path:"id" description:"任务ID" required:"true"`
}
// CancelExportTaskResponse 取消导出任务响应。
type CancelExportTaskResponse struct {
TaskID uint `json:"task_id" description:"任务ID"`
Status int `json:"status" description:"任务状态 (1:待处理, 2:处理中, 3:已完成, 4:已失败, 5:已取消)"`
StatusName string `json:"status_name" description:"任务状态名称(中文)"`
CancelRequested bool `json:"cancel_requested" description:"是否已请求取消"`
Message string `json:"message" description:"提示信息"`
}

View File

@@ -0,0 +1,106 @@
package model
import (
"database/sql/driver"
"encoding/json"
"time"
"gorm.io/datatypes"
"gorm.io/gorm"
)
// UIntListJSON uint 数组 JSON 类型。
// 用于在 PostgreSQL jsonb 字段中存储店铺 ID 列表。
type UIntListJSON []uint
// Value 实现 driver.Valuer。
func (ids UIntListJSON) Value() (driver.Value, error) {
if ids == nil {
return "[]", nil
}
return json.Marshal(ids)
}
// Scan 实现 sql.Scanner。
func (ids *UIntListJSON) Scan(value any) error {
if value == nil {
*ids = UIntListJSON{}
return nil
}
switch v := value.(type) {
case []byte:
return json.Unmarshal(v, ids)
case string:
return json.Unmarshal([]byte(v), ids)
default:
*ids = UIntListJSON{}
return nil
}
}
// ExportTask 导出主任务模型。
// 记录导出任务的创建信息、执行进度、最终产物和取消状态。
type ExportTask struct {
gorm.Model
BaseModel `gorm:"embedded"`
TaskNo string `gorm:"column:task_no;type:varchar(50);uniqueIndex:idx_export_task_no,where:deleted_at IS NULL;not null;comment:任务编号(EXP-YYYYMMDD-XXXXXX)" json:"task_no"`
Scene string `gorm:"column:scene;type:varchar(50);index:idx_export_task_scene_status_created_at,priority:1;not null;comment:导出场景(device/iot_card)" json:"scene"`
Format string `gorm:"column:format;type:varchar(20);not null;comment:导出格式(xlsx/csv)" json:"format"`
Status int `gorm:"column:status;type:int;index:idx_export_task_scene_status_created_at,priority:2;not null;default:1;comment:任务状态 1-待处理 2-处理中 3-已完成 4-已失败 5-已取消" json:"status"`
Progress int `gorm:"column:progress;type:int;not null;default:0;comment:任务进度(0-100)" json:"progress"`
TotalRows int `gorm:"column:total_rows;type:int;not null;default:0;comment:总行数" json:"total_rows"`
ProcessedRows int `gorm:"column:processed_rows;type:int;not null;default:0;comment:已处理行数" json:"processed_rows"`
TotalShards int `gorm:"column:total_shards;type:int;not null;default:0;comment:总分片数" json:"total_shards"`
SuccessShards int `gorm:"column:success_shards;type:int;not null;default:0;comment:成功分片数" json:"success_shards"`
FailedShards int `gorm:"column:failed_shards;type:int;not null;default:0;comment:失败分片数" json:"failed_shards"`
CancelRequested bool `gorm:"column:cancel_requested;type:boolean;not null;default:false;index;comment:是否请求取消" json:"cancel_requested"`
QueryJSON datatypes.JSON `gorm:"column:query_json;type:jsonb;not null;default:'{}';comment:导出查询参数(JSON)" json:"query_json"`
ScopeShopIDs UIntListJSON `gorm:"column:scope_shop_ids;type:jsonb;not null;default:'[]';comment:导出时的数据权限店铺范围快照" json:"scope_shop_ids"`
CreatorUserID uint `gorm:"column:creator_user_id;type:bigint;not null;index;comment:创建人用户ID" json:"creator_user_id"`
CreatorUserType int `gorm:"column:creator_user_type;type:int;not null;comment:创建人用户类型 2-平台 3-代理 4-企业" json:"creator_user_type"`
CreatorShopID *uint `gorm:"column:creator_shop_id;type:bigint;index;comment:创建人店铺ID快照" json:"creator_shop_id,omitempty"`
CreatorEnterpriseID *uint `gorm:"column:creator_enterprise_id;type:bigint;index;comment:创建人企业ID快照" json:"creator_enterprise_id,omitempty"`
FileKey string `gorm:"column:file_key;type:varchar(500);comment:最终产物文件Key" json:"file_key"`
FileSize int64 `gorm:"column:file_size;type:bigint;not null;default:0;comment:最终产物文件大小(字节)" json:"file_size"`
ErrorMessage string `gorm:"column:error_message;type:text;comment:错误信息" json:"error_message"`
StartedAt *time.Time `gorm:"column:started_at;comment:开始处理时间" json:"started_at"`
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at"`
}
// TableName 指定表名。
func (ExportTask) TableName() string {
return "tb_export_task"
}
// ExportShardTask 导出分片任务模型。
// 记录主任务拆分后的每个分片执行状态和分片产物。
type ExportShardTask struct {
gorm.Model
BaseModel `gorm:"embedded"`
TaskID uint `gorm:"column:task_id;type:bigint;index:idx_export_shard_task_task_status,priority:1;not null;comment:主任务ID" json:"task_id"`
ShardNo int `gorm:"column:shard_no;type:int;not null;comment:分片序号(从1开始)" json:"shard_no"`
Status int `gorm:"column:status;type:int;index:idx_export_shard_task_task_status,priority:2;not null;default:1;comment:分片状态 1-待处理 2-处理中 3-已成功 4-已失败 5-已取消" json:"status"`
CursorStart uint64 `gorm:"column:cursor_start;type:bigint;not null;default:0;comment:分片游标起点(不含)" json:"cursor_start"`
CursorEnd uint64 `gorm:"column:cursor_end;type:bigint;not null;default:0;comment:分片游标终点(含)" json:"cursor_end"`
RowCount int `gorm:"column:row_count;type:int;not null;default:0;comment:分片行数" json:"row_count"`
ProcessedRows int `gorm:"column:processed_rows;type:int;not null;default:0;comment:分片已处理行数" json:"processed_rows"`
FileKey string `gorm:"column:file_key;type:varchar(500);comment:分片产物文件Key" json:"file_key"`
FileSize int64 `gorm:"column:file_size;type:bigint;not null;default:0;comment:分片产物文件大小(字节)" json:"file_size"`
ErrorMessage string `gorm:"column:error_message;type:text;comment:分片错误信息" json:"error_message"`
StartedAt *time.Time `gorm:"column:started_at;comment:开始处理时间" json:"started_at"`
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at"`
}
// TableName 指定表名。
func (ExportShardTask) TableName() string {
return "tb_export_shard_task"
}