From 7b2d94e22df1312ad2ee313a407e16f114d364ac Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 14 Jul 2026 13:46:38 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BA=BF=E4=B8=8B=E6=8F=90?= =?UTF-8?q?=E5=8F=B7=E5=90=8E=E5=95=86=E5=93=81=E4=BB=8D=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=9C=A8=E3=80=8C=E5=B7=B2=E4=B8=8A=E6=9E=B6=E3=80=8D=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 创建提号时将 listing/account 置为 rented 并记录状态事件,与正常订单锁定一致;完成提号对齐 completed/offline;取消提号恢复上架。补充 migration 修复历史数据。 --- backend/internal/modules/pickup/repository.go | 62 ++++++++++++++++--- .../000027_pickup_lock_listing_status.sql | 46 ++++++++++++++ 2 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 backend/migrations/000027_pickup_lock_listing_status.sql diff --git a/backend/internal/modules/pickup/repository.go b/backend/internal/modules/pickup/repository.go index 940508b..a43def0 100644 --- a/backend/internal/modules/pickup/repository.go +++ b/backend/internal/modules/pickup/repository.go @@ -33,7 +33,8 @@ func NewRepository(db *gorm.DB) *Repository { } // Create 创建提号订单(状态:提号中)。 -// 锁定 listing.in_transaction,与正常订单互斥;不结算、不动钱包。 +// 与正常订单支付成功一致:listing 置 rented + in_transaction,从商品管理「已上架」中移出。 +// 不结算、不动钱包。 func (r *Repository) Create(ctx context.Context, req CreateRequest, adminID uint64, meta auditlog.Meta) (*PickupDTO, error) { if r == nil || r.db == nil { return nil, ErrDependencyUnavailable @@ -63,7 +64,7 @@ func (r *Repository) Create(ctx context.Context, req CreateRequest, adminID uint } var account model.GameAccount - if err := tx.First(&account, listing.AccountID).Error; err != nil { + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, listing.AccountID).Error; err != nil { return err } priceSnapshot := buildPickupPriceSnapshot(listing, account) @@ -98,10 +99,28 @@ func (r *Repository) Create(ctx context.Context, req CreateRequest, adminID uint } createdID = pickup.ID + // 锁定商品:status=rented,从「已上架」列表移除,进入「已锁定」 + fromStatus := listing.Status listing.InTransaction = true + listing.Status = "rented" + account.Status = "rented" if err := tx.Save(&listing).Error; err != nil { return err } + if err := tx.Save(&account).Error; err != nil { + return err + } + if err := listingstatus.AppendTransition( + tx, + &listing, + fromStatus, + listingstatus.SourcePickup, + listingstatus.ActorAdmin, + adminID, + "管理员线下提号锁定", + ); err != nil { + return err + } bid := pickup.ID if err := notification.Append(tx, notification.Entry{ @@ -164,7 +183,9 @@ func (r *Repository) Complete(ctx context.Context, pickupID uint64, req Complete return err } var account model.GameAccount - _ = tx.First(&account, pickup.AccountID).Error + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, pickup.AccountID).Error; err != nil { + return err + } now := time.Now() pickup.SettleAmountCent = req.SettleAmountCent @@ -178,20 +199,25 @@ func (r *Repository) Complete(ctx context.Context, pickupID uint64, req Complete return err } - // listing 置 completed:listingLockedForOwnerMutation 已拦截此状态再上架。 + // listing 置 completed(售出终态,不可再上架),与正常订单 completeAssets 对齐。 fromStatus := listing.Status - listing.InTransaction = true + listing.InTransaction = false listing.Status = "completed" + listing.PublishedAt = nil + account.Status = "offline" if err := tx.Save(&listing).Error; err != nil { return err } + if err := tx.Save(&account).Error; err != nil { + return err + } if err := listingstatus.AppendTransition( tx, &listing, fromStatus, listingstatus.SourcePickup, listingstatus.ActorAdmin, - 0, + adminID, "管理员提号完成", ); err != nil { return err @@ -244,7 +270,7 @@ func (r *Repository) Complete(ctx context.Context, pickupID uint64, req Complete return r.FindByID(ctx, pickupID) } -// Cancel 取消提号:解锁 listing.in_transaction,账号恢复可租。 +// Cancel 取消提号:恢复 listing 为 published + 解锁 in_transaction,账号恢复可租。 func (r *Repository) Cancel(ctx context.Context, pickupID uint64, reason string, adminID uint64, meta auditlog.Meta) error { if r == nil || r.db == nil { return ErrDependencyUnavailable @@ -264,16 +290,38 @@ func (r *Repository) Cancel(ctx context.Context, pickupID uint64, reason string, if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, pickup.ListingID).Error; err != nil { return err } + var account model.GameAccount + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, pickup.AccountID).Error; err != nil { + return err + } now := time.Now() pickup.Status = StatusCancelled pickup.CancelledAt = &now if err := tx.Save(&pickup).Error; err != nil { return err } + // 取消提号:恢复上架,重新出现在商品管理「已上架」 + fromStatus := listing.Status listing.InTransaction = false + listing.Status = "published" + account.Status = "published" if err := tx.Save(&listing).Error; err != nil { return err } + if err := tx.Save(&account).Error; err != nil { + return err + } + if err := listingstatus.AppendTransition( + tx, + &listing, + fromStatus, + listingstatus.SourcePickup, + listingstatus.ActorAdmin, + adminID, + "取消提号恢复上架", + ); err != nil { + return err + } bid := pickup.ID _ = notification.Append(tx, notification.Entry{ UserID: pickup.OwnerID, diff --git a/backend/migrations/000027_pickup_lock_listing_status.sql b/backend/migrations/000027_pickup_lock_listing_status.sql new file mode 100644 index 0000000..359c038 --- /dev/null +++ b/backend/migrations/000027_pickup_lock_listing_status.sql @@ -0,0 +1,46 @@ +-- +goose Up +-- +goose StatementBegin + +-- 进行中的线下提号:listing 应从「已上架」移出,与正常订单锁定一致(status=rented) +UPDATE rental_listings l +JOIN admin_pickups p ON p.listing_id = l.id AND p.status = 'picking_up' +SET l.status = 'rented', + l.in_transaction = 1 +WHERE l.status = 'published'; + +UPDATE game_accounts a +JOIN admin_pickups p ON p.account_id = a.id AND p.status = 'picking_up' +SET a.status = 'rented' +WHERE a.status IN ('published', 'draft', ''); + +-- 已完成的线下提号:listing 应为 completed,账号下线(兜底历史数据) +UPDATE rental_listings l +JOIN admin_pickups p ON p.listing_id = l.id AND p.status = 'completed' +SET l.status = 'completed', + l.in_transaction = 0, + l.published_at = NULL +WHERE l.status IN ('published', 'rented'); + +UPDATE game_accounts a +JOIN admin_pickups p ON p.account_id = a.id AND p.status = 'completed' +SET a.status = 'offline' +WHERE a.status NOT IN ('offline', 'sealed', 'abnormal'); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +-- 回滚仅恢复「进行中提号」的锁定(completed 终态不回滚,避免误恢复可租) +UPDATE rental_listings l +JOIN admin_pickups p ON p.listing_id = l.id AND p.status = 'picking_up' +SET l.status = 'published', + l.in_transaction = 1 +WHERE l.status = 'rented'; + +UPDATE game_accounts a +JOIN admin_pickups p ON p.account_id = a.id AND p.status = 'picking_up' +SET a.status = 'published' +WHERE a.status = 'rented'; + +-- +goose StatementEnd