feat(#4): 删除抽奖活动接口 DELETE /admin/lottery/activities/:id

- 软删活动 + 硬删其奖品(同事务)+ 审计日志
- 运行中的活动禁止删除(需先暂停)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 00:16:34 -07:00
parent e8e3a3a72b
commit 13bafd5847
4 changed files with 71 additions and 0 deletions
+4
View File
@@ -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)
@@ -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))
}
}
+1
View File
@@ -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))
+49
View File
@@ -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 {