package mall import ( "joybuywork/internal/app" "joybuywork/internal/consts/ckey" "joybuywork/internal/plugins/hashid" "joybuywork/internal/plugins/jsonm" "joybuywork/internal/repository/cache" "joybuywork/internal/repository/dbcheck" "joybuywork/internal/repository/model" "joybuywork/internal/repository/mtype" "joybuywork/internal/service/provider" "joybuywork/internal/service/provider/malls" "joybuywork/internal/service/provider/projectuser" stos "joybuywork/internal/service/provider/s2s" "joybuywork/internal/service/provider/stocks" "joybuywork/internal/utils/logic" "joybuywork/internal/utils/verify" pb "joybuywork/proto/go_proto/w_mall" "time" "gorm.io/gen" ) const ( exchangeStatusOk = iota // 成功 exchangeStatusLack // 库存不足 exchangeStatusLimit // 兑换上限 exchangeStatusDown // 商品已下架 exchangeStatusPointLack // 积分不足 exchangeStatusExchanging // 兑换中 exchangeStatusFail // 兑换失败 ) type ExchangeItem struct { logic.Logic Req *pb.ExchangeItemReq Res *pb.ExchangeItemRes } func (s *ExchangeItem) Run() (err error) { return logic.Run(s) } func (s *ExchangeItem) Data() (data jsonm.JsonM) { data, _ = jsonm.Marshal(s.Req) return } func (s *ExchangeItem) Exec() (err error) { item, err := s.item() if err != nil || item == nil { return } pu, err := s.pu() if err != nil { return } exchanging := app.NewRedisLock(s.Ctx, ckey.MallItemExchanging(pu.GetProjectUser().ID)) if !exchanging.SoftLock() { s.Res.Status = exchangeStatusExchanging return } defer exchanging.Release() err = s.checkItem(item) if err != nil || s.Res.GetStatus() != exchangeStatusOk { return } if s.Req.GetPointSource() == 0 { return s.appExchange(pu, item) } return s.exchange(pu, item) } func (s *ExchangeItem) item() (item *model.RedeemMallItem, err error) { 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 *ExchangeItem) pu() (pu *projectuser.ProjectUser, err error) { return projectuser.NewProjectUser(s.Ctx, projectuser.WithInit(s.Req.GetProjectId(), s.Req.GetUserId()), projectuser.WithDao(s.GetDao())) } func (s *ExchangeItem) 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 *ExchangeItem) exchange(pu *projectuser.ProjectUser, item *model.RedeemMallItem) (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.ProjectUserCoinUseSourceCenter, mtype.ProjectUserCoinDerMall, item.MallID, item.CoinNum) if err != nil { return } if !ok { s.Rollback("项目用户积分不足") s.Res.Status = exchangeStatusPointLack return } return s._pushReward(item) } func (s *ExchangeItem) appExchange(pu *projectuser.ProjectUser, item *model.RedeemMallItem) (err error) { lackKey := ckey.ProjectUserCoinLack(pu.GetProjectUser().ProjectID, pu.GetProjectUser().UserID) // 看看是不是有 if app.Redis.Cache.Exists(s.Ctx, lackKey).Val() != 0 { s.Res.Status = exchangeStatusLack return } deductM := s.GetDao().ProjectUserCoinDeduct var cond = []gen.Condition{ deductM.UserID.Eq(pu.GetProjectUser().ID), deductM.RelateID.Eq(item.ID), deductM.Num.Eq(item.CoinNum), deductM.Source.Eq(mtype.ProjectUserCoinDeductSourceApp), } deduct, err := deductM.WithContext(s.Ctx).Where(cond...).Attrs(deductM.ID.Value(hashid.IdBigint())).FirstOrCreate() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } defer func() { if err != nil { return } _, err = deductM.WithContext(s.Ctx).Delete(deduct) if err != nil { err = s.BE().MysqlError(err) } }() s2s, err := stos.NewUserCoinChange(s.Ctx, pu.GetProjectUser(), s.Req.GetUrlId(), stos.SetDao(s.GetDao())) if err != nil { return } res, err := s2s.CenterExchange( item.CoinNum, deduct.ID, item.ID, s.Req.GetSourceData().GetCenterId(), s.Req.GetSourceData().GetCenterTitle(), s.Req.GetSourceData().GetCenterHashId(), s.Req.GetUserId(), ) if err != nil { return } if res.State == stos.PointsChangeLack { app.Redis.Cache.Set(s.Ctx, lackKey, 1, 10*time.Second) s.Res.Status = exchangeStatusPointLack return } return s._pushReward(item) } func (s *ExchangeItem) _pushReward(item *model.RedeemMallItem) (err error) { mall, err := malls.NewMall(s.Ctx, item.MallID) if err != nil { return } var record *model.RedeemMallRecord var info = provider.RewardAutoLang{} switch s.Req.GetSource() { case 1: record, info, err = mall.Exchange( item, mtype.RewardPushSourceCenter, s.Req.GetSourceData().GetCenterId(), s.Req.GetSourceData().GetModuleId(), s.Req.GetSourceData().GetRelateId(), mtype.RewardPushParam{ SourceHashId: s.Req.GetSourceData().GetCenterTitle(), UserId: s.Req.GetUserId(), UserPlatform: s.Req.GetSourceData().GetUserPlatform(), ParentUserId: s.Req.GetSourceData().GetParentUserId(), ParentUserPlatform: s.Req.GetSourceData().GetParentUserPlatform(), Num: item.CoinNum, Lang: s.Req.GetUserId(), Title: s.Req.GetSourceData().GetCenterTitle(), RewardType: mtype.FissionCampaignRewardTypeExchange, }, ) if err != nil { return err } if record == nil { return s.BE().VerifyError("not found reward") } default: return s.BE().VerifyError("not found source") } s.Res.Status = exchangeStatusOk if record.Status == mtype.RedeemMallRecordStatusFail { s.Res.Status = exchangeStatusFail } 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.ExchangeItemRes_KV, 0, len(info.KvData)) for _, datum := range info.KvData { s.Res.KvData = append(s.Res.KvData, &pb.ExchangeItemRes_KV{ Key: datum.Key, Value: datum.Value, IsShow: datum.IsShow, }) } return }