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.

360 lines
7.5 KiB
Go

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
}