feat: 记录查询列表补齐 ICCID、设备号、套餐名称、运营商名称与操作人
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 4m7s

分页后按当前页 ID 批量查主键补齐展示字段,不在分页查询中关联,也不在记录表冗余:
- 采集成功记录补 ICCID、运营商名称;采集失败、卡日流量补运营商名称
- 套餐日流量详单、冲正流水补套餐名称、ICCID、设备号与 IMEI
- 冲正流水(含资产详情)补操作人用户名;退款后处理尝试记录补设备号与 IMEI
This commit is contained in:
邹进
2026-11-07 17:07:27 +09:00
parent 7d705cf1e8
commit 3f593b912d
7 changed files with 321 additions and 30 deletions

View File

@@ -0,0 +1,142 @@
// Package recorddisplay 为记录查询列表补齐展示字段(ICCID、设备号、运营商名称、操作人)。
//
// 记录表只存内部 ID;列表先分页,再按当前页出现的 ID 批量查主键补齐,
// 不在分页查询里关联,也不在记录表里冗余这些字段。查到的是当前值。
package recorddisplay
import (
"context"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// Device 是设备展示字段。
type Device struct {
VirtualNo string
IMEI string
}
// Lookup 是当前页的展示字段字典;未找到的 ID 返回零值。
type Lookup struct {
iccids map[uint]string
devices map[uint]Device
carriers map[uint]string
operators map[uint]string
packages map[uint]string
}
// Request 是需要补齐的 ID 集合(可重复、可含 0,内部去重并忽略 0)。
type Request struct {
CardIDs []uint
DeviceIDs []uint
CarrierIDs []uint
OperatorIDs []uint
// PackageUsageIDs 用于取套餐使用记录上的套餐名称快照。
PackageUsageIDs []uint
}
// Load 按 ID 批量查询展示字段:每类最多一条主键查询,已删除的资产同样返回(历史记录仍需显示)。
func Load(ctx context.Context, db *gorm.DB, req Request) (*Lookup, error) {
lookup := &Lookup{
iccids: map[uint]string{}, devices: map[uint]Device{}, carriers: map[uint]string{}, operators: map[uint]string{},
packages: map[uint]string{},
}
session := db.WithContext(ctx).Unscoped()
if ids := distinct(req.CardIDs); len(ids) > 0 {
var rows []struct {
ID uint
ICCID string
}
if err := session.Model(&model.IotCard{}).Select("id, iccid").Where("id IN ?", ids).Scan(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询卡展示信息失败")
}
for _, row := range rows {
lookup.iccids[row.ID] = row.ICCID
}
}
if ids := distinct(req.DeviceIDs); len(ids) > 0 {
var rows []struct {
ID uint
VirtualNo string
IMEI string
}
if err := session.Model(&model.Device{}).Select("id, virtual_no, imei").Where("id IN ?", ids).Scan(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询设备展示信息失败")
}
for _, row := range rows {
lookup.devices[row.ID] = Device{VirtualNo: row.VirtualNo, IMEI: row.IMEI}
}
}
if ids := distinct(req.CarrierIDs); len(ids) > 0 {
var rows []struct {
ID uint
CarrierName string
}
if err := session.Model(&model.Carrier{}).Select("id, carrier_name").Where("id IN ?", ids).Scan(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询运营商账户展示信息失败")
}
for _, row := range rows {
lookup.carriers[row.ID] = row.CarrierName
}
}
if ids := distinct(req.OperatorIDs); len(ids) > 0 {
var rows []struct {
ID uint
Username string
}
if err := session.Model(&model.Account{}).Select("id, username").Where("id IN ?", ids).Scan(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询操作人展示信息失败")
}
for _, row := range rows {
lookup.operators[row.ID] = row.Username
}
}
if ids := distinct(req.PackageUsageIDs); len(ids) > 0 {
var rows []struct {
ID uint
PackageName string
}
if err := session.Model(&model.PackageUsage{}).Select("id, package_name").Where("id IN ?", ids).Scan(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐名称失败")
}
for _, row := range rows {
lookup.packages[row.ID] = row.PackageName
}
}
return lookup, nil
}
// ICCID 返回卡 ICCID。
func (l *Lookup) ICCID(cardID uint) string { return l.iccids[cardID] }
// Device 返回设备号与 IMEI。
func (l *Lookup) Device(deviceID uint) Device { return l.devices[deviceID] }
// CarrierName 返回运营商账户名称。
func (l *Lookup) CarrierName(carrierID uint) string { return l.carriers[carrierID] }
// PackageName 返回套餐使用记录上的套餐名称快照。
func (l *Lookup) PackageName(packageUsageID uint) string { return l.packages[packageUsageID] }
// OperatorName 返回操作人账号用户名。
func (l *Lookup) OperatorName(accountID uint) string { return l.operators[accountID] }
func distinct(ids []uint) []uint {
seen := make(map[uint]struct{}, len(ids))
result := make([]uint, 0, len(ids))
for _, id := range ids {
if id == 0 {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
result = append(result, id)
}
return result
}

View File

@@ -0,0 +1,50 @@
package recorddisplay
import (
"reflect"
"testing"
)
func TestDistinctSkipsZeroAndDuplicates(t *testing.T) {
tests := []struct {
name string
in []uint
want []uint
}{
{name: "空", in: nil, want: []uint{}},
{name: "只有 0", in: []uint{0, 0}, want: []uint{}},
{name: "去重并保持顺序", in: []uint{3, 0, 1, 3, 2, 1}, want: []uint{3, 1, 2}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := distinct(tt.in); !reflect.DeepEqual(got, tt.want) {
t.Fatalf("distinct(%v) = %v, want %v", tt.in, got, tt.want)
}
})
}
}
func TestLookupMissingIDsReturnZeroValues(t *testing.T) {
lookup := &Lookup{
iccids: map[uint]string{1: "8986001"},
devices: map[uint]Device{2: {VirtualNo: "60003081372", IMEI: "869555083099518"}},
carriers: map[uint]string{},
operators: map[uint]string{},
packages: map[uint]string{},
}
if got := lookup.ICCID(1); got != "8986001" {
t.Fatalf("ICCID(1) = %q", got)
}
if got := lookup.ICCID(9); got != "" {
t.Fatalf("ICCID(9) = %q, want empty", got)
}
if got := lookup.Device(2); got.VirtualNo != "60003081372" || got.IMEI != "869555083099518" {
t.Fatalf("Device(2) = %+v", got)
}
if got := lookup.Device(0); got != (Device{}) {
t.Fatalf("Device(0) = %+v, want zero", got)
}
if lookup.CarrierName(5) != "" || lookup.OperatorName(5) != "" || lookup.PackageName(5) != "" {
t.Fatal("missing IDs must return empty strings")
}
}