You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

293 lines
6.9 KiB
Go

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
}