feat: 实现运营商模块重构,添加冗余字段优化查询性能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m16s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m16s
主要变更: - 新增 Carrier CRUD API(创建、列表、详情、更新、删除、状态更新) - IotCard/IotCardImportTask 添加 carrier_type/carrier_name 冗余字段 - 移除 Carrier 表的 channel_name/channel_code 字段 - 查询时直接使用冗余字段,避免 JOIN Carrier 表 - 添加数据库迁移脚本(000021-000023) - 添加单元测试和集成测试 - 同步更新 OpenAPI 文档和 specs
This commit is contained in:
83
internal/store/postgres/carrier_store.go
Normal file
83
internal/store/postgres/carrier_store.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
)
|
||||
|
||||
type CarrierStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCarrierStore(db *gorm.DB) *CarrierStore {
|
||||
return &CarrierStore{db: db}
|
||||
}
|
||||
|
||||
func (s *CarrierStore) Create(ctx context.Context, carrier *model.Carrier) error {
|
||||
return s.db.WithContext(ctx).Create(carrier).Error
|
||||
}
|
||||
|
||||
func (s *CarrierStore) GetByID(ctx context.Context, id uint) (*model.Carrier, error) {
|
||||
var carrier model.Carrier
|
||||
if err := s.db.WithContext(ctx).First(&carrier, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &carrier, nil
|
||||
}
|
||||
|
||||
func (s *CarrierStore) GetByCode(ctx context.Context, code string) (*model.Carrier, error) {
|
||||
var carrier model.Carrier
|
||||
if err := s.db.WithContext(ctx).Where("carrier_code = ?", code).First(&carrier).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &carrier, nil
|
||||
}
|
||||
|
||||
func (s *CarrierStore) Update(ctx context.Context, carrier *model.Carrier) error {
|
||||
return s.db.WithContext(ctx).Save(carrier).Error
|
||||
}
|
||||
|
||||
func (s *CarrierStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Carrier{}, id).Error
|
||||
}
|
||||
|
||||
func (s *CarrierStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Carrier, int64, error) {
|
||||
var carriers []*model.Carrier
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Carrier{})
|
||||
|
||||
if carrierType, ok := filters["carrier_type"].(string); ok && carrierType != "" {
|
||||
query = query.Where("carrier_type = ?", carrierType)
|
||||
}
|
||||
if carrierName, ok := filters["carrier_name"].(string); ok && carrierName != "" {
|
||||
query = query.Where("carrier_name LIKE ?", "%"+carrierName+"%")
|
||||
}
|
||||
if status, ok := filters["status"]; ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
}
|
||||
|
||||
if err := query.Find(&carriers).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return carriers, total, nil
|
||||
}
|
||||
204
internal/store/postgres/carrier_store_test.go
Normal file
204
internal/store/postgres/carrier_store_test.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCarrierStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "CMCC_TEST_001",
|
||||
CarrierName: "中国移动测试",
|
||||
CarrierType: constants.CarrierTypeCMCC,
|
||||
Description: "测试运营商",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, carrier)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, carrier.ID)
|
||||
}
|
||||
|
||||
func TestCarrierStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "CUCC_TEST_001",
|
||||
CarrierName: "中国联通测试",
|
||||
CarrierType: constants.CarrierTypeCUCC,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, carrier))
|
||||
|
||||
t.Run("查询存在的运营商", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, carrier.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, carrier.CarrierCode, result.CarrierCode)
|
||||
assert.Equal(t, carrier.CarrierName, result.CarrierName)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的运营商", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCarrierStore_GetByCode(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "CTCC_TEST_001",
|
||||
CarrierName: "中国电信测试",
|
||||
CarrierType: constants.CarrierTypeCTCC,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, carrier))
|
||||
|
||||
t.Run("查询存在的编码", func(t *testing.T) {
|
||||
result, err := s.GetByCode(ctx, "CTCC_TEST_001")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, carrier.ID, result.ID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的编码", func(t *testing.T) {
|
||||
_, err := s.GetByCode(ctx, "NOT_EXISTS")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCarrierStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "CBN_TEST_001",
|
||||
CarrierName: "中国广电测试",
|
||||
CarrierType: constants.CarrierTypeCBN,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, carrier))
|
||||
|
||||
carrier.CarrierName = "中国广电测试-更新"
|
||||
carrier.Description = "更新后的描述"
|
||||
err := s.Update(ctx, carrier)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, carrier.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "中国广电测试-更新", updated.CarrierName)
|
||||
assert.Equal(t, "更新后的描述", updated.Description)
|
||||
}
|
||||
|
||||
func TestCarrierStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierCode: "DEL_TEST_001",
|
||||
CarrierName: "待删除运营商",
|
||||
CarrierType: constants.CarrierTypeCMCC,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, carrier))
|
||||
|
||||
err := s.Delete(ctx, carrier.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetByID(ctx, carrier.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCarrierStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewCarrierStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
carriers := []*model.Carrier{
|
||||
{CarrierCode: "LIST_001", CarrierName: "移动1", CarrierType: constants.CarrierTypeCMCC, Status: constants.StatusEnabled},
|
||||
{CarrierCode: "LIST_002", CarrierName: "联通1", CarrierType: constants.CarrierTypeCUCC, Status: constants.StatusEnabled},
|
||||
{CarrierCode: "LIST_003", CarrierName: "电信1", CarrierType: constants.CarrierTypeCTCC, Status: constants.StatusEnabled},
|
||||
}
|
||||
for _, c := range carriers {
|
||||
require.NoError(t, s.Create(ctx, c))
|
||||
}
|
||||
// 显式更新第三个 carrier 为禁用状态(GORM 不会写入零值)
|
||||
carriers[2].Status = constants.StatusDisabled
|
||||
require.NoError(t, s.Update(ctx, carriers[2]))
|
||||
|
||||
t.Run("查询所有运营商", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按类型过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"carrier_type": constants.CarrierTypeCMCC}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, c := range result {
|
||||
assert.Equal(t, constants.CarrierTypeCMCC, c.CarrierType)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按名称模糊搜索", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"carrier_name": "联通"}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, c := range result {
|
||||
assert.Contains(t, c.CarrierName, "联通")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤-禁用", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusDisabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, c := range result {
|
||||
assert.Equal(t, constants.StatusDisabled, c.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤-启用", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusEnabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, c := range result {
|
||||
assert.Equal(t, constants.StatusEnabled, c.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
|
||||
t.Run("默认分页选项", func(t *testing.T) {
|
||||
result, _, err := s.List(ctx, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user