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.

671 lines
16 KiB
Go

package players
import (
"context"
"joybuywork/internal/app"
"joybuywork/internal/consts"
"joybuywork/internal/consts/ckey"
"joybuywork/internal/plugins/jsonm"
"joybuywork/internal/repository/cache"
"joybuywork/internal/repository/dao"
"joybuywork/internal/repository/dbcheck"
"joybuywork/internal/repository/model"
"joybuywork/internal/repository/mtype"
"joybuywork/internal/repository/withdefault"
"joybuywork/internal/service/cmm"
"joybuywork/internal/service/provider"
"joybuywork/internal/service/provider/award"
"joybuywork/internal/service/provider/fissionaction"
"joybuywork/internal/service/provider/projectuser"
stos "joybuywork/internal/service/provider/s2s"
"joybuywork/internal/service/provider/stocks"
"joybuywork/internal/utils"
"joybuywork/internal/utils/lockdo"
"joybuywork/internal/utils/logic"
"joybuywork/internal/utils/merr"
"joybuywork/internal/utils/timez"
"joybuywork/proto/go_proto/workerr"
"fmt"
"strings"
"time"
"gits.branchcn.com/backend/microcode/plugins/lucky"
)
type Player struct {
logic.Context
cpn *model.FissionCampaign
user *model.FissionUser
tz *provider.CampaignTZ
mpInApp *fissionaction.InApp
}
// Init 初始化一个逻辑类
func (s *Player) Init(ctx context.Context, user *model.FissionUser, fissionId int64, opts ...Option) (err error) {
s.Context.Init(ctx, dao.Q, merr.NewBE(ctx))
s.user = user
for _, opt := range opts {
opt(s)
}
// 如果外部没设置的话自己设置一下
if s.cpn == nil {
s.cpn, err = cache.NewCampaign(s.Ctx).FindById(s.user.CampaignID)
if err != nil {
err = s.BE().CacheError(err)
return
}
if s.cpn == nil {
return workerr.NotFound("not found cpn")
}
}
// 验证一下活动玩法和实际玩法是否一致
if s.cpn.FissionID != fissionId {
return workerr.VerifyError(fmt.Sprintf("The current user identity does not belong to the current activity (%s)", consts.FissionIdMap[fissionId]))
}
return
}
// IsView 预览用户
func (s *Player) IsView() bool {
return s.user.IsView == mtype.Yes
}
// HelpOk 助力成功用户
func (s *Player) HelpOk() bool {
return s.user.ParentID > 0 && s.user.IsHelp == mtype.Yes
}
// appTrack 应用事件记录
func (s *Player) appTrack() fissionaction.InApp {
if s.mpInApp == nil {
mpInApp := fissionaction.NewMarkPoint(s.Ctx).InApp(s.user)
s.mpInApp = &mpInApp
}
return *s.mpInApp
}
// 统一抽奖
func (s *Player) _lottery() (reward *model.FissionCampaignReward, err error) {
rewards, err := cmm.NewCMM(s.Ctx, s.GetDao()).UserAppReward(s.user)
if err != nil {
return
}
//如果是预览用户,随机返回一个奖品
if s.IsView() {
var dataMap = make(map[int64]*model.FissionCampaignReward)
for _, v := range rewards {
if v.Odds == 0 {
continue
}
dataMap[v.ID] = v
}
for _, v := range dataMap {
reward = v
}
return
}
var ids []int64
for _, v := range rewards {
ids = append(ids, v.ID)
}
stock, err := stocks.NewCampaignReward(s.Ctx, ids...)
if err != nil {
return
}
useStockMap, _ := stock.AllUseStock()
var thinkId int64
var thinkMap = make(map[int64]*model.FissionCampaignReward)
var rewardMap = make(map[int64]*model.FissionCampaignReward)
for _, v := range rewards {
if v.IsThank == mtype.Yes {
thinkMap[v.ID] = v
rewardMap[v.ID] = v
thinkId = v.ID
} else if int64(v.Stock) > useStockMap[v.ID] {
rewardMap[v.ID] = v
}
}
//随机一个谢谢参与发放
var think *model.FissionCampaignReward
for _, v := range thinkMap {
think = v
}
//获取到了奖品
for {
//构建抽奖数据
var prize = make([]lucky.Reward, 0, len(rewardMap))
for _, v := range rewardMap {
prize = append(prize, lucky.Reward{
Share: int(v.Odds),
Mark: v.ID,
})
}
// 抽奖
lcy, _ := lucky.NewDiscrete(s.Ctx, prize).Lottery()
// 没中奖
if lcy.Int64() == 0 {
reward = think
} else {
reward = rewardMap[lcy.Int64()]
}
// 中奖了
var check bool
check, err = award.NewAwardIssVerify(s.Ctx, s.user, reward).DeductStock().RuleFilter().Unlock().IsOK()
if err != nil {
return
}
// 扣除库存成功
if check {
return
}
// 这个奖品这个人不能中了
rewardMap[thinkId].Odds += reward.Odds
delete(rewardMap, lcy.Int64())
}
}
// GetTz 获取用户的时区
func (s *Player) GetTz() *provider.CampaignTZ {
if s.tz == nil {
s.tz = provider.NewCampaignTZ(s.Ctx, s.cpn, s.user.LangMark)
}
return s.tz
}
// 最后签到
func (s *Player) _lastSign(lastSign string) (ok bool, day string) {
tz := provider.NewCampaignTZ(s.Ctx, s.cpn, s.user.LangMark)
if tz.IsWait() {
return false, lastSign
}
signDate, _, _ := timez.GMTStartAndEndTs(tz.GetTz())
day = strings.ReplaceAll(signDate, "-", "")
if lastSign == "" || day > lastSign {
s.appTrack().Sign(time.Now().Unix(), tz.GetTz(), day)
return true, day
}
return false, lastSign
}
// 手动领取任务奖励
func (s *Player) _receiveReward(taskRewards []*model.FissionUserTaskReward, callback func(tasks []*model.FissionCampaignTask, records []*award.RewardRes) error) (err error) {
var taskRewardMap = make(map[int64]int64)
var ids []int64
defer func() {
if err != nil || len(ids) == 0 {
return
}
receiveTime := time.Now().Unix()
m := s.GetDao().FissionUserTaskReward
_, err = m.WithContext(s.Ctx).Where(m.ID.In(ids...)).UpdateSimple(m.ReceiveTime.Value(receiveTime))
if err != nil {
err = s.BE().MysqlError(err)
return
}
for _, task := range taskRewards {
s.appTrack().ManualTask(receiveTime, task.TaskID, taskRewardMap[task.TaskID])
}
}()
var taskMap = make(map[int64]*model.FissionCampaignTask)
var taskIds []int64
for _, v := range taskRewards {
ids = append(ids, v.ID)
if _, ok := taskMap[v.TaskID]; !ok {
taskIds = append(taskIds, v.TaskID)
taskMap[v.TaskID] = nil
}
}
taskM := s.GetDao().FissionCampaignTask
cpnTasks, err := taskM.WithContext(s.Ctx).Where(taskM.ID.In(taskIds...)).Find()
if err != nil {
err = s.BE().MysqlError(err)
return
}
for _, v := range cpnTasks {
taskMap[v.ID] = v
}
var rewardIds []int64
var tasks []*model.FissionCampaignTask
for _, v := range taskRewards {
task, ok := taskMap[v.TaskID]
if !ok {
continue
}
taskRewardMap[v.TaskID] = task.RewardID
tasks = append(tasks, task)
switch task.PropMark {
case mtype.FissionCampaignRewardPropMarkReward:
rewardIds = append(rewardIds, task.RewardID)
}
}
var records []*award.RewardRes
if len(rewardIds) > 0 {
records, _, err = award.NewAwardReward(s.Ctx, s.GetDao(), s.cpn.ProjectID, s.user).RewardIdsBatch(rewardIds, true)
if err != nil {
return
}
}
return callback(tasks, records)
}
const (
pointsUseOk = iota // 积分使用成功
pointsUseLack // 积分不足
pointsUseClose // 积分使用关闭
pointsUseDayLimit // 积分每日使用次数超限额
pointsUseTotalLimit // 积分使用次数超限额
)
type pointsUseCond struct {
Field string
Value any
}
func (s *Player) pointsUse(source, tableName string, other mtype.PointsLogOther, where []*pointsUseCond) (status int32, deduct *model.FissionPointDeduct, err error) {
lackKey := ckey.FissionCoinLack(s.user.ID)
if app.Redis.Cache.Exists(s.Ctx, lackKey).Val() != 0 {
status = pointsUseLack
return
}
conf, err := cache.NewCampaignPointsUse(s.Ctx).FindByCampaignId(s.user.CampaignID)
if err != nil {
err = s.BE().CacheError(err)
return
}
if conf == nil || conf.UseNum == 0 {
status = pointsUseClose
return
}
if conf.DayLimit > 0 {
tz := s.GetTz()
day, start, end := timez.GMTStartAndEndTs(tz.GetTz())
dayLimitKey := ckey.FissionPointsUseDayNum(s.user.ID, day)
var dayNum int64
dayNum, err = lockdo.Int64(s.Ctx, dayLimitKey, func() (num int64, err error) {
query := app.DB.Client.Debug().Table(tableName).WithContext(s.Ctx).
Where("campaign_id = ? and user_id = ? and created_at between ? and ?", s.user.CampaignID, s.user.ID, start, end)
for _, cond := range where {
query = query.Where(fmt.Sprintf("%s = ?", cond.Field), cond.Value)
}
query.Count(&num)
return
})
if err != nil {
err = s.BE().CacheError(err)
return
}
if dayNum >= int64(conf.DayLimit) {
status = pointsUseDayLimit
return
}
defer func() {
if err == nil && status == pointsUseOk {
app.Redis.Cache.Incr(s.Ctx, dayLimitKey)
}
}()
}
if conf.TotalLimit > 0 {
totalLimitKey := ckey.FissionPointsUseNum(s.user.ID)
var totalNum int64
totalNum, err = lockdo.Int64(s.Ctx, totalLimitKey, func() (num int64, err error) {
query := app.DB.Client.Debug().Table(tableName).WithContext(s.Ctx).
Where("campaign_id = ? and user_id = ?", s.user.CampaignID, s.user.ID)
for _, cond := range where {
query = query.Where(fmt.Sprintf("%s = ?", cond.Field), cond.Value)
}
query.Count(&num)
return
})
if err != nil {
err = s.BE().CacheError(err)
return
}
if totalNum >= int64(conf.TotalLimit) {
status = pointsUseTotalLimit
return
}
defer func() {
if err == nil && status == pointsUseOk {
app.Redis.Cache.Incr(s.Ctx, totalLimitKey)
}
}()
}
deductM := s.GetDao().FissionPointDeduct
deduct, err = deductM.WithContext(s.Ctx).Where(deductM.UserID.Eq(s.user.ID)).Take()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
if deduct == nil {
deduct = withdefault.FissionPointsDeduct(&model.FissionPointDeduct{
UserID: s.user.ID,
Num: conf.UseNum,
})
if err = s.GetDao().FissionPointDeduct.WithContext(s.Ctx).Create(deduct); err != nil {
err = s.BE().MysqlError(err)
return
}
} else {
deduct.Num = conf.UseNum
}
s2s, err := stos.NewPointsChange(s.Ctx, s.cpn, s.user, conf.URLID)
if err != nil {
return 0, nil, err
}
other.DeductId = deduct.ID
res, err := s2s.Deduct(conf.UseNum, source, other)
if err != nil {
return 0, nil, err
}
if res.State == stos.PointsChangeLack {
status = pointsUseLack
app.Redis.Cache.Set(s.Ctx, lackKey, 1, 10*time.Second)
return
}
return
}
func (s *Player) pointsUseOk(deduct *model.FissionPointDeduct) {
if deduct == nil {
return
}
deductM := s.GetDao().FissionPointDeduct
_, _ = deductM.WithContext(s.Ctx).Delete(deduct)
s.appTrack().PointsUse(time.Now().Unix(), deduct.Num)
}
func (s *Player) GetTCoin() (coinNum int64, err error) {
pu, err := projectuser.NewProjectUser(s.Ctx, projectuser.WithInit(s.cpn.ProjectID, s.user.SubUserID))
if err != nil {
return
}
coin, err := pu.GetCoin()
if err != nil {
return
}
return coin.Num, nil
}
func (s *Player) unlockReward(task *model.FissionCampaignTask) (ok bool, err error) {
if task.PropMark != mtype.FissionCampaignTaskPropMarkUnlockReward {
return
}
cpnId, userId, rewardId := s.cpn.ID, s.user.ID, task.RewardID
key := ckey.FissionUserUnlockReward(userId, rewardId)
first := app.Redis.Cache.SetNX(s.Ctx, key, 1, consts.TimeDay).Val()
if !first {
return false, nil
}
m := s.GetDao().FissionUnlockReward
ulr, err := m.WithContext(s.Ctx).Where(
m.CampaignID.Eq(cpnId),
m.UserID.Eq(userId),
m.RewardID.Eq(rewardId),
).Take()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
if ulr != nil {
return false, nil
}
err = m.WithContext(s.Ctx).Create(withdefault.FissionUnlockReward(&model.FissionUnlockReward{
CampaignID: cpnId,
UserID: userId,
RewardID: rewardId,
}))
if err != nil {
err = s.BE().MysqlError(err)
return
}
s.appTrack().UnlockReward(time.Now().Unix(), rewardId, task.ID)
return true, nil
}
type EventReward struct {
PropMark string
Chance int32
Reward *award.RewardRes
}
func (s *Player) eventOk(mark string) (ok bool, rewards []*EventReward, err error) {
event, err := cache.NewCampaignEvent(s.Ctx).FindByCampaignId(s.user.CampaignID, mark)
if err != nil {
err = s.BE().CacheError(err)
return
}
if event == nil {
return
}
switch event.Mark {
case mtype.FissionCampaignEventMarkFirstVisit:
return s._eventFirstVisit(event)
}
return
}
func (s *Player) _eventFirstVisit(event *model.FissionCampaignEvent) (ok bool, rewards []*EventReward, err error) {
eventKey := ckey.FissionUserEventFirstVisit(s.user.ID)
if !app.Redis.Cache.SetNX(s.Ctx, eventKey, 1, lockdo.Rand2HourExp()).Val() {
return
}
m := s.GetDao().FissionUserEvent
fv, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(s.user.ID), m.Mark.Eq(mtype.FissionCampaignEventMarkFirstVisit)).Take()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
if fv != nil {
return
}
fv = withdefault.FissionUserEvent(&model.FissionUserEvent{
CampaignID: s.user.CampaignID,
UserID: s.user.ID,
Mark: mtype.FissionCampaignEventMarkFirstVisit,
})
if err = m.WithContext(s.Ctx).Create(fv); err != nil {
err = s.BE().MysqlError(err)
}
if event.Open == mtype.No {
return
}
var rewardIds []int64
for _, reward := range event.GetRewards() {
switch reward.PropMark {
case mtype.FissionCampaignRewardPropMarkChance:
rewards = append(rewards, &EventReward{
PropMark: reward.PropMark,
Chance: reward.Chance,
})
case mtype.FissionCampaignRewardPropMarkReward, mtype.FissionCampaignRewardPropMarkPoint:
rewardIds = append(rewardIds, reward.RewardId)
}
}
if len(rewardIds) == 0 {
return
}
res, _, err := award.NewAwardReward(s.Ctx, s.GetDao(), s.cpn.ProjectID, s.user).RewardIdsBatch(rewardIds, true)
if err != nil {
return
}
for _, reward := range res {
rewards = append(rewards, &EventReward{
PropMark: reward.Reward.PropMark,
Reward: reward,
})
}
return
}
// _lotteryTask 抽奖任务通用处理
func (s *Player) _lotteryTask(checkManual bool, task *model.FissionCampaignTask) (next bool, t int8, source string, lotteryVal mtype.LotteryLogVal) {
if checkManual && task.AwardMode == mtype.FissionCampaignTaskAwardModeManual {
next = true
return
}
lotteryVal.TaskId = task.ID
switch task.PropMark {
case mtype.FissionCampaignTaskPropMarkChance:
lotteryVal.Chance = task.Chance
t = mtype.LotteryFissionTypeAdd
case mtype.FissionCampaignTaskPropMarkUnlockReward:
ok, _ := s.unlockReward(task)
if !ok {
next = true
return
}
t = mtype.LotteryFissionTypeUnlock
lotteryVal.RewardId = task.RewardID
}
switch task.TaskType {
case mtype.FissionCampaignTaskTypeRegister:
source = mtype.FissionLogSourceRegister
case mtype.FissionCampaignTaskTypeInstall:
source = mtype.FissionLogSourceInstall
case mtype.FissionCampaignTaskTypeCondAll:
source = utils.Ternary[string](task.AwardMode == mtype.FissionCampaignTaskAwardModeManual, mtype.FissionLogSourceManualCondAll, mtype.FissionLogSourceCondAll)
case mtype.FissionCampaignTaskTypeCondOne:
source = utils.Ternary[string](task.AwardMode == mtype.FissionCampaignTaskAwardModeManual, mtype.FissionLogSourceManualCondOne, mtype.FissionLogSourceCondOne)
}
return
}
type TaskTip struct {
PropMark string
Chance int32
RecordId int64
}
func GetTaskTip(ctx context.Context, userId int64) (list []*TaskTip, err error) {
taskTipKey := ckey.FissionUserTaskTip(userId)
strs, err := app.Redis.Cache.LRange(ctx, taskTipKey, 0, -1).Result()
if err != nil {
return
}
list = make([]*TaskTip, 0, len(strs))
for _, str := range strs {
taskTip := &TaskTip{}
if err = jsonm.StrUnmarshal(str, taskTip); err != nil {
continue
}
list = append(list, taskTip)
}
return list, nil
}
func AddTaskTip(ctx context.Context, userId int64, propMark string, chance int32, recordId int64) {
taskTipKey := ckey.FissionUserTaskTip(userId)
taskTip := &TaskTip{
PropMark: propMark,
Chance: chance,
RecordId: recordId,
}
str, _ := jsonm.MarshalToString(taskTip)
app.Redis.Cache.RPush(ctx, taskTipKey, str)
app.Redis.Cache.Expire(ctx, taskTipKey, consts.TimeWeek)
}
func DelTaskTip(ctx context.Context, userId int64, l int32) {
taskTipKey := ckey.FissionUserTaskTip(userId)
for i := 0; i < int(l); i++ {
app.Redis.Cache.LPop(ctx, taskTipKey)
}
}