package campaignuser import ( "joybuywork/internal/app" "joybuywork/internal/consts" "joybuywork/internal/consts/ckey" "joybuywork/internal/plugins/jsonm" "joybuywork/internal/repository/cache" "joybuywork/internal/repository/finder" "joybuywork/internal/repository/model" "joybuywork/internal/repository/mtype" "joybuywork/internal/repository/withdefault" "joybuywork/internal/service/provider/fissionaction" "joybuywork/internal/service/provider/malls" "joybuywork/internal/service/provider/players" "joybuywork/internal/service/provider/projectuser" "joybuywork/internal/service/provider/stocks" "joybuywork/internal/service/queue/producer" "joybuywork/internal/service/queue/types" "joybuywork/internal/utils/logic" "joybuywork/internal/utils/verify" pb "joybuywork/proto/go_proto/w_campaign_user" "joybuywork/proto/go_proto/workerr" "time" ) const ( exchangeStatusOk = iota // 成功 exchangeStatusLack // 库存不足 exchangeStatusLimit // 兑换上限 exchangeStatusDown // 商品已下架 exchangeStatusPointLack // 积分不足 exchangeStatusExchanging // 兑换中 exchangeStatusFail // 兑换失败 ) type Exchange struct { logic.Logic Req *pb.ExchangeReq Res *pb.ExchangeRes } func (s *Exchange) Run() (err error) { return logic.Run(s) } func (s *Exchange) Data() (data jsonm.JsonM) { data, _ = jsonm.Marshal(s.Req) return } func (s *Exchange) Exec() (err error) { cpn, user, err := s.user() if err != nil { return } pu, item, err := s.pu(cpn, user) if err != nil || item == nil { return } err = s.checkItem(item) if err != nil || s.Res.GetStatus() != exchangeStatusOk { return } exchanging := app.NewRedisLock(s.Ctx, ckey.MallItemExchanging(pu.GetProjectUser().ID)) if !exchanging.SoftLock() { s.Res.Status = exchangeStatusExchanging return } defer exchanging.Release() return s.exchange(pu, item, user, cpn) } func (s *Exchange) user() (cpn *model.FissionCampaign, user *model.FissionUser, err error) { cpn, err = finder.Campaign(s.Ctx, s.Req.GetCampaignId()) if err != nil { err = s.BE().CacheError(err) return } if cpn == nil || cpn.IsDel == mtype.Yes { err = workerr.NotFound("not found cpn") return } user, err = finder.FissionUser(s.Ctx, cpn.ID, s.Req.GetUserId()) if err != nil { err = s.BE().CacheError(err) return } if user == nil { err = workerr.UnauthorizedError return } return } func (s *Exchange) pu(cpn *model.FissionCampaign, user *model.FissionUser) (pu *projectuser.ProjectUser, item *model.RedeemMallItem, err error) { pu, err = projectuser.NewProjectUser(s.Ctx, projectuser.WithInit(cpn.ProjectID, user.SubUserID), projectuser.WithDao(s.GetDao())) if err != nil { return } item, err = cache.NewRedeemMallItem(s.Ctx).FindById(s.Req.GetMallItemId()) if err != nil { err = s.BE().CacheError(err) return } if item == nil { s.Res.Status = exchangeStatusDown return } return } func (s *Exchange) checkItem(item *model.RedeemMallItem) (err error) { if verify.InArray[int32](item.GetStatus(), mtype.RedeemMallItemStatusDel, mtype.RedeemMallItemStatusDown) { s.Res.Status = exchangeStatusDown return } stock, err := stocks.NewMallItem(s.Ctx, item.ID) if err != nil { return } ok, err := stock.DecrPoolAndAdd(item.ID, 1) if err != nil { return } if !ok { s.Res.Status = exchangeStatusLack return } s.AddErrRun(func() (err error) { _, _ = stock.DecrPoolAndAdd(item.ID, -1) return }) s.AddErrRun(func() (err error) { if s.Res.Status != exchangeStatusOk { _, _ = stock.DecrPoolAndAdd(item.ID, -1) } return }) return } func (s *Exchange) exchange(pu *projectuser.ProjectUser, item *model.RedeemMallItem, user *model.FissionUser, cpn *model.FissionCampaign) (err error) { coin, err := pu.GetCoin() if err != nil { return } if coin.Num < int64(item.CoinNum) { s.Res.Status = exchangeStatusPointLack return } ok, err := pu.Deduct(mtype.ProjectUserCoinUseSourceCampaign, mtype.ProjectUserCoinDerMall, user.CampaignID, item.CoinNum) if err != nil { return } if !ok { s.Rollback("项目用户积分币不足") s.Res.Status = exchangeStatusPointLack return } mall, err := malls.NewMall(s.Ctx, item.MallID) if err != nil { return } reward, mallRecord, info, err := mall.ExchangeRecord(item, user.LangMark) if err != nil { return } record := withdefault.FissionRewardRecord(&model.FissionRewardRecord{ CampaignID: user.CampaignID, UserID: user.ID, RewardID: reward.ID, Title: info.Title, ImgURL: info.ImgUrl, RewardType: mtype.FissionCampaignRewardTypeExchange, RewardNum: 1, BaseRewardID: reward.ID, PropMark: mtype.FissionCampaignRewardPropMarkReward, MallRecordID: mallRecord.ID, ExchangePoints: int32(mallRecord.CoinNum), }) err = s.GetDao().FissionRewardRecord.WithContext(s.Ctx).Create(record) if err != nil { err = s.BE().MysqlError(err) return } err = s.GetDao().FissionRewardRecord.SubTable(user.CampaignID).WithContext(s.Ctx).Create(record) if err != nil { err = s.BE().MysqlError(err) return } err = s.GetDao().FissionClientReward.WithContext(s.Ctx).Create(withdefault.FissionClientReward(&model.FissionClientReward{ UserID: user.SubUserID, ProjectID: pu.GetProjectUser().ProjectID, CampaignID: user.CampaignID, RecordID: record.ID, CreatedAt: record.CreatedAt, })) if err != nil { err = s.BE().MysqlError(err) return } err = s._fissionCallback(cpn, user, item) if err != nil { return } s.AddAfterRun(func() (err error) { bp := fissionaction.NewMarkPoint(s.Ctx).InApp(user) if item.CoinNum > 0 { bp.PointsUse(time.Now().Unix(), item.CoinNum) } bp.MallRedeem(time.Now().Unix(), item.MallID, item.ID, item.CoinNum) producer.NewCampaignNotice(s.Ctx).CampaignRewardNotify(types.CampaignRewardNotify{ CampaignId: user.CampaignID, UserId: user.ID, UserRewardId: record.ID, }) return }) s.Res.Status = exchangeStatusOk s.Res.Id = record.ID s.Res.RewardId = info.Id s.Res.Title = info.Title s.Res.ImgUrl = info.ImgUrl s.Res.ReceiveType = info.ReceiveType s.Res.Contact = info.Contact s.Res.OpenToUse = info.OpenToUse s.Res.CustomRewardVal = record.CustomRewardVal s.Res.WebUrl = info.WebUrl s.Res.AppParam = info.AppParam s.Res.PcParam = info.PcParam s.Res.FailType = record.FailType s.Res.FailMsg = record.FailMsg s.Res.KvData = make([]*pb.ExchangeRes_KV, 0, len(info.KvData)) for _, datum := range info.KvData { s.Res.KvData = append(s.Res.KvData, &pb.ExchangeRes_KV{ Key: datum.Key, Value: datum.Value, IsShow: datum.IsShow, }) } return } func (s *Exchange) _fissionCallback(cpn *model.FissionCampaign, user *model.FissionUser, item *model.RedeemMallItem) (err error) { switch cpn.FissionID { case consts.IdVs: player, err1 := players.NewVs(s.Ctx, user, players.SetCpn(cpn), players.SetDao(s.GetDao())) if err1 != nil { return err1 } err = player.Exchange(item) if err != nil { return } } return }