package projectuser import ( "context" "joybuywork/internal/app" "joybuywork/internal/consts/ckey" "joybuywork/internal/repository/cache" "joybuywork/internal/repository/model" "joybuywork/internal/repository/mtype" "joybuywork/internal/repository/withdefault" "joybuywork/internal/service/queue/producer" "joybuywork/internal/service/queue/types" "joybuywork/internal/utils" "joybuywork/internal/utils/lockdo" "joybuywork/internal/utils/logic" "joybuywork/internal/utils/merr" "time" ) type initData struct { ProjectID int64 UserID string } type ProjectUser struct { logic.Context user *model.ProjectUser coinKey string withInit *initData } func NewProjectUser(ctx context.Context, opts ...Option) (pu *ProjectUser, err error) { pu = new(ProjectUser) { pu.BaseInit(ctx, merr.NewBE(ctx)) } for _, opt := range opts { opt(pu) } if pu.withInit != nil { err = pu.init(pu.withInit.ProjectID, pu.withInit.UserID) if err != nil { return } } pu.coinKey = ckey.ProjectUserCoinNum(pu.user.ID) return } func (s *ProjectUser) GetProjectUser() *model.ProjectUser { return s.user } func (s *ProjectUser) init(projectId int64, userId string) (err error) { s.user, err = cache.NewProjectUser(s.Ctx).FindByUniqId(projectId, userId) if err != nil { err = s.BE().CacheError(err) return } if s.user != nil { return } lc := app.NewFollower(s.Ctx, ckey.ProjectUserCreate(projectId, userId)).Lock() defer lc.Release() if lc.Error() != nil { return lc.Error() } s.user, err = cache.NewProjectUser(s.Ctx).FindByUniqId(projectId, userId) if err != nil { err = s.BE().CacheError(err) return } if s.user != nil { return } user := withdefault.ProjectUser(&model.ProjectUser{ ProjectID: projectId, UserID: userId, }) if err = s.GetDao().ProjectUser.WithContext(s.Ctx).Create(user); err != nil { err = s.BE().MysqlError(err) return } s.user = user return } type Coin struct { Num int64 `json:"num"` } func (s *ProjectUser) GetCoin() (coin Coin, err error) { coinNum, err := s.coinNum() if err != nil { return } coin.Num = coinNum return } func (s *ProjectUser) IncrCoin(num int64) (coinNum int64, err error) { coinNum, err = s.coinNum() if err != nil { return } coinNum, err = app.Redis.Cache.IncrBy(s.Ctx, s.coinKey, num).Result() if err != nil { return } app.Redis.Cache.Expire(s.Ctx, s.coinKey, lockdo.Rand2HourExp()) return } func (s *ProjectUser) Expired(num int64) (coinNum int64, err error) { if app.Redis.Cache.Exists(s.Ctx, s.coinKey).Val() == 0 { return } return s.IncrCoin(-num) } type coin struct { Num int64 `json:"num"` } func (s *ProjectUser) coinNum() (num int64, err error) { var coinNum coin m := s.GetDao().ProjectUserCoin err = m.WithContext(s.Ctx). Select(m.CoinUse.Sum().As("num")). Where( m.ProjectID.Eq(s.user.ProjectID), m.UserID.Eq(s.user.ID), m.ExpiredAt.Gt(time.Now().Unix()), m.IsUsed.Eq(mtype.No), ). Scan(&coinNum) if err != nil { err = s.BE().MysqlError(err) return } return coinNum.Num, nil } type EarnCoinParam struct { CoinNum int32 IsExpired int8 ExpiredAt int64 Source string SourceID int64 SourceData map[string]string Operate string } func (s *ProjectUser) EarnCoin(earns ...*EarnCoinParam) (err error) { var incrNum int64 var coins []*model.ProjectUserCoin var expired []*model.ProjectUserCoinExpired var records []*model.ProjectUserCoinRecord for _, earn := range earns { one := withdefault.ProjectUserCoin(&model.ProjectUserCoin{ ProjectID: s.user.ProjectID, UserID: s.user.ID, CoinNum: earn.CoinNum, CoinUse: earn.CoinNum, ExpiredAt: earn.ExpiredAt, IsExpired: earn.IsExpired, IsUsed: 0, Source: earn.Source, SourceID: earn.SourceID, SourceData: "", }, earn.SourceData) coins = append(coins, one) if earn.IsExpired == mtype.Yes { expired = append(expired, withdefault.ProjectUserCoinExpired(&model.ProjectUserCoinExpired{ UserID: s.user.ID, CoinID: one.ID, DoneAt: earn.ExpiredAt, })) } incrNum += int64(earn.CoinNum) records = append(records, withdefault.ProjectUserCoinRecord(&model.ProjectUserCoinRecord{ ProjectID: s.user.ProjectID, UserID: s.user.ID, Source: earn.Source, SourceID: earn.SourceID, Type: mtype.ProjectUserCoinRecordTypeAdd, Mark: earn.Operate, Num: earn.CoinNum, ExpiredAt: utils.Ternary[int64](earn.IsExpired == mtype.Yes, earn.ExpiredAt, 0), })) } if err = s.GetDao().ProjectUserCoin.WithContext(s.Ctx).Create(coins...); err != nil { err = s.BE().MysqlError(err) return } if err = s.GetDao().ProjectUserCoinRecord.WithContext(s.Ctx).Create(records...); err != nil { err = s.BE().MysqlError(err) return } if len(expired) > 0 { if err = s.GetDao().ProjectUserCoinExpired.WithContext(s.Ctx).Create(expired...); err != nil { err = s.BE().MysqlError(err) return } } //_, err = s.IncrCoin(incrNum) //if err != nil { // return //} return } func (s *ProjectUser) Deduct(source, operate string, sourceId int64, num int32) (ok bool, err error) { m := s.GetDao().ProjectUserCoin var coinIds []int64 var deductNum = num var lastId int64 for { var list []*model.ProjectUserCoin list, err = m.WithContext(s.Ctx).Where( m.UserID.Eq(s.user.ID), m.ExpiredAt.Gte(time.Now().Unix()), m.IsUsed.Eq(mtype.No), m.ID.Gt(lastId), ).Order(m.ExpiredAt, m.ID).Limit(100).Find() if err != nil { err = s.BE().MysqlError(err) return } if len(list) == 0 { break } var partIds []int64 for _, item := range list { deductNum -= item.CoinUse if deductNum >= 0 { partIds = append(partIds, item.ID) } if deductNum < 0 { _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(item.ID)).UpdateSimple(m.CoinUse.Value(-deductNum)) if err != nil { return } coinIds = append(coinIds, item.ID) break } } if len(partIds) > 0 { _, err = m.WithContext(s.Ctx).Where(m.ID.In(partIds...)).UpdateSimple(m.CoinUse.Value(0), m.IsUsed.Value(mtype.Yes)) if err != nil { return } coinIds = append(coinIds, partIds...) } if deductNum <= 0 { break } lastId = list[len(list)-1].ID } if deductNum > 0 { return false, nil } use := withdefault.ProjectUserCoinUse(&model.ProjectUserCoinUse{ UserID: s.user.ID, Num: num, Source: source, SourceID: sourceId, Operate: operate, }, coinIds) err = s.GetDao().ProjectUserCoinUse.WithContext(s.Ctx).Create(use) if err != nil { err = s.BE().MysqlError(err) return } err = s.GetDao().ProjectUserCoinRecord.WithContext(s.Ctx).Create(withdefault.ProjectUserCoinRecord(&model.ProjectUserCoinRecord{ ProjectID: s.user.ProjectID, UserID: s.user.ID, Source: source, SourceID: sourceId, Type: mtype.ProjectUserCoinRecordTypeDer, Mark: operate, Num: num, })) if err != nil { err = s.BE().MysqlError(err) return } return true, nil } func (s *ProjectUser) DeductOk(source, relateMark string, relateID int64, operate string, num int32) (err error) { deduct := withdefault.ProjectUserCoinDeduct(&model.ProjectUserCoinDeduct{ UserID: s.user.ID, Source: source, RelateMark: relateMark, RelateID: relateID, Operate: operate, Num: num, }) if err = s.GetDao().ProjectUserCoinDeduct.WithContext(s.Ctx).Create(deduct); err != nil { err = s.BE().MysqlError(err) return } producer.NewCampaignNotice(s.Ctx).ProjectUserCoinDeduct(types.ProjectUserCoinDeduct{ Type: types.ProjectUserCoinDeductTypeDeduct, UserId: s.user.ID, DeductId: deduct.ID, }) return }