feat: 新增数据库迁移,重命名 device_no 为 virtual_no,新增 iot_card.virtual_no 和 package.virtual_ratio 字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -9,10 +9,11 @@ import (
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
// CardInfo 卡信息(ICCID + MSISDN)
|
||||
// CardInfo 卡信息(ICCID + MSISDN + VirtualNo)
|
||||
type CardInfo struct {
|
||||
ICCID string
|
||||
MSISDN string
|
||||
ICCID string
|
||||
MSISDN string
|
||||
VirtualNo string
|
||||
}
|
||||
|
||||
// CSVParseResult Excel/CSV 解析结果
|
||||
@@ -33,7 +34,7 @@ type CSVParseError struct {
|
||||
// DeviceRow 设备导入数据行
|
||||
type DeviceRow struct {
|
||||
Line int
|
||||
DeviceNo string
|
||||
VirtualNo string
|
||||
DeviceName string
|
||||
DeviceModel string
|
||||
DeviceType string
|
||||
@@ -128,8 +129,8 @@ func ParseDeviceExcel(filePath string) ([]DeviceRow, int, error) {
|
||||
row := DeviceRow{Line: lineNum}
|
||||
|
||||
// 提取各字段
|
||||
if idx := colIndex["device_no"]; idx >= 0 && idx < len(record) {
|
||||
row.DeviceNo = strings.TrimSpace(record[idx])
|
||||
if idx := colIndex["virtual_no"]; idx >= 0 && idx < len(record) {
|
||||
row.VirtualNo = strings.TrimSpace(record[idx])
|
||||
}
|
||||
if idx := colIndex["device_name"]; idx >= 0 && idx < len(record) {
|
||||
row.DeviceName = strings.TrimSpace(record[idx])
|
||||
@@ -161,8 +162,8 @@ func ParseDeviceExcel(filePath string) ([]DeviceRow, int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 跳过设备号为空的行
|
||||
if row.DeviceNo == "" {
|
||||
// 跳过虚拟号为空的行
|
||||
if row.VirtualNo == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -204,47 +205,44 @@ func parseCardRows(rows [][]string) (*CSVParseResult, error) {
|
||||
ParseErrors: make([]CSVParseError, 0),
|
||||
}
|
||||
|
||||
// 检测表头 (第1行)
|
||||
headerSkipped := false
|
||||
iccidCol, msisdnCol := -1, -1
|
||||
iccidCol, msisdnCol, virtualNoCol := -1, -1, -1
|
||||
|
||||
if len(rows) > 0 {
|
||||
iccidCol, msisdnCol = findCardColumns(rows[0])
|
||||
iccidCol, msisdnCol, virtualNoCol = findCardColumns(rows[0])
|
||||
if iccidCol >= 0 && msisdnCol >= 0 {
|
||||
headerSkipped = true
|
||||
}
|
||||
}
|
||||
|
||||
// 确定数据开始行
|
||||
startLine := 0
|
||||
if headerSkipped {
|
||||
startLine = 1
|
||||
}
|
||||
|
||||
// 解析数据行
|
||||
for i := startLine; i < len(rows); i++ {
|
||||
row := rows[i]
|
||||
lineNum := i + 1 // Excel行号从1开始
|
||||
lineNum := i + 1
|
||||
|
||||
// 跳过空行
|
||||
if len(row) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 提取字段
|
||||
iccid := ""
|
||||
msisdn := ""
|
||||
virtualNo := ""
|
||||
|
||||
if iccidCol >= 0 {
|
||||
// 有表头,使用列索引
|
||||
if iccidCol < len(row) {
|
||||
iccid = strings.TrimSpace(row[iccidCol])
|
||||
}
|
||||
if msisdnCol < len(row) {
|
||||
msisdn = strings.TrimSpace(row[msisdnCol])
|
||||
}
|
||||
if virtualNoCol >= 0 && virtualNoCol < len(row) {
|
||||
virtualNo = strings.TrimSpace(row[virtualNoCol])
|
||||
}
|
||||
} else {
|
||||
// 无表头,假设第一列ICCID,第二列MSISDN
|
||||
if len(row) >= 1 {
|
||||
iccid = strings.TrimSpace(row[0])
|
||||
}
|
||||
@@ -253,11 +251,9 @@ func parseCardRows(rows [][]string) (*CSVParseResult, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 验证
|
||||
result.TotalCount++
|
||||
|
||||
if iccid == "" && msisdn == "" {
|
||||
// 空行,跳过
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -280,45 +276,50 @@ func parseCardRows(rows [][]string) (*CSVParseResult, error) {
|
||||
}
|
||||
|
||||
result.Cards = append(result.Cards, CardInfo{
|
||||
ICCID: iccid,
|
||||
MSISDN: msisdn,
|
||||
ICCID: iccid,
|
||||
MSISDN: msisdn,
|
||||
VirtualNo: virtualNo,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// findCardColumns 查找ICCID和MSISDN列索引
|
||||
// findCardColumns 查找ICCID、MSISDN和VirtualNo列索引
|
||||
// 支持中英文列名识别
|
||||
func findCardColumns(header []string) (iccidCol, msisdnCol int) {
|
||||
iccidCol, msisdnCol = -1, -1
|
||||
func findCardColumns(header []string) (iccidCol, msisdnCol, virtualNoCol int) {
|
||||
iccidCol, msisdnCol, virtualNoCol = -1, -1, -1
|
||||
|
||||
for i, col := range header {
|
||||
colLower := strings.ToLower(strings.TrimSpace(col))
|
||||
|
||||
// 识别ICCID列
|
||||
if colLower == "iccid" || colLower == "卡号" || colLower == "号码" {
|
||||
if iccidCol == -1 { // 只取第一个匹配
|
||||
if iccidCol == -1 {
|
||||
iccidCol = i
|
||||
}
|
||||
}
|
||||
|
||||
// 识别MSISDN列
|
||||
if colLower == "msisdn" || colLower == "接入号" || colLower == "手机号" || colLower == "电话" {
|
||||
if msisdnCol == -1 { // 只取第一个匹配
|
||||
if msisdnCol == -1 {
|
||||
msisdnCol = i
|
||||
}
|
||||
}
|
||||
|
||||
if colLower == "virtual_no" || colLower == "virtualno" || colLower == "虚拟号" || colLower == "设备号" {
|
||||
if virtualNoCol == -1 {
|
||||
virtualNoCol = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iccidCol, msisdnCol
|
||||
return iccidCol, msisdnCol, virtualNoCol
|
||||
}
|
||||
|
||||
// buildDeviceColumnIndex 构建设备导入列索引
|
||||
// 识别表头中的列名,返回列名到列索引的映射
|
||||
func buildDeviceColumnIndex(header []string) map[string]int {
|
||||
index := map[string]int{
|
||||
"device_no": -1,
|
||||
"virtual_no": -1,
|
||||
"device_name": -1,
|
||||
"device_model": -1,
|
||||
"device_type": -1,
|
||||
|
||||
Reference in New Issue
Block a user