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.

1679 lines
39 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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/cachecurd"
"joybuywork/internal/service/provider/fissionaction"
"joybuywork/internal/service/provider/stocks"
"joybuywork/internal/utils"
"joybuywork/internal/utils/timez"
"errors"
"fmt"
"strings"
"time"
"joybuywork/proto/go_proto/workerr"
"github.com/golang-module/carbon/v2"
"github.com/rs/xid"
"gorm.io/gen/field"
"gorm.io/gorm"
)
const (
SignStatusOk = iota //签到成功
SignStatusFail //签到失败
SignStatusMissCard //补签卡不足
SignStatusDone //已签到
SignStatusCard //补签签到
SignStatusWait //活动未开启
)
const (
SignWait = iota // 未签到
SignDone // 已签到
SignMiss // 漏签
)
type Status struct {
DirectNum int64 // 直接邀请人数
MaxContinue int32 // 最大连续签到
TodaySign int32 // 今天是否有签到
CycleDay int32 // 周期签到天数
}
// Sign 签到交互逻辑
type Sign struct {
Player
player *model.FissionSign
conf *model.FissionCampaignSign
upPlayer []field.AssignExpr
signDataKey string
cacheData *cachecurd.HMap
}
func NewSign(ctx context.Context, user *model.FissionUser, opts ...Option) (player *Sign, err error) {
player = &Sign{}
err = player.Init(ctx, user, consts.IdSignIn, opts...)
if err != nil {
return
}
err = player.initPlayer()
if err != nil {
return
}
return
}
type SignReward struct {
RewardTitle string //奖品名称
RewardNum int32 //获得的奖品数量
ImgUrl string //奖品图片
RewardId int64 //奖品id
ReceiveType int32 //发放类型(0-无操作,1-系统发放,2-联系客服,3-客户端推送)
KvData []*mtype.KV //键值数据
Contact string //联系方式
}
// SignIn 签到
func (s *Sign) SignIn(signTime int64) (status int32, reward *SignReward, err error) {
defer func() {
s.AfterDo(err)
}()
tz := s.GetTz()
if !tz.IsRunning() {
status = SignStatusWait
return
}
// 今天的开始
today, _ := timez.GMTTimeStart(tz.GetTz())
// 提交上来的签到时间有这个可能是补签卡
submitDay, _ := timez.GMTTimeStart(tz.GetTz(), signTime)
s.Tran()
defer func() {
s.TranCommit(err)
}()
conf := s.GetConf()
if conf.SignType == mtype.FissionCampaignSignTypeCycle {
if signTime == 0 || today.Equal(submitDay) {
return s._cycleSign()
}
return s._cycleCardSign(submitDay)
}
// 下面的逻辑太老旧了,不想嵌套到里面 !!!
if today.Before(submitDay) { // 如果当前时间小于提交的时间,那就是未来的时间,未来时间不可以签到
status = SignStatusFail
return
}
if signTime == 0 || today.Equal(submitDay) {
return s._todaySign(today)
}
return s._cardSign(submitDay)
}
// 周期补签卡签到
func (s *Sign) _cycleCardSign(signTime time.Time) (status int32, reward *SignReward, err error) {
s._cycleStart()
playerM := s.GetDao().FissionSign
player := s.GetPlayer()
if player.CardNum <= 0 {
status = SignStatusMissCard
return
}
if player.CycleStart > signTime.Unix() || signTime.Unix() >= time.Now().Unix() {
status = SignStatusFail
return
}
status, signDay, err := s._sign(signTime, true)
if err != nil || status != SignStatusOk {
return
}
cd, err := s._cycleData(false)
if err != nil {
return
}
// 签到的天更新
signStr, err := cd.Get(signCycleDay)
if err != nil {
return
}
if signStr == "" {
signStr = signDay.SignDate
} else {
signStr += consts.JoinComma + signDay.SignDate
}
s.AddOkDone(func() {
_ = cd.Set(signCycleDay, signStr)
})
continueNum, maxContinue, _, err := s._continueNum()
if err != nil {
return
}
s.AddOkDone(func() {
_ = cd.Set(signMaxContinue, fmt.Sprint(maxContinue))
_ = cd.Set(signContinueNum, fmt.Sprint(continueNum))
fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).UseProp(time.Now().Unix(), consts.PropNameSignCard)
})
s.upPlayer = append(
s.upPlayer,
playerM.SignContinue.Value(continueNum),
playerM.MaxContinue.Value(maxContinue),
playerM.CardNum.Sub(1),
)
signDays := strings.Split(signStr, consts.JoinComma)
signNum := int32(len(signDays))
reward, recordIds, err := s._cycleReward(signNum, signDay)
if err != nil {
return
}
err = s._cyclePlayerSync(recordIds)
if err != nil {
return
}
return
}
// 周期签到
func (s *Sign) _cycleSign() (status int32, reward *SignReward, err error) {
s._cycleStart()
playerM := s.GetDao().FissionSign
player, tz := s.GetPlayer(), s.GetTz()
// 今天的开始
today, _ := timez.GMTTimeStart(tz.GetTz())
status, signDay, err := s._sign(today, false)
if err != nil || status != SignStatusOk {
return
}
if player.SignStart == 0 {
s.player.SignStart = today.Unix()
s.upPlayer = append(s.upPlayer, playerM.SignStart.Value(today.Unix()))
}
cd, err := s._cycleData(false)
if err != nil {
return
}
s.AddOkDone(func() {
// 下一次签到时间
err = cd.Set(signNextDay, fmt.Sprint(today.AddDate(0, 0, 1).Unix()))
if err != nil {
return
}
// 最后签到时间
err = cd.Set(signLastDay, fmt.Sprint(today.Unix()))
if err != nil {
return
}
})
// 签到的天更新
signStr, err := cd.Get(signCycleDay)
if err != nil {
return
}
if signStr == "" {
signStr = signDay.SignDate
} else {
signStr += consts.JoinComma + signDay.SignDate
}
s.AddOkDone(func() {
_ = cd.Set(signCycleDay, signStr)
})
// 连续签到数据
continueNum, _ := cd.GetInt64(signContinueNum)
s.AddOkDone(func() {
_, _ = cd.Incr(signContinueNum, 1)
})
continueNum += 1
maxContinue, _ := cd.GetInt64(signMaxContinue)
if continueNum > maxContinue {
maxContinue = continueNum
s.AddOkDone(func() {
_ = cd.Set(signMaxContinue, fmt.Sprint(maxContinue))
})
}
s.upPlayer = append(
s.upPlayer,
playerM.SignContinue.Value(int32(continueNum)),
playerM.MaxContinue.Value(int32(maxContinue)),
)
signDays := strings.Split(signStr, consts.JoinComma)
signNum := int32(len(signDays))
reward, recordIds, err := s._cycleReward(signNum, signDay)
if err != nil {
return
}
err = s._cyclePlayerSync(recordIds)
if err != nil {
return
}
return
}
// 周期奖品处理
func (s *Sign) _cycleReward(signNum int32, signDay *model.FissionSignDay) (reward *SignReward, recordIds []int64, err error) {
//当前签到是否有奖品
rewards, err := cmm.NewUserAppReward(s.Ctx).Get(s.GetDao(), s.user)
if err != nil {
return
}
var signReward *model.FissionCampaignReward
for _, v := range rewards {
if v.SignDay > signNum {
break
}
if v.SignDay == signNum {
signReward = v
}
}
if signReward == nil {
return
}
reward, recordIds, err = s._signReward(signDay, signReward)
if err != nil {
return
}
// 绑定数据
sdm := s.GetDao().FissionSignDay
_, err = sdm.WithContext(s.Ctx).Where(sdm.ID.Eq(signDay.ID)).UpdateSimple(sdm.RecordIds.Value(utils.Join(recordIds, consts.JoinComma)))
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
// 周期用户更新数据同步
func (s *Sign) _cyclePlayerSync(recordIds []int64) (err error) {
playerM := s.GetDao().FissionSign
player := s.GetPlayer()
// 更新玩家数据
_, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(s.upPlayer...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
// 更新统计数据
statM := s.GetDao().FissionUserStat
_, err = statM.WithContext(s.Ctx).Where(statM.UserID.Eq(player.UserID)).UpdateSimple(statM.SignNum.Add(1), statM.SignRewardNum.Add(int64(len(recordIds))))
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
type SignBoard struct {
SignDate []*SignBoardOne //签到板信息
NowMouth string //当前月
CycleDate []*SignCycleDateOne
}
type SignCycleDateOne struct {
SignTime int64 //签到时间
SignIn int32 //签到类型(0-未签,1-已签,2-漏签)
NowDay bool //是否今天
RewardId int64 //奖品id,0即没有礼物
RewardTitle string //奖品图片
RewardImg string //奖品标题
IsSuper int32 //是否超级大奖 (0-否,1-是)
Y string //年
M string //月
D string //日
}
type SignBoardOne struct {
Y string //年
M string //月
D string //日
SignIn int32 //签到类型(0-未签,1-已签,2-漏签)
Ban bool //是否非签到日期
RewardId int64 //奖品id,0即没有礼物
Open bool //是否已经领奖
NowWeek bool //是否本周
NowDay bool //是否今天
LastMiss bool //是否最后一个漏签
NowMouth bool //是否本月
}
// SignBoard 签到版
func (s *Sign) SignBoard(pageType int32, nowMouth string) (signBoard *SignBoard, err error) {
defer func() {
s.AfterDo(err)
}()
conf := s.GetConf()
if conf.SignType == mtype.FissionCampaignSignTypeCycle {
return s._cycleSignBoard()
}
return s._normalSignBoard(pageType, nowMouth)
}
// 周期签到板
func (s *Sign) _cycleSignBoard() (signBoard *SignBoard, err error) {
s._cycleStart()
rewards, err := cmm.NewUserAppReward(s.Ctx).Get(s.GetDao(), s.user, s.GetDao().FissionCampaignReward.SignDay)
if err != nil {
return
}
var rewardIds []int64
var dayReward = make(map[int32]*model.FissionCampaignReward)
for _, reward := range rewards {
dayReward[reward.SignDay] = reward
rewardIds = append(rewardIds, reward.RewardID)
}
prewards, err := provider.NewRewards(s.Ctx, s.cpn.ProjectID, rewardIds...)
if err != nil {
return
}
player, conf, tz := s.GetPlayer(), s.GetConf(), s.GetTz()
today, _ := timez.GMTTimeStart(tz.GetTz())
cycleStart, _ := timez.GMTTimeStart(tz.GetTz(), player.CycleStart)
cpnEnd := s.GetTz().EndTime()
cd, err := s._cycleData(false)
if err != nil {
return
}
signDayStr, _ := cd.Get(signCycleDay)
var signDay []string
if signDayStr != "" {
signDay = strings.Split(signDayStr, consts.JoinComma)
}
var signMap = make(map[string]struct{})
for _, v := range signDay {
signMap[v] = struct{}{}
}
var signNum int32
var signDate = make([]*SignCycleDateOne, 0, int(conf.SignCycle))
for i := 0; i < int(conf.SignCycle); i++ {
one := &SignCycleDateOne{
SignTime: cycleStart.Unix(),
SignIn: 0,
NowDay: false,
RewardId: 0,
RewardTitle: "",
RewardImg: "",
IsSuper: 0,
Y: fmt.Sprint(cycleStart.Year()),
M: fmt.Sprint(int(cycleStart.Month())),
D: fmt.Sprint(cycleStart.Day()),
}
shortDate := cycleStart.Format(timez.ShortDateOnly)
if cycleStart.Before(today) {
if _, ok := signMap[shortDate]; ok {
one.SignIn = SignDone
signNum++
} else {
one.SignIn = SignMiss
}
} else {
signNum++
}
if cycleStart.Equal(today) {
one.NowDay = true
if _, ok := signMap[shortDate]; ok {
one.SignIn = SignDone
}
}
if reward, ok := dayReward[signNum]; ok {
info := prewards.AutoLang(reward.RewardID, s.user.LangMark)
one.RewardId = reward.ID
one.IsSuper = int32(reward.IsSuper)
one.RewardTitle = info.Title
one.RewardImg = info.ImgUrl
delete(dayReward, signNum)
}
signDate = append(signDate, one)
cycleStart = cycleStart.AddDate(0, 0, 1)
if cycleStart.After(cpnEnd) {
break
}
}
signBoard = &SignBoard{CycleDate: signDate}
return
}
// 看看要不要重制周期时间
func (s *Sign) _cycleStart() {
cd, err := s._cycleData(false)
if err != nil {
return
}
player, conf := s.GetPlayer(), s.GetConf()
dayStart := s._dayStart()
yesterdayStart := dayStart.AddDate(0, 0, -1)
var refresh bool
playerM := s.GetDao().FissionSign
if player.CycleStart == 0 {
s.player.CycleStart = dayStart.Unix()
s.upPlayer = append(s.upPlayer, playerM.CycleStart.Value(dayStart.Unix()))
s.AddOkDone(func() {
_ = cd.Set(signNextDay, fmt.Sprint(dayStart.Unix()))
})
} else if conf.RecycleType == mtype.FissionCampaignRecycleTypeContinue {
lastDay, _ := cd.GetInt64(signLastDay)
if lastDay < yesterdayStart.Unix() {
s.player.CycleStart = dayStart.Unix()
s.upPlayer = append(s.upPlayer, playerM.CycleStart.Value(dayStart.Unix()))
_ = cd.Clear()
refresh = true
}
} else if conf.RecycleType == mtype.FissionCampaignRecycleTypeCycle {
if (dayStart.Unix()-player.CycleStart)/consts.DaySec >= int64(conf.SignCycle) {
s.player.CycleStart = dayStart.Unix()
s.upPlayer = append(s.upPlayer, playerM.CycleStart.Value(dayStart.Unix()))
_ = cd.Clear()
refresh = true
}
}
_, err = s._cycleData(refresh)
if err != nil {
return
}
}
func (s *Sign) _dayStart() time.Time {
tz := s.GetTz()
dayStart, _ := timez.GMTTimeStart(tz.GetTz())
cpnStart := tz.StarTime()
if dayStart.Before(cpnStart) {
dayStart = cpnStart
}
return dayStart
}
// GetSignCard 获得了签到卡
func (s *Sign) GetSignCard(num int32) (err error) {
player := s.GetPlayer()
m := s.GetDao().FissionSign
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(m.CardNum.Add(num))
if err != nil {
err = s.BE().MysqlError(err)
return
}
val, _ := jsonm.ToObjString(mtype.SignLogVal{CardNum: num})
err = s._addLog(withdefault.FissionSignLog(&model.FissionSignLog{
UserID: player.UserID,
CampaignID: player.CampaignID,
SignID: player.ID,
Type: mtype.FissionSignLogTypeSignCard,
Source: mtype.FissionSignLogSourceGetCard,
Val: val,
}))
if err != nil {
return
}
s.appTrack().GetProp(time.Now().Unix(), consts.PropNameSignCard, num)
return
}
// GetStatus 用户额外信息
func (s *Sign) GetStatus() (status Status, err error) {
player, conf, tz := s.GetPlayer(), s.GetConf(), s.GetTz()
statM := s.GetDao().FissionUserStat
stat, err := statM.WithContext(s.Ctx).Where(statM.UserID.Eq(player.UserID)).Take()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
if stat != nil {
status.DirectNum = stat.DirectNum
}
var maxContinue = player.MaxContinue
if player.NextStep > 0 {
maxContinue = 0
m := s.GetDao().FissionCampaignReward
if player.NextStep > 1 {
var reward *model.FissionCampaignReward
reward, err = m.WithContext(s.Ctx).Where(m.CampaignID.Eq(player.CampaignID), m.Step.Eq(player.NextStep-1)).Take()
if err != nil {
err = s.BE().MysqlError(err)
return
}
if reward != nil {
maxContinue = reward.SignDay
}
} else if player.LoopNum > 0 {
var rewards []*model.FissionCampaignReward
rewards, err = cmm.NewCMM(s.Ctx, s.GetDao()).UserAppReward(s.user, m.Step)
if err != nil {
return
}
l := len(rewards)
maxContinue = rewards[l-1].SignDay
}
}
status.MaxContinue = maxContinue
today, _ := timez.GMTTimeStart(tz.GetTz())
exist, err := s._hasSign(today)
if err != nil {
return
}
status.TodaySign = utils.Ternary[int32](exist, 1, 0)
// 周期签到天数
if conf.SignType == mtype.FissionCampaignSignTypeCycle {
s._cycleStart()
cd, _ := s._cycleData(false)
cycleDay, _ := cd.Get(signCycleDay)
if cycleDay != "" {
status.CycleDay = int32(len(strings.Split(cycleDay, consts.JoinComma)))
}
}
return
}
// GetConf 获取玩法配置
func (s *Sign) GetConf() *model.FissionCampaignSign {
if s.conf == nil {
s.conf, _ = cache.NewCampaignSign(s.Ctx).FindByCampaignId(s.user.CampaignID)
}
return s.conf
}
// GetPlayer 获取玩家基础数据
func (s *Sign) GetPlayer() *model.FissionSign {
return s.player
}
// 玩家
func (s *Sign) initPlayer() (err error) {
player, err := s._dbFirst()
if err != nil {
return
}
defer func() {
if err != nil {
return
}
s.player = player
s.signDataKey = ckey.FissionSignData(player.ID)
}()
if player != nil {
return
}
player, err = s.firstOrCreate()
if err != nil {
return
}
return
}
// 创建玩家基础数据
func (s *Sign) firstOrCreate() (player *model.FissionSign, err error) {
lc := app.NewFollower(s.Ctx, ckey.FissionUserCreatePlayerOnce(s.user.ID)).Lock()
defer lc.Release()
if lc.Error() != nil {
err = lc.Error()
return
}
player, err = s._dbFirst()
if err != nil {
return
}
if player != nil {
return
}
return s.createPlayer()
}
// 数据库查询
func (s *Sign) _dbFirst() (*model.FissionSign, error) {
playerM := dao.FissionSign
player, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(s.user.ID)).Take()
if err = dbcheck.DbError(err); err != nil {
return nil, s.BE().MysqlError(err)
}
return player, nil
}
// 创建一个玩家
func (s *Sign) createPlayer() (player *model.FissionSign, err error) {
playerM := dao.FissionSign
player = withdefault.FissionSign(&model.FissionSign{
CampaignID: s.user.CampaignID,
UserID: s.user.ID,
})
err = playerM.WithContext(s.Ctx).Create(player)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
func (s *Sign) _addLog(logs ...*model.FissionSignLog) (err error) {
err = s.GetDao().FissionSignLog.WithContext(s.Ctx).Create(logs...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
// 连续签到数据
func (s *Sign) _continueNum() (continueNum, maxContinue int32, cycleDate []string, err error) {
player, tz := s.GetPlayer(), s.GetTz()
m := s.GetDao().FissionSignDay
list, err := m.WithContext(s.Ctx).Where(
m.UserID.Eq(player.UserID),
).Find()
if err != nil {
err = s.BE().MysqlError(err)
return
}
signMap := make(map[string]struct{})
for _, day := range list {
signMap[day.SignDate] = struct{}{}
}
var start = player.SignStart
if start == 0 {
start = time.Now().Unix()
}
sdn := timez.GMTCarbon(tz.GetTz()).Now()
sd := utils.BetweenDay(timez.GMTCarbon(tz.GetTz()).CreateFromTimestamp(start).StartOfDay(), sdn)
cycleStart, _ := timez.GMTTimeStart(tz.GetTz(), player.CycleStart)
cycle := cycleStart.Format(timez.ShortDateOnly)
for _, day := range sd {
_, ok := signMap[day]
if !ok {
continueNum = 0
continue
}
if day >= cycle {
cycleDate = append(cycleDate, day)
}
continueNum++
if continueNum > maxContinue {
maxContinue = continueNum
}
}
return
}
// 获得奖品通用处理
func (s *Sign) _signReward(sign *model.FissionSignDay, rewards ...*model.FissionCampaignReward) (signReward *SignReward, recordId []int64, err error) {
if len(rewards) == 0 {
return
}
var rewardIds []int64
var add []*award.RewardOne
for _, reward := range rewards {
rewardIds = append(rewardIds, reward.ID)
add = append(add, &award.RewardOne{
Reward: reward,
})
}
records, async, err := award.NewAwardReward(s.Ctx, s.GetDao(), s.cpn.ProjectID, s.user).Batch(add, false)
if err != nil {
return
}
stock, err := stocks.NewCampaignReward(s.Ctx, rewardIds...)
if err != nil {
return
}
player := s.GetPlayer()
var addLog []*model.FissionSignLog
for _, v := range records {
recordId = append(recordId, v.Record.ID)
val, _ := jsonm.MarshalToString(mtype.SignLogVal{
TOffset: s.GetTz().GetTz(),
SignDate: sign.SignDate,
RecordId: v.Record.ID,
RewardId: v.Record.RewardID,
})
addLog = append(addLog, withdefault.FissionSignLog(&model.FissionSignLog{
UserID: player.UserID,
CampaignID: player.CampaignID,
SignID: player.ID,
Type: mtype.FissionSignLogTypeGetReward,
Source: utils.Ternary[string](sign.SignType == mtype.FissionSignTypeRemedy, mtype.FissionSignLogSourceCardReward, mtype.FissionSignLogSourceSignReward),
Val: val,
}))
_ = stock.AddUseStock(1, v.Record.RewardID)
}
err = s._addLog(addLog...)
if err != nil {
return
}
record, info := records[0].Record, records[0].Info
signReward = &SignReward{
RewardTitle: info.Title,
RewardNum: record.RewardNum,
ImgUrl: info.ImgUrl,
RewardId: record.RewardID,
ReceiveType: info.ReceiveType,
KvData: make([]*mtype.KV, 0, len(info.KvData)),
Contact: info.Contact,
}
for _, v := range info.KvData {
signReward.KvData = append(signReward.KvData, &mtype.KV{
Key: v.Key,
Value: v.Value,
})
}
s.AddOkDone(async...)
return
}
const (
signNextDay = "next_day" // 下次签到日期
signLastDay = "last_day" // 最后签到日期
signContinueNum = "continue_num" // 连续签到天数
signMaxContinue = "max_continue" // 最大连续签到天数
signCycleDay = "cycle_day" // 周期内签到天
)
func (s *Sign) _cycleData(refresh bool) (data *cachecurd.HMap, err error) {
if s.cacheData != nil && !refresh {
return s.cacheData, nil
}
player, tz := s.GetPlayer(), s.GetTz()
dayStart, _ := timez.GMTTimeStart(tz.GetTz())
yesterdayStart := dayStart.AddDate(0, 0, -1)
data, err = cachecurd.NewHMap(s.Ctx, s.signDataKey, cachecurd.SetHMapExp(2*consts.TimeDay))
if err != nil {
return
}
data, err = data.CustomBuild(func() ([]string, error) {
var values []string
today := dayStart.Format(timez.ShortDateOnly)
yesterday := yesterdayStart.Format(timez.ShortDateOnly)
m := s.GetDao().FissionSignDay
sign, err1 := m.WithContext(s.Ctx).Where(m.UserID.Eq(player.UserID), m.SignDate.In(today, yesterday)).Find()
if err1 != nil {
return nil, err1
}
var todaySign, yesterdaySign *model.FissionSignDay
for _, v := range sign {
if v.SignDate == today {
todaySign = v
} else if v.SignDate == yesterday {
yesterdaySign = v
}
}
if todaySign == nil {
values = append(values, signNextDay, fmt.Sprint(dayStart.Unix()))
} else {
values = append(values, signNextDay, fmt.Sprint(dayStart.AddDate(0, 0, 1).Unix()))
}
if yesterdaySign == nil {
values = append(values, signLastDay, "0")
} else {
values = append(values, signLastDay, fmt.Sprint(yesterdayStart.Unix()))
}
// 连续签到天数
continueNum, maxContinue, cycleDate, err1 := s._continueNum()
if err1 != nil {
return nil, err1
}
values = append(values, signContinueNum, fmt.Sprint(continueNum), signMaxContinue, fmt.Sprint(maxContinue), signCycleDay, strings.Join(cycleDate, consts.JoinComma))
return values, nil
})
if err != nil {
return
}
s.cacheData = data
return
}
// 是否已经签到了
func (s *Sign) _hasSign(today time.Time) (exist bool, err error) {
shortDay := today.Format(timez.ShortDateOnly)
player := s.GetPlayer()
key := ckey.FissionCampaignSignState(player.CampaignID, shortDay)
if app.Redis.Cache.HExists(s.Ctx, key, fmt.Sprint(player.UserID)).Val() {
exist = true
return
}
m := s.GetDao().FissionSignDay
sign, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(player.UserID), m.SignDate.Eq(shortDay)).Take()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
return sign != nil, nil
}
// 签到某一天
func (s *Sign) _sign(signTime time.Time, useCard bool) (status int32, signDay *model.FissionSignDay, err error) {
shortDay := signTime.Format(timez.ShortDateOnly)
player, tz, conf := s.GetPlayer(), s.GetTz(), s.GetConf()
// 是否已经签到了
exist, err := s._hasSign(signTime)
if err != nil {
return
}
if exist {
status = SignStatusDone
return
}
var value = xid.New().String()
var mapKey = fmt.Sprint(player.UserID)
var key = ckey.FissionCampaignSignState(player.CampaignID, shortDay)
// 预防一些错误,要回滚缓存
s.AddErrDone(func() {
v := app.Redis.Cache.HGet(s.Ctx, key, mapKey).Val()
if v == value {
app.Redis.Cache.HDel(s.Ctx, key, mapKey)
}
})
if !app.Redis.Cache.HSetNX(s.Ctx, key, mapKey, value).Val() {
status = SignStatusDone
return
}
// 延长缓存时间
app.Redis.Cache.ExpireAt(s.Ctx, key, signTime.AddDate(0, 0, 1))
// 签到记录
signDay = withdefault.FissionSignDay(&model.FissionSignDay{
UserID: player.UserID,
CampaignID: player.CampaignID,
SignDate: shortDay,
SignType: utils.Ternary[int8](useCard, mtype.FissionSignTypeOk, mtype.FissionSignTypeRemedy),
RecordIds: "",
})
if err = s.GetDao().FissionSignDay.WithContext(s.Ctx).Create(signDay); err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
status = SignStatusFail
return
}
err = s.BE().MysqlError(err)
return
}
// 加日志
if useCard {
val, _ := jsonm.ToObjString(mtype.SignLogVal{
TOffset: tz.GetTz(),
SignDate: shortDay,
CardNum: 1,
})
err = s._addLog(withdefault.FissionSignLog(&model.FissionSignLog{
UserID: player.UserID,
CampaignID: player.CampaignID,
SignID: player.ID,
Type: mtype.FissionSignLogTypeSignCard,
Source: mtype.FissionSignLogSourceUseCard,
Val: val,
}))
if err != nil {
return
}
} else {
val, _ := jsonm.ToObjString(mtype.SignLogVal{
TOffset: tz.GetTz(),
SignDate: shortDay,
})
err = s._addLog(withdefault.FissionSignLog(&model.FissionSignLog{
UserID: player.UserID,
CampaignID: player.CampaignID,
SignID: player.ID,
Type: mtype.FissionSignLogTypeSign,
Source: utils.Ternary[string](conf.SignType == mtype.FissionCampaignSignTypeCycle, mtype.FissionSignLogSourceManualSign, mtype.FissionSignLogSourceAutoSign),
Val: val,
}))
if err != nil {
return
}
}
s.AddOkDone(func() {
// 新版报表数据
if s.cpn.ReportType == mtype.FissionCampaignReportTypeNew {
fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).Sign(time.Now().Unix(), tz.GetTz(), signTime.Format(timez.ShortDateOnly))
}
})
s.upPlayer = append(s.upPlayer, s.GetDao().FissionSign.SignDay.Add(1))
return
}
// 当天签到(旧)
func (s *Sign) _todaySign(today time.Time) (status int32, reward *SignReward, err error) {
status, signDay, err := s._sign(today, false)
if err != nil || status != SignStatusOk {
return
}
reward, err = s._signOk(signDay)
if err != nil {
return
}
return
}
// 补签卡签到(旧)
func (s *Sign) _cardSign(signDate time.Time) (status int32, reward *SignReward, err error) {
player, tz := s.GetPlayer(), s.GetTz()
if player.CardNum == 0 {
status = SignStatusMissCard
return
}
shortDay := signDate.Format(timez.ShortDateOnly)
m := s.GetDao().FissionSignDay
sign, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(player.UserID), m.SignDate.Eq(shortDay)).Take()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
if sign != nil {
status = SignStatusDone
return
}
signDay := withdefault.FissionSignDay(&model.FissionSignDay{
UserID: player.UserID,
CampaignID: player.CampaignID,
SignDate: shortDay,
SignType: mtype.FissionSignTypeOk,
RecordIds: "",
})
if err = m.WithContext(s.Ctx).Create(signDay); err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
status = SignStatusFail
return
}
err = s.BE().MysqlError(err)
return
}
status = SignStatusCard
playerM := s.GetDao().FissionSign
s.upPlayer = append(s.upPlayer, playerM.CardNum.Sub(1))
// 加日志
val, _ := jsonm.ToObjString(mtype.SignLogVal{
TOffset: tz.GetTz(),
SignDate: shortDay,
})
err = s._addLog(withdefault.FissionSignLog(&model.FissionSignLog{
UserID: player.UserID,
CampaignID: player.CampaignID,
SignID: player.ID,
Type: mtype.FissionSignLogTypeSignCard,
Source: mtype.FissionSignLogSourceUseCard,
Val: val,
}))
if err != nil {
return
}
reward, err = s._signOk(signDay)
if err != nil {
return
}
if s.cpn.ReportType == mtype.FissionCampaignReportTypeNew {
fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).UseProp(time.Now().Unix(), consts.PropNameSignCard)
}
return
}
// 普通签到版(旧)
func (s *Sign) _normalSignBoard(pageType int32, nowMouth string) (signBoard *SignBoard, err error) {
cpn, conf, player, tz := s.cpn, s.conf, s.GetPlayer(), s.GetTz()
// 签到开始和结束时间
var now = timez.GMTCarbon(tz.GetTz()).Now()
var start carbon.Carbon
var end = timez.GMTCarbon(tz.GetTz()).CreateFromTimestamp(cpn.TEnd).EndOfDay()
if cpn.Status == mtype.FissionCampaignStatusWait {
start = timez.GMTCarbon(tz.GetTz()).CreateFromTimestamp(cpn.TStart)
} else if player.SignStart != 0 {
start = timez.GMTCarbon(tz.GetTz()).CreateFromTimestamp(player.SignStart)
} else {
start = now
}
start = start.StartOfDay()
// 奖品日期
dayReward, lastReward, err := s._dayReward(start, end, player.LoopNum)
if err != nil {
return
}
// 签到数据
signMap, rewardIdMap, openMap, lastMiss, err := s._dataMap(start, now, dayReward, lastReward)
if err != nil {
return
}
// 本周开始和周结束
wStart, wEnd := now.StartOfWeek(), now.EndOfWeek()
// 某个呀的签到板
var mStart carbon.Carbon
switch pageType {
case 1:
mStart = timez.GMTCarbon(tz.GetTz()).ParseByFormat(nowMouth, "Y-n").AddMonth()
case 2:
mStart = timez.GMTCarbon(tz.GetTz()).ParseByFormat(nowMouth, "Y-n").SubMonth()
}
if mStart.IsInvalid() {
mStart = now
}
// 完整月
mStart = mStart.StartOfMonth()
mEnd := mStart.EndOfMonth()
signBoard = &SignBoard{
SignDate: nil,
NowMouth: mStart.ToFormatString("Y-n"),
}
// 之前的时间
nw := mStart.Week()
for i := nw; i > 0; i-- {
s._appendSignDate(mStart.SubDays(i), start, end, wStart, wEnd, mStart, mEnd, rewardIdMap, signMap, openMap, conf.HasCard, lastMiss, signBoard)
}
begin := mStart
for {
if begin.Gte(mEnd) {
break
}
s._appendSignDate(begin, start, end, wStart, wEnd, mStart, mEnd, rewardIdMap, signMap, openMap, conf.HasCard, lastMiss, signBoard)
begin = begin.AddDay()
}
ew := begin.Week()
for i := 0; i < 7-ew; i++ {
s._appendSignDate(begin.AddDays(i), start, end, wStart, wEnd, mStart, mEnd, rewardIdMap, signMap, openMap, conf.HasCard, lastMiss, signBoard)
}
return
}
// 普通签到获得奖品逻辑(旧)
func (s *Sign) _normalReward(sign *model.FissionSignDay, totalNum int32) (signReward *SignReward, recordId []int64, err error) {
cm := cmm.NewCMM(s.Ctx, s.GetDao())
rewards, err := cm.UserAppReward(s.user)
if err != nil {
return
}
if len(rewards) == 0 {
return
}
reward := rewards[0]
num := totalNum / reward.SignDay
if num == 0 {
return
}
player := s.GetPlayer()
m := s.GetDao().FissionRewardRecord.SubTable(player.ID)
count, err := m.WithContext(s.Ctx).Where(
m.UserID.Eq(player.ID),
m.RewardID.Eq(reward.ID),
m.RewardType.Eq(mtype.FissionCampaignRewardTypeApp),
).Count()
if err != nil {
err = s.BE().MysqlError(err)
return
}
can := int64(num) - count
if can == 0 {
return
}
var addRewards []*model.FissionCampaignReward
for i := can; i > 0; i-- {
addRewards = append(addRewards, reward)
}
return s._signReward(sign, addRewards...)
}
// 阶梯签到获得奖品逻辑(旧)
func (s *Sign) _stepReward(sign *model.FissionSignDay, totalNum int32) (signReward *SignReward, recordId []int64, err error) {
cm := cmm.NewCMM(s.Ctx, s.GetDao())
rewards, err := cm.UserAppReward(s.user, s.GetDao().FissionCampaignReward.Step)
if err != nil {
return
}
if len(rewards) == 0 {
return
}
player, conf := s.GetPlayer(), s.GetConf()
var stepMap = make(map[int32]*model.FissionCampaignReward)
for _, v := range rewards {
stepMap[v.Step] = v
}
m := s.GetDao().FissionRewardRecord.SubTable(player.CampaignID)
list, err := m.WithContext(s.Ctx).Where(
m.UserID.Eq(player.UserID),
m.RewardType.Eq(mtype.FissionCampaignRewardTypeApp),
).Find()
if err != nil {
err = s.BE().MysqlError(err)
return
}
if conf.IsLoop == mtype.No {
for _, v := range list {
delete(stepMap, v.Step)
}
var addRewards []*model.FissionCampaignReward
for _, reward := range stepMap {
if totalNum < reward.SignDay {
continue
}
addRewards = append(addRewards, reward)
}
signReward, recordId, err = s._signReward(sign, addRewards...)
if err != nil {
return
}
return
}
l := len(rewards)
lastReward := rewards[l-1]
signDay := 1 + player.SignDay - player.LoopNum*lastReward.SignDay
nextStep := player.NextStep
if nextStep == 0 {
nextStep = 1
}
if signDay < totalNum {
totalNum = signDay
}
var addLoopNum int32
var addRewards []*model.FissionCampaignReward
for {
stepReward := stepMap[nextStep]
if totalNum < stepReward.SignDay {
break
}
if stepReward.ID == lastReward.ID {
addLoopNum++
nextStep = 1
totalNum -= lastReward.SignDay
} else {
nextStep++
}
addRewards = append(addRewards, stepReward)
}
signReward, recordId, err = s._signReward(sign, addRewards...)
if err != nil {
return
}
playerM := s.GetDao().FissionSign
s.upPlayer = append(s.upPlayer, playerM.LoopNum.Add(addLoopNum), playerM.NextStep.Value(nextStep))
return
}
type signState struct {
RewardId int64
SignDay int32
addDay int
}
// 签到板奖品分布(旧)
func (s *Sign) _dayReward(start, end carbon.Carbon, loopNum int32) (dayReward []*signState, lastReward *model.FissionCampaignReward, err error) {
conf := s.GetConf()
rewards, err := cmm.NewCMM(s.Ctx, s.GetDao()).UserAppReward(s.user)
if err != nil {
return
}
if len(rewards) == 0 {
err = workerr.NotFound("not found reward")
return
}
l := len(rewards)
lastReward = rewards[l-1]
// 非阶梯一个奖品,多次获得
if conf.SignType == mtype.FissionCampaignSignTypeNormal {
reward := rewards[0]
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
if reward == nil {
err = workerr.NotFound("not found reward")
return
}
diff := start.DiffAbsInDays(end) + 1
times := int(diff / int64(reward.SignDay))
dayReward = make([]*signState, times)
for i := 0; i < times; i++ {
day := int32(i+1)*reward.SignDay - 1
dayReward[i] = &signState{
RewardId: reward.ID,
addDay: int(day),
SignDay: reward.SignDay,
}
}
return
}
// 活动奖品,阶梯多奖品
// 不支持阶梯循环
if conf.IsLoop == mtype.No {
dayReward = make([]*signState, 0, len(rewards))
for _, v := range rewards {
dayReward = append(dayReward, &signState{
RewardId: v.ID,
addDay: int(v.SignDay - 1),
SignDay: v.SignDay,
})
}
return
}
doLoop := int(loopNum + 1)
for i := 0; i < doLoop; i++ {
for _, v := range rewards {
dayReward = append(dayReward, &signState{
RewardId: v.ID,
addDay: int(v.SignDay-1) + int(lastReward.SignDay)*i,
SignDay: v.SignDay,
})
}
}
return
}
// 签到版基础数据(旧)
func (s *Sign) _dataMap(start, now carbon.Carbon, dayReward []*signState, lastReward *model.FissionCampaignReward,
) (signMap map[string]int8, rewardIdMap map[string]int64, openMap map[int64]int, lastMiss string, err error) {
player, conf, tz := s.GetPlayer(), s.GetConf(), s.GetTz()
// 签到的所有数据
sdm := s.GetDao().FissionSignDay
sd, err := sdm.WithContext(s.Ctx).
Select(sdm.SignDate, sdm.RecordIds).
Where(
sdm.UserID.Eq(player.UserID),
sdm.SignDate.Gte(start.ToShortDateString()),
sdm.SignDate.Lte(now.ToShortDateString()),
).Order(sdm.SignDate).Find()
if err != nil {
err = s.BE().MysqlError(err)
return
}
sdMap := make(map[string][]int64)
for _, day := range sd {
sdMap[day.SignDate] = day.GetRecordIds()
}
rewardIdMap = make(map[string]int64)
var recordIds []int64
signMap = make(map[string]int8)
se := utils.BetweenDay(start, now)
for _, day := range se {
idv, ok := sdMap[day]
if ok {
if len(idv) > 0 {
recordIds = append(recordIds, idv...)
}
signMap[day] = mtype.FissionSignTypeOk
continue
}
signMap[day] = mtype.FissionSignTypeMiss
lastMiss = day
missDay := timez.GMTCarbon(tz.GetTz()).ParseByFormat(day, "Ymd")
if conf.IsContinue == mtype.Yes {
var idx int32
for k, reward := range dayReward {
rewardDay := start.AddDays(reward.addDay)
if missDay.Gt(rewardDay) {
continue
}
switch conf.SignType {
case mtype.FissionCampaignSignTypeStep:
dayReward[k].addDay = int(missDay.AddDays(int(reward.SignDay)).DiffAbsInDays(start)) + int(idx*lastReward.SignDay)
if reward.RewardId == lastReward.ID {
idx++
}
case mtype.FissionCampaignSignTypeNormal:
idx++
dayReward[k].addDay = int(missDay.AddDays(int(idx * reward.SignDay)).DiffAbsInDays(start))
}
}
} else {
for k, reward := range dayReward {
if missDay.Gt(start.AddDays(reward.addDay)) {
continue
}
dayReward[k].addDay++
}
}
}
for _, state := range dayReward {
rewardIdMap[start.AddDays(state.addDay).ToShortDateString()] = state.RewardId
}
if len(recordIds) == 0 {
return
}
// 领奖数据
openMap = make(map[int64]int)
recordM := s.GetDao().FissionRewardRecord.SubTable(player.CampaignID)
records, err := recordM.WithContext(s.Ctx).Where(recordM.ID.In(recordIds...)).Find()
if err != nil {
err = s.BE().MysqlError(err)
return
}
for _, record := range records {
openMap[record.RewardID]++
}
return
}
// 签到板追加数据(旧)
func (s *Sign) _appendSignDate(dt, start, end, wStart, wEnd, mStart, mEnd carbon.Carbon, rewardIdMap map[string]int64, signMap map[string]int8, openMap map[int64]int, hashCard int8, lastMiss string, res *SignBoard) {
dateStr := dt.ToShortDateString()
one := &SignBoardOne{
Y: fmt.Sprint(dt.Year()),
M: fmt.Sprint(dt.Month()),
D: fmt.Sprint(dt.Day()),
SignIn: 0,
Ban: dt.Lt(start) || dt.Gt(end),
RewardId: rewardIdMap[dateStr],
Open: false,
NowWeek: dt.Gte(wStart) && dt.Lte(wEnd),
NowDay: dt.IsToday(),
LastMiss: lastMiss != "" && lastMiss == dateStr,
NowMouth: dt.Gte(mStart) && dt.Lte(mEnd),
}
if st, ok := signMap[dateStr]; ok {
one.SignIn = utils.Ternary[int32](st == mtype.FissionSignTypeMiss, 2, 1)
one.Ban = !one.Ban && st == mtype.FissionSignTypeMiss && hashCard == mtype.No // 不能补签禁用
}
if open, ok := openMap[one.RewardId]; ok {
one.Open = open > 0
openMap[one.RewardId]--
}
res.SignDate = append(res.SignDate, one)
}
// 签到成功 (旧)
func (s *Sign) _signOk(signDay *model.FissionSignDay) (reward *SignReward, err error) {
continueNum, maxContinue, _, err := s._continueNum()
if err != nil {
return
}
player, conf := s.GetPlayer(), s.GetConf()
playerM := s.GetDao().FissionSign
s.upPlayer = append(s.upPlayer,
playerM.SignContinue.Value(continueNum),
playerM.MaxContinue.Value(maxContinue),
)
if player.SignStart == 0 {
s.upPlayer = append(s.upPlayer, playerM.SignStart.Value(time.Now().Unix()))
}
var totalNum = player.SignDay + 1
if conf.IsLoop == mtype.Yes {
totalNum = continueNum
} else if conf.IsContinue == mtype.Yes {
totalNum = maxContinue
}
var recordIds []int64
switch conf.SignType {
case mtype.FissionCampaignSignTypeNormal:
reward, recordIds, err = s._normalReward(signDay, totalNum)
if err != nil {
return
}
case mtype.FissionCampaignSignTypeStep:
reward, recordIds, err = s._stepReward(signDay, totalNum)
if err != nil {
return
}
}
// 绑定数据
if len(recordIds) > 0 {
sdm := s.GetDao().FissionSignDay
_, err = sdm.WithContext(s.Ctx).Where(sdm.ID.Eq(signDay.ID)).UpdateSimple(sdm.RecordIds.Value(utils.Join(recordIds, consts.JoinComma)))
if err != nil {
err = s.BE().MysqlError(err)
return
}
}
// 更新玩家数据
_, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(s.upPlayer...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
// 更新统计数据
statM := s.GetDao().FissionUserStat
_, err = statM.WithContext(s.Ctx).Where(statM.UserID.Eq(player.UserID)).UpdateSimple(statM.SignNum.Add(1), statM.SignRewardNum.Add(int64(len(recordIds))))
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}