修复 UR#55 代码审查问题:事务隔离、错误包装、重复辅助函数

- resolvePackageTerms 接受 tx 参数并使用事务绑定的 Store,确保快照与写入同库
- auto_purchase.go 错误包装改用 pkgerrors.Wrap 而非标准库 errors
- shop_package_batch_allocation 去除 fmt 依赖改用 strconv 拼接
- shop_series_grant 提取 effectiveBase 局部变量避免重复调用
- 测试辅助函数统一迁移至 testutil.StringPointer,删除各文件本地重复定义
- 新增 testutil.NewRedisClient 和 testutil.StringPointer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 11:09:08 +09:00
parent f7e0f07692
commit 147f3eb775
7 changed files with 29 additions and 22 deletions

View File

@@ -2223,7 +2223,7 @@ func (s *Service) markOrderCreated(ctx context.Context, idempotencyKey string, o
// activateMainPackage 任务 8.2-8.4: 主套餐激活逻辑
func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *model.Order, pkg *model.Package, carrierType string, carrierID uint, now time.Time) error {
terms, err := s.resolvePackageTerms(ctx, pkg, order.SellerShopID)
terms, err := s.resolvePackageTerms(ctx, tx, pkg, order.SellerShopID)
if err != nil {
s.logger.Error("生成套餐计时快照失败", zap.Uint("package_id", pkg.ID), zap.Error(err))
return err
@@ -2366,7 +2366,7 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
// activateAddonPackage 任务 8.5-8.7: 加油包激活逻辑
func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *model.Order, pkg *model.Package, carrierType string, carrierID uint, now time.Time) error {
terms, err := s.resolvePackageTerms(ctx, pkg, order.SellerShopID)
terms, err := s.resolvePackageTerms(ctx, tx, pkg, order.SellerShopID)
if err != nil {
s.logger.Error("生成加油包计时快照失败", zap.Uint("package_id", pkg.ID), zap.Error(err))
return err
@@ -2446,10 +2446,13 @@ func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *
return nil
}
func (s *Service) resolvePackageTerms(ctx context.Context, pkg *model.Package, sellerShopID *uint) (packagedomain.TermsSnapshot, error) {
// resolvePackageTerms 在给定事务内查询分配覆盖并解析计时条款快照。
// 必须在事务内调用,确保分配读取与 PackageUsage 写入在同一连接,避免快照与提交值不一致。
func (s *Service) resolvePackageTerms(ctx context.Context, tx *gorm.DB, pkg *model.Package, sellerShopID *uint) (packagedomain.TermsSnapshot, error) {
var allocation *model.ShopPackageAllocation
if sellerShopID != nil && *sellerShopID > 0 {
found, err := s.shopPackageAllocationStore.GetByShopAndPackageForSystem(ctx, *sellerShopID, pkg.ID)
store := postgres.NewShopPackageAllocationStore(tx)
found, err := store.GetByShopAndPackageForSystem(ctx, *sellerShopID, pkg.ID)
if err != nil && err != gorm.ErrRecordNotFound {
return packagedomain.TermsSnapshot{}, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐分配失败")
}

View File

@@ -2,7 +2,7 @@ package shop_package_batch_allocation
import (
"context"
"fmt"
"strconv"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
@@ -94,7 +94,7 @@ func (s *Service) logExpiryBaseAudit(ctx context.Context, allocation *model.Shop
s.auditService.LogOperation(ctx, &model.AccountOperationLog{
OperatorID: middleware.GetUserIDFromContext(ctx), OperatorType: middleware.GetUserTypeFromContext(ctx),
OperatorName: middleware.GetUsernameFromContext(ctx), OperationType: "update_package_expiry_base",
OperationDesc: fmt.Sprintf("修改套餐分配生效条件覆盖: %d", allocation.ID),
OperationDesc: "修改套餐分配生效条件覆盖: " + strconv.FormatUint(uint64(allocation.ID), 10),
BeforeData: model.JSONB{"allocation_id": allocation.ID, "expiry_base_override": before},
AfterData: model.JSONB{"allocation_id": allocation.ID, "expiry_base_override": after},
RequestID: middleware.GetRequestIDFromContext(ctx), IPAddress: middleware.GetIPFromContext(ctx), UserAgent: middleware.GetUserAgentFromContext(ctx),

View File

@@ -185,6 +185,7 @@ func (s *Service) buildGrantResponse(ctx context.Context, allocation *model.Shop
if pkg.IsGift {
continue
}
effectiveBase := packagepkg.EffectiveExpiryBase(pkg, pa)
packages = append(packages, dto.ShopSeriesGrantPackageItem{
PackageID: pa.PackageID,
PackageName: pkg.PackageName,
@@ -196,8 +197,8 @@ func (s *Service) buildGrantResponse(ctx context.Context, allocation *model.Shop
DefaultExpiryBaseName: packagepkg.ExpiryBaseName(pkg.ExpiryBase),
ExpiryBaseOverride: pa.ExpiryBaseOverride,
ExpiryBaseOverrideName: packagepkg.ExpiryBaseOverrideName(pa.ExpiryBaseOverride),
EffectiveExpiryBase: packagepkg.EffectiveExpiryBase(pkg, pa),
EffectiveExpiryBaseName: packagepkg.ExpiryBaseName(packagepkg.EffectiveExpiryBase(pkg, pa)),
EffectiveExpiryBase: effectiveBase,
EffectiveExpiryBaseName: packagepkg.ExpiryBaseName(effectiveBase),
})
}
resp.Packages = packages