270 lines
9.9 KiB
Go
270 lines
9.9 KiB
Go
package cardrealname
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/bytedance/sonic"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
|
)
|
|
|
|
// Query 提供单卡实名记录与二次实名批次查询。
|
|
type Query struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewQuery 创建实名记录查询。
|
|
func NewQuery(db *gorm.DB) *Query {
|
|
return &Query{db: db}
|
|
}
|
|
|
|
// CardHistory 返回单卡当前实名时间与实名时间历史(按时间倒序分页);卡按当前账号数据范围可见。
|
|
func (q *Query) CardHistory(ctx context.Context, req dto.CardRealnameHistoryListRequest) (*dto.CardRealnameHistoryResponse, error) {
|
|
var card model.IotCard
|
|
if err := middleware.ApplyShopFilter(ctx, q.db.WithContext(ctx).Select("id", "first_realname_at").Where("id = ?", req.ID)).
|
|
First(&card).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil, errors.New(errors.CodeNotFound, "IoT 卡不存在")
|
|
}
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询 IoT 卡失败")
|
|
}
|
|
page, size := normalizePage(req.Page, req.PageSize)
|
|
resp := &dto.CardRealnameHistoryResponse{Current: dto.CardRealnameCurrentResponse{CardID: card.ID}, Page: page, PageSize: size}
|
|
var record model.IotCardRealname
|
|
err := q.db.WithContext(ctx).Where("card_id = ?", card.ID).Take(&record).Error
|
|
switch {
|
|
case err == nil:
|
|
resp.Current = dto.CardRealnameCurrentResponse{
|
|
CardID: card.ID, RealnameAt: record.RealnameAt, RealnameAtSource: record.RealnameAtSource,
|
|
RealnameAtSourceName: constants.RealnameAtSourceName(record.RealnameAtSource),
|
|
UpstreamRealnameAt: record.UpstreamRealnameAt, FirstObservedAt: record.FirstObservedAt,
|
|
ResetPending: record.ResetStatus == constants.RealnameResetStatusPending, ResetAt: record.ResetAt,
|
|
ResetBaselineAt: record.ResetBaselineAt, ResetBatchID: record.ResetBatchID,
|
|
}
|
|
case err == gorm.ErrRecordNotFound:
|
|
resp.Current.RealnameAt = card.FirstRealnameAt
|
|
default:
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询卡当前实名记录失败")
|
|
}
|
|
base := q.db.WithContext(ctx).Model(&model.IotCardRealnameHistory{}).Where("card_id = ?", card.ID)
|
|
if err := base.Count(&resp.Total).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "统计卡实名时间历史失败")
|
|
}
|
|
var rows []model.IotCardRealnameHistory
|
|
if err := base.Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&rows).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询卡实名时间历史失败")
|
|
}
|
|
operators, err := q.operatorNames(ctx, rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp.Items = make([]dto.CardRealnameHistoryItem, 0, len(rows))
|
|
for _, row := range rows {
|
|
resp.Items = append(resp.Items, dto.CardRealnameHistoryItem{
|
|
ID: row.ID, CardID: row.CardID, ICCID: row.ICCID, BeforeAt: row.BeforeAt, AfterAt: row.AfterAt,
|
|
AfterSource: row.AfterSource, AfterSourceName: constants.RealnameAtSourceName(row.AfterSource),
|
|
ChangeType: row.ChangeType, ChangeTypeName: constants.RealnameChangeTypeName(row.ChangeType),
|
|
ObservationSource: row.ObservationSource, ObservationSourceName: observationSourceName(row.ObservationSource),
|
|
ResetBatchID: row.ResetBatchID, OperatorID: row.OperatorID, OperatorName: operators[row.OperatorID],
|
|
ObservedAt: row.ObservedAt, CreatedAt: row.CreatedAt,
|
|
})
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// ListResetBatches 分页查询二次实名批次(不含逐行结果)。
|
|
func (q *Query) ListResetBatches(ctx context.Context, req dto.RealnameResetBatchListRequest) ([]dto.RealnameResetBatchResponse, int64, int, int, error) {
|
|
if err := requirePlatform(ctx); err != nil {
|
|
return nil, 0, 0, 0, err
|
|
}
|
|
page, size := normalizePage(req.Page, req.PageSize)
|
|
base := q.db.WithContext(ctx).Model(&model.IotCardRealnameResetBatch{})
|
|
if req.BatchNo != "" {
|
|
base = base.Where("batch_no = ?", req.BatchNo)
|
|
}
|
|
var total int64
|
|
if err := base.Count(&total).Error; err != nil {
|
|
return nil, 0, 0, 0, errors.Wrap(errors.CodeDatabaseError, err, "统计二次实名批次失败")
|
|
}
|
|
var rows []model.IotCardRealnameResetBatch
|
|
if err := base.Omit("items").Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&rows).Error; err != nil {
|
|
return nil, 0, 0, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询二次实名批次失败")
|
|
}
|
|
pending, err := q.pendingCounts(ctx, batchIDs(rows))
|
|
if err != nil {
|
|
return nil, 0, 0, 0, err
|
|
}
|
|
list := make([]dto.RealnameResetBatchResponse, 0, len(rows))
|
|
for _, row := range rows {
|
|
item := batchResponse(row)
|
|
item.PendingCount = pending[row.ID]
|
|
list = append(list, item)
|
|
}
|
|
return list, total, page, size, nil
|
|
}
|
|
|
|
// GetResetBatch 查询二次实名批次详情,逐行结果按卡当前状态刷新为等待 / 已完成 / 已撤销。
|
|
func (q *Query) GetResetBatch(ctx context.Context, id uint) (*dto.RealnameResetBatchResponse, error) {
|
|
if err := requirePlatform(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
var row model.IotCardRealnameResetBatch
|
|
if err := q.db.WithContext(ctx).Where("id = ?", id).Take(&row).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil, errors.New(errors.CodeNotFound, "二次实名批次不存在")
|
|
}
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询二次实名批次失败")
|
|
}
|
|
var items []dto.RealnameResetItem
|
|
if len(row.Items) > 0 {
|
|
if err := sonic.Unmarshal(row.Items, &items); err != nil {
|
|
return nil, errors.Wrap(errors.CodeInternalError, err, "解析二次实名批次结果失败")
|
|
}
|
|
}
|
|
finals, err := q.finalResults(ctx, row.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pending := 0
|
|
for index := range items {
|
|
if items[index].Result == constants.RealnameResetItemPending {
|
|
if final, ok := finals[items[index].CardID]; ok {
|
|
items[index].Result = final
|
|
} else {
|
|
pending++
|
|
}
|
|
}
|
|
items[index].ResultName = constants.RealnameResetItemName(items[index].Result)
|
|
}
|
|
resp := batchResponse(row)
|
|
resp.PendingCount = pending
|
|
resp.Items = items
|
|
return &resp, nil
|
|
}
|
|
|
|
// finalResults 返回批次内已完成或已撤销的卡(以历史为准,同卡取最新一条)。
|
|
func (q *Query) finalResults(ctx context.Context, batchID uint) (map[uint]string, error) {
|
|
var rows []model.IotCardRealnameHistory
|
|
if err := q.db.WithContext(ctx).Select("card_id", "change_type").
|
|
Where("reset_batch_id = ? AND change_type IN ?", batchID,
|
|
[]string{constants.RealnameChangeResetCompleted, constants.RealnameChangeResetReverted}).
|
|
Order("id").Find(&rows).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询二次实名结果失败")
|
|
}
|
|
result := make(map[uint]string, len(rows))
|
|
for _, row := range rows {
|
|
if row.ChangeType == constants.RealnameChangeResetCompleted {
|
|
result[row.CardID] = constants.RealnameResetItemCompleted
|
|
} else {
|
|
result[row.CardID] = constants.RealnameResetItemReverted
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (q *Query) pendingCounts(ctx context.Context, ids []uint) (map[uint]int, error) {
|
|
result := make(map[uint]int, len(ids))
|
|
if len(ids) == 0 {
|
|
return result, nil
|
|
}
|
|
var rows []struct {
|
|
BatchID uint `gorm:"column:reset_batch_id"`
|
|
Count int `gorm:"column:count"`
|
|
}
|
|
if err := q.db.WithContext(ctx).Model(&model.IotCardRealname{}).Select("reset_batch_id, COUNT(*) AS count").
|
|
Where("reset_batch_id IN ? AND reset_status = ?", ids, constants.RealnameResetStatusPending).
|
|
Group("reset_batch_id").Scan(&rows).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "统计等待二次实名卡数失败")
|
|
}
|
|
for _, row := range rows {
|
|
result[row.BatchID] = row.Count
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (q *Query) operatorNames(ctx context.Context, rows []model.IotCardRealnameHistory) (map[uint]string, error) {
|
|
ids := make([]uint, 0)
|
|
for _, row := range rows {
|
|
if row.OperatorID > 0 {
|
|
ids = append(ids, row.OperatorID)
|
|
}
|
|
}
|
|
result := make(map[uint]string, len(ids))
|
|
if len(ids) == 0 {
|
|
return result, nil
|
|
}
|
|
var accounts []model.Account
|
|
if err := q.db.WithContext(ctx).Select("id", "username").Where("id IN ?", ids).Find(&accounts).Error; err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询操作人失败")
|
|
}
|
|
for _, account := range accounts {
|
|
result[account.ID] = account.Username
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func batchResponse(row model.IotCardRealnameResetBatch) dto.RealnameResetBatchResponse {
|
|
return dto.RealnameResetBatchResponse{
|
|
ID: row.ID, BatchNo: row.BatchNo, Reason: row.Reason, ImportFileKey: row.ImportFileKey,
|
|
TotalCount: row.TotalCount, SucceededCount: row.SucceededCount, SkippedCount: row.SkippedCount,
|
|
CompletedCount: row.CompletedCount, RevertedCount: row.RevertedCount,
|
|
OperatorID: row.OperatorID, OperatorName: row.OperatorName, CreatedAt: row.CreatedAt,
|
|
}
|
|
}
|
|
|
|
func batchIDs(rows []model.IotCardRealnameResetBatch) []uint {
|
|
ids := make([]uint, 0, len(rows))
|
|
for _, row := range rows {
|
|
ids = append(ids, row.ID)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func requirePlatform(ctx context.Context) error {
|
|
switch middleware.GetUserTypeFromContext(ctx) {
|
|
case constants.UserTypeSuperAdmin, constants.UserTypePlatform:
|
|
return nil
|
|
default:
|
|
return errors.New(errors.CodeForbidden, "仅平台账号可查看二次实名批次")
|
|
}
|
|
}
|
|
|
|
func normalizePage(page, size int) (int, int) {
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if size < 1 {
|
|
size = 20
|
|
}
|
|
if size > 100 {
|
|
size = 100
|
|
}
|
|
return page, size
|
|
}
|
|
|
|
func observationSourceName(source string) string {
|
|
switch source {
|
|
case constants.CardObservationSourcePolling:
|
|
return "轮询"
|
|
case constants.CardObservationSourceManualSync:
|
|
return "手动刷新"
|
|
case constants.CardObservationSourceManualOverride:
|
|
return "人工"
|
|
case constants.CardObservationSourceCarrierCallback:
|
|
return "运营商回调"
|
|
case constants.CardObservationSourceBusinessEvent:
|
|
return "业务观测"
|
|
case "migration":
|
|
return "上线迁移"
|
|
case "package_silence":
|
|
return "沉默期判定"
|
|
default:
|
|
return source
|
|
}
|
|
}
|