diff --git a/apis/admin/lottery.api b/apis/admin/lottery.api index 6d66871..cd2d19f 100644 --- a/apis/admin/lottery.api +++ b/apis/admin/lottery.api @@ -43,6 +43,10 @@ service ppanel { @handler UpdateLotteryRules put /activities/rules (UpdateAdminLotteryRulesRequest) + @doc "Delete activity (soft-delete; running must be paused first)" + @handler DeleteLotteryActivity + delete /activities/:id (AdminActivityIdRequest) + @doc "Create prize" @handler CreateLotteryPrize post /prizes (CreateAdminLotteryPrizeRequest) returns (AdminLotteryPrize) diff --git a/internal/handler/admin/lottery/lottery_handler.go b/internal/handler/admin/lottery/lottery_handler.go index 15d911c..4b0e468 100644 --- a/internal/handler/admin/lottery/lottery_handler.go +++ b/internal/handler/admin/lottery/lottery_handler.go @@ -175,3 +175,20 @@ func GrantLotteryChanceHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) result.HttpResult(c, nil, l.GrantLotteryChance(&req)) } } + +// DeleteLotteryActivityHandler DELETE /v1/admin/lottery/activities/:id +func DeleteLotteryActivityHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.AdminActivityIdRequest + _ = c.ShouldBind(&req) + if id, err := strconv.ParseInt(c.Param("id"), 10, 64); err == nil { + req.Id = id + } + if err := svcCtx.Validate(&req); err != nil { + result.ParamErrorResult(c, err) + return + } + l := adminlottery.NewDeleteLotteryActivityLogic(c.Request.Context(), svcCtx) + result.HttpResult(c, nil, l.DeleteLotteryActivity(&req)) + } +} diff --git a/internal/handler/lottery_routes.go b/internal/handler/lottery_routes.go index c88d0cd..267b2f1 100644 --- a/internal/handler/lottery_routes.go +++ b/internal/handler/lottery_routes.go @@ -33,6 +33,7 @@ func registerLotteryRoutes(router *gin.Engine, serverCtx *svc.ServiceContext) { adminGroup.POST("/activities/publish", adminLottery.PublishLotteryActivityHandler(serverCtx)) adminGroup.POST("/activities/pause", adminLottery.PauseLotteryActivityHandler(serverCtx)) adminGroup.PUT("/activities/rules", adminLottery.UpdateLotteryRulesHandler(serverCtx)) + adminGroup.DELETE("/activities/:id", adminLottery.DeleteLotteryActivityHandler(serverCtx)) adminGroup.POST("/prizes", adminLottery.CreateLotteryPrizeHandler(serverCtx)) adminGroup.PUT("/prizes/:id", adminLottery.UpdateLotteryPrizeHandler(serverCtx)) diff --git a/internal/logic/admin/lottery/lottery.go b/internal/logic/admin/lottery/lottery.go index 7c5ba90..db24309 100644 --- a/internal/logic/admin/lottery/lottery.go +++ b/internal/logic/admin/lottery/lottery.go @@ -318,6 +318,55 @@ func (l *PauseLotteryActivityLogic) PauseLotteryActivity(req *types.AdminActivit return t.run(req.Id) } +// ---- DeleteLotteryActivity ------------------------------------------------- + +type DeleteLotteryActivityLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDeleteLotteryActivityLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteLotteryActivityLogic { + return &DeleteLotteryActivityLogic{Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx} +} + +// DeleteLotteryActivity 软删活动 + 硬删其奖品(同事务)。运行中的活动禁止删除, +// 需先暂停,避免误删正在进行的抽奖。历史抽奖记录/快照保留(独立于奖品行)。 +func (l *DeleteLotteryActivityLogic) DeleteLotteryActivity(req *types.AdminActivityIdRequest) error { + actor := currentAdminId(l.ctx) + if actor == 0 { + return xerr.NewErrCode(xerr.ErrorTokenInvalid) + } + return l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error { + var a modelLottery.Activity + if err := tx.Where("id = ?", req.Id).First(&a).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return xerr.NewErrCode(xerr.LotteryActivityEnded) + } + return errors.Wrap(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error()) + } + if a.Status == modelLottery.ActivityStatusRunning { + return xerr.NewErrCodeMsg(xerr.InvalidParams, "运行中的活动请先暂停再删除") + } + // 软删活动(Activity 有 gorm.DeletedAt)。 + if err := tx.Delete(&modelLottery.Activity{}, req.Id).Error; err != nil { + return errors.Wrap(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error()) + } + // 硬删奖品(Prize 无软删字段),避免残留孤儿奖品。 + if err := tx.Where("activity_id = ?", req.Id).Delete(&modelLottery.Prize{}).Error; err != nil { + return errors.Wrap(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error()) + } + ip, ua := requestMeta(l.ctx) + return audit.WriteAdminAction(l.ctx, tx, audit.Entry{ + ActorUserId: actor, + Action: audit.ActionLotteryActivityDelete, + TargetIds: int64ToStr(req.Id), + IP: ip, + UserAgent: ua, + }) + }) +} + // ---- UpdateLotteryRules (with caps) ---------------------------------------- type UpdateLotteryRulesLogic struct {