# 需求08:设备批量分配代理和套餐系列(Excel导入) --- ## 背景 设备号不连续,无法通过号段批量分配。需要通过上传 Excel 表(表头:设备号)来批量指定分配目标。 `Device` 表已有 `shop_id *uint` 和 `series_id *uint` 字段,分配即更新这两个字段。 --- ## 数据库变更 新建批量分配任务表(不复用 `DeviceImportTask`,业务语义不同): ```sql CREATE TABLE tb_device_batch_allocation_task ( id BIGSERIAL PRIMARY KEY, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ, creator BIGINT NOT NULL DEFAULT 0, updater BIGINT NOT NULL DEFAULT 0, task_no VARCHAR(30) NOT NULL, shop_id BIGINT NOT NULL, series_id BIGINT, operator_id BIGINT NOT NULL, total_count INT NOT NULL DEFAULT 0, success_count INT NOT NULL DEFAULT 0, fail_count INT NOT NULL DEFAULT 0, status INT NOT NULL DEFAULT 1, failed_items JSONB, started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ ); CREATE UNIQUE INDEX idx_device_batch_allocation_task_no ON tb_device_batch_allocation_task(task_no) WHERE deleted_at IS NULL; ``` 状态常量:1=处理中,2=已完成,3=失败。 失败明细存 JSONB(`failed_items`),失败条数有限,无需单独明细表。 --- ## Model(`internal/model/device_batch_allocation_task.go`) ```go // DeviceBatchAllocationTask 设备批量分配任务模型 type DeviceBatchAllocationTask struct { gorm.Model BaseModel `gorm:"embedded"` TaskNo string `gorm:"column:task_no;type:varchar(30);uniqueIndex:idx_device_batch_allocation_task_no,where:deleted_at IS NULL;not null" json:"task_no"` ShopID uint `gorm:"column:shop_id;not null;comment:分配目标代理店铺ID" json:"shop_id"` SeriesID *uint `gorm:"column:series_id;comment:套餐系列ID(可选)" json:"series_id,omitempty"` OperatorID uint `gorm:"column:operator_id;not null;comment:操作人ID" json:"operator_id"` TotalCount int `gorm:"column:total_count;default:0;comment:总记录数" json:"total_count"` SuccessCount int `gorm:"column:success_count;default:0;comment:成功数" json:"success_count"` FailCount int `gorm:"column:fail_count;default:0;comment:失败数" json:"fail_count"` Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1=处理中 2=已完成 3=失败" json:"status"` FailedItems ImportResultItems `gorm:"column:failed_items;type:jsonb;comment:失败记录详情" json:"failed_items"` StartedAt *time.Time `gorm:"column:started_at" json:"started_at"` CompletedAt *time.Time `gorm:"column:completed_at" json:"completed_at"` } func (DeviceBatchAllocationTask) TableName() string { return "tb_device_batch_allocation_task" } ``` > `ImportResultItems` 复用 `internal/model/device_import_task.go` 中已定义的类型。 --- ## API 设计 ### 1. 下载 Excel 模板 ``` GET /admin/devices/batch-allocation/template ``` 响应:Excel 文件,表头为"设备号"(一列)。 ### 2. 上传并提交 ``` POST /admin/devices/batch-allocation Content-Type: multipart/form-data 字段: shop_id: 123 (必填,分配给哪个代理) series_id: 45 (可选,同时分配套餐系列) file: ``` **处理逻辑(Asynq 异步)**: 1. 解析 Excel,读取"设备号"列 2. 逐行查找 `tb_device` 中匹配的设备(按 `virtual_no` 或 `imei`) 3. 对找到的设备:更新 `shop_id` + `series_id` 4. 未找到的:记录失败原因"设备号不存在" 5. 已分配给其他代理的:记录"该设备已分配,请先回收" ### 3. 查询任务状态 ``` GET /admin/devices/batch-allocation/{task_id} ``` 响应: ```json { "task_no": "DBA20240101001", "status": 2, "status_name": "已完成", "total_count": 100, "success_count": 95, "fail_count": 5, "failed_items": [ { "line": 3, "virtual_no": "12345", "reason": "设备号不存在" }, { "line": 7, "virtual_no": "67890", "reason": "该设备已分配,请先回收" } ], "started_at": "2024-01-01T10:00:00Z", "completed_at": "2024-01-01T10:00:05Z" } ``` --- ## 前端对接 ### 入口 设备管理页 > 批量操作 > "批量分配代理" ### 操作流程 1. 点击"批量分配代理" 2. 弹框内: - 代理选择器(搜索+选择目标代理,必填) - 套餐系列选择器(可选) - Excel 上传区域(含模板下载链接) 3. 上传后点击"提交" → `POST /admin/devices/batch-allocation` 4. 提示"任务提交成功,正在处理..." 5. 轮询 `GET /admin/devices/batch-allocation/{task_id}` 直到 `status != 1` 6. 展示结果:成功X条,失败X条,失败明细在页面展示 ### Excel 规范 - 表头:`设备号` - 每行一个设备号(支持虚拟号或IMEI)