fix: 修复分页字段/角色DTO/套餐详情/批量分配Bug,新增C端测试登录接口和Hurl价格验证测试
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
- 修复B-2:27个分页DTO字段名统一(list→items, page_size→size,删除total_pages) - 修复B-1:角色接口统一返回DTO(id小写,消除GORM大写字段名) - 修复C-1:套餐详情接口(GET /packages/:id)补充代理佣金字段 - 修复C-2:批量分配已存在记录由500改为静默跳过 - 修复C-3:创建系列授权响应在事务提交后构建,packages数组不再为空 - 新增C端开发测试登录接口(POST /api/c/v1/auth/dev-login,仅logging.development=true时生效) - 新增Hurl测试:card-price-verification-flow.hurl(导入卡→分配→C端价格验证) - 新增测试Excel数据文件和dev.env测试变量
This commit is contained in:
@@ -101,17 +101,11 @@ func (s *Service) List(ctx context.Context, req *dto.ListAssetAllocationRecordRe
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.ListAssetAllocationRecordResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -108,17 +108,11 @@ func (s *Service) ListTransactions(ctx context.Context, assetType string, assetI
|
||||
})
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.AssetWalletTransactionListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -717,6 +717,54 @@ func (s *Service) issueLoginToken(ctx context.Context, customerID uint) (string,
|
||||
return token, needBindPhone, nil
|
||||
}
|
||||
|
||||
// DevLogin 开发环境测试登录
|
||||
// 根据资产标识符查找或创建测试客户并直接签发 JWT,无需微信 OAuth
|
||||
// ⚠️ 仅限 logging.development=true 时由路由层暴露,严禁生产环境调用
|
||||
func (s *Service) DevLogin(ctx context.Context, identifier string) (string, uint, bool, error) {
|
||||
assetType, assetID, err := s.resolveAsset(ctx, identifier)
|
||||
if err != nil {
|
||||
return "", 0, false, err
|
||||
}
|
||||
|
||||
var (
|
||||
customerID uint
|
||||
isNewUser bool
|
||||
)
|
||||
|
||||
// 在事务中查找或创建测试客户并绑定资产
|
||||
// 使用固定的开发测试 OpenID(dev_test_+identifier),避免重复创建
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
devOpenID := "dev_test_" + identifier
|
||||
devAppID := "dev_test_app"
|
||||
|
||||
cid, created, findErr := s.findOrCreateCustomer(ctx, tx, devAppID, devOpenID, "", "测试用户", "", "dev")
|
||||
if findErr != nil {
|
||||
return findErr
|
||||
}
|
||||
if bindErr := s.bindAsset(ctx, tx, cid, assetType, assetID); bindErr != nil {
|
||||
return bindErr
|
||||
}
|
||||
customerID = cid
|
||||
isNewUser = created
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", 0, false, err
|
||||
}
|
||||
|
||||
token, _, err := s.issueLoginToken(ctx, customerID)
|
||||
if err != nil {
|
||||
return "", 0, false, err
|
||||
}
|
||||
|
||||
s.logger.Info("开发环境测试登录成功",
|
||||
zap.Uint("customer_id", customerID),
|
||||
zap.String("identifier", identifier),
|
||||
)
|
||||
|
||||
return token, customerID, isNewUser, nil
|
||||
}
|
||||
|
||||
func (s *Service) checkSendCodeRateLimit(ctx context.Context, phone, clientIP string) error {
|
||||
phoneCooldownKey := constants.RedisClientSendCodePhoneLimitKey(phone)
|
||||
exists, err := s.redis.Exists(ctx, phoneCooldownKey).Result()
|
||||
|
||||
@@ -127,17 +127,11 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.Li
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.ListDeviceResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -110,17 +110,11 @@ func (s *Service) List(ctx context.Context, req *dto.ListDeviceImportTaskRequest
|
||||
list = append(list, s.toTaskResponse(task))
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.ListDeviceImportTaskResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -157,17 +157,11 @@ func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIot
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.ListStandaloneIotCardResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -143,17 +143,11 @@ func (s *Service) List(ctx context.Context, req *dto.ListImportTaskRequest) (*dt
|
||||
list = append(list, s.toTaskResponse(task))
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.ListImportTaskResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1234,17 +1234,11 @@ func (s *Service) List(ctx context.Context, req *dto.OrderListRequest, buyerType
|
||||
list = append(list, s.buildOrderResponse(o, itemsMap[o.ID]))
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.OrderListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -155,6 +155,28 @@ func (s *Service) Get(ctx context.Context, id uint) (*dto.PackageResponse, error
|
||||
series, err := s.packageSeriesStore.GetByID(ctx, pkg.SeriesID)
|
||||
if err == nil {
|
||||
resp.SeriesName = &series.SeriesName
|
||||
|
||||
// 代理用户:增强佣金信息(与 List 接口保持一致)
|
||||
userType := middleware.GetUserTypeFromContext(ctx)
|
||||
shopID := middleware.GetShopIDFromContext(ctx)
|
||||
if userType == constants.UserTypeAgent && shopID > 0 && series.EnableOneTimeCommission {
|
||||
config, _ := series.GetOneTimeCommissionConfig()
|
||||
if config != nil && config.Enable {
|
||||
allocations, saErr := s.shopSeriesAllocationStore.GetByShopID(ctx, shopID)
|
||||
if saErr == nil {
|
||||
seriesAllocationMap := make(map[uint]*model.ShopSeriesAllocation)
|
||||
seriesConfigMap := make(map[uint]*model.OneTimeCommissionConfig)
|
||||
for _, alloc := range allocations {
|
||||
if alloc.SeriesID == pkg.SeriesID {
|
||||
seriesAllocationMap[alloc.SeriesID] = alloc
|
||||
break
|
||||
}
|
||||
}
|
||||
seriesConfigMap[pkg.SeriesID] = config
|
||||
s.fillCommissionInfo(resp, pkg.SeriesID, seriesAllocationMap, seriesConfigMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
|
||||
@@ -258,17 +258,11 @@ func (s *Service) List(ctx context.Context, req *dto.RechargeListRequest, userID
|
||||
list = append(list, s.buildRechargeResponse(r))
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.RechargeListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
@@ -34,7 +35,7 @@ func New(roleStore *postgres.RoleStore, permissionStore *postgres.PermissionStor
|
||||
}
|
||||
|
||||
// Create 创建角色
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreateRoleRequest) (*model.Role, error) {
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreateRoleRequest) (*dto.RoleResponse, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
@@ -62,11 +63,11 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateRoleRequest) (*mode
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "创建角色失败")
|
||||
}
|
||||
|
||||
return role, nil
|
||||
return toResponse(role), nil
|
||||
}
|
||||
|
||||
// Get 获取角色
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*model.Role, error) {
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*dto.RoleResponse, error) {
|
||||
role, err := s.roleStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@@ -74,11 +75,11 @@ func (s *Service) Get(ctx context.Context, id uint) (*model.Role, error) {
|
||||
}
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "获取角色失败")
|
||||
}
|
||||
return role, nil
|
||||
return toResponse(role), nil
|
||||
}
|
||||
|
||||
// Update 更新角色
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateRoleRequest) (*model.Role, error) {
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateRoleRequest) (*dto.RoleResponse, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
@@ -120,7 +121,7 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateRoleReques
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "更新角色失败")
|
||||
}
|
||||
|
||||
return role, nil
|
||||
return toResponse(role), nil
|
||||
}
|
||||
|
||||
// Delete 软删除角色
|
||||
@@ -293,6 +294,21 @@ func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// toResponse 将 model.Role 转换为 dto.RoleResponse
|
||||
func toResponse(role *model.Role) *dto.RoleResponse {
|
||||
return &dto.RoleResponse{
|
||||
ID: role.ID,
|
||||
RoleName: role.RoleName,
|
||||
RoleDesc: role.RoleDesc,
|
||||
RoleType: role.RoleType,
|
||||
Status: role.Status,
|
||||
Creator: role.Creator,
|
||||
Updater: role.Updater,
|
||||
CreatedAt: role.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: role.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
func contains(availableForRoleTypes, roleTypeStr string) bool {
|
||||
types := strings.Split(availableForRoleTypes, ",")
|
||||
for _, t := range types {
|
||||
|
||||
@@ -82,8 +82,15 @@ func (s *Service) BatchAllocate(ctx context.Context, req *dto.BatchAllocatePacka
|
||||
}
|
||||
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
packageAllocations := make([]*model.ShopPackageAllocation, 0, len(packages))
|
||||
txPkgAllocStore := postgres.NewShopPackageAllocationStore(tx)
|
||||
|
||||
for _, pkg := range packages {
|
||||
// 已存在该套餐的分配记录则跳过,避免唯一约束冲突
|
||||
_, existErr := txPkgAllocStore.GetByShopAndPackageForSystem(ctx, req.ShopID, pkg.ID)
|
||||
if existErr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
costPrice := pkg.CostPrice
|
||||
if req.PriceAdjustment != nil {
|
||||
costPrice = s.calculateAdjustedPrice(pkg.CostPrice, req.PriceAdjustment)
|
||||
@@ -99,11 +106,9 @@ func (s *Service) BatchAllocate(ctx context.Context, req *dto.BatchAllocatePacka
|
||||
SeriesAllocationID: &seriesAllocation.ID,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
packageAllocations = append(packageAllocations, allocation)
|
||||
}
|
||||
|
||||
if err := tx.CreateInBatches(packageAllocations, 100).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "批量创建套餐分配失败")
|
||||
if err := tx.Create(allocation).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "创建套餐分配失败")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -212,6 +212,12 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesGrantRequ
|
||||
return nil, errors.New(errors.CodeInvalidParam, "该系列未启用一次性佣金,无法创建授权")
|
||||
}
|
||||
|
||||
// 1.5 校验目标店铺是否存在
|
||||
_, err = s.shopStore.GetByID(ctx, req.ShopID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.CodeNotFound, "目标店铺不存在")
|
||||
}
|
||||
|
||||
// 2. 检查重复授权
|
||||
exists, err := s.shopSeriesAllocationStore.ExistsByShopAndSeries(ctx, req.ShopID, req.SeriesID)
|
||||
if err != nil {
|
||||
@@ -300,7 +306,6 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesGrantRequ
|
||||
}
|
||||
|
||||
// 6. 事务中创建 ShopSeriesAllocation + N 条 ShopPackageAllocation
|
||||
var result *dto.ShopSeriesGrantResponse
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
txSeriesStore := postgres.NewShopSeriesAllocationStore(tx)
|
||||
if createErr := txSeriesStore.Create(ctx, allocation); createErr != nil {
|
||||
@@ -353,15 +358,14 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesGrantRequ
|
||||
}
|
||||
}
|
||||
|
||||
var buildErr error
|
||||
result, buildErr = s.buildGrantResponse(ctx, allocation, series, config)
|
||||
return buildErr
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
// 事务提交后构建完整响应(此时 packages 已可查询到)
|
||||
return s.buildGrantResponse(ctx, allocation, series, config)
|
||||
}
|
||||
|
||||
// Get 查询单条系列授权详情
|
||||
@@ -494,17 +498,11 @@ func (s *Service) List(ctx context.Context, req *dto.ShopSeriesGrantListRequest)
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize != 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &dto.ShopSeriesGrantPageResult{
|
||||
List: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPages: totalPages,
|
||||
List: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user