|
|
package cachecurd
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
"joybuywork/internal/app"
|
|
|
"joybuywork/internal/repository/dao"
|
|
|
"joybuywork/internal/repository/model"
|
|
|
"joybuywork/internal/repository/mtype"
|
|
|
"joybuywork/internal/utils/lockdo"
|
|
|
"fmt"
|
|
|
"time"
|
|
|
|
|
|
grds "github.com/go-redis/redis/v8"
|
|
|
"gorm.io/gen"
|
|
|
)
|
|
|
|
|
|
type RankOption func(rank *Ranker)
|
|
|
|
|
|
func SetChildBuild(cpnId int64, childType int8) RankOption {
|
|
|
return func(rank *Ranker) {
|
|
|
rank.build = &RankBuild{
|
|
|
CpnId: cpnId,
|
|
|
ChildType: childType,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func SetRankExp(exp time.Duration) RankOption {
|
|
|
return func(rank *Ranker) {
|
|
|
rank.exp = exp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
type Ranker struct {
|
|
|
ctx context.Context
|
|
|
cli *grds.Client
|
|
|
key string
|
|
|
|
|
|
build *RankBuild
|
|
|
exp time.Duration
|
|
|
}
|
|
|
|
|
|
type RankBuild struct {
|
|
|
CpnId int64
|
|
|
ChildType int8
|
|
|
}
|
|
|
|
|
|
func NewRanker(ctx context.Context, key string, opts ...RankOption) (*Ranker, error) {
|
|
|
rank := &Ranker{
|
|
|
ctx: ctx,
|
|
|
cli: app.Redis.Cache,
|
|
|
key: key,
|
|
|
exp: time.Hour * 24,
|
|
|
}
|
|
|
for _, opt := range opts {
|
|
|
opt(rank)
|
|
|
}
|
|
|
|
|
|
err := rank.Build()
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
return rank, nil
|
|
|
}
|
|
|
|
|
|
func (s *Ranker) Build() (err error) {
|
|
|
if s.build == nil {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
if s.build.ChildType == mtype.FissionCampaignChildTypeInvite {
|
|
|
return s.invite(s.build)
|
|
|
}
|
|
|
|
|
|
return
|
|
|
}
|
|
|
|
|
|
type RankMember struct {
|
|
|
UserId string
|
|
|
Score float64
|
|
|
}
|
|
|
|
|
|
// GetRank 获取排名数据
|
|
|
func (s *Ranker) GetRank(num int64) (members []*RankMember, err error) {
|
|
|
data, err := s.cli.ZRevRangeWithScores(s.ctx, s.key, 0, num-1).Result()
|
|
|
if err != nil {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
members = make([]*RankMember, len(data))
|
|
|
for i, v := range data {
|
|
|
members[i] = &RankMember{
|
|
|
UserId: fmt.Sprintf("%v", v.Member),
|
|
|
Score: v.Score,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// InitMember 初始化一个用户
|
|
|
func (s *Ranker) InitMember(member string, asc int64) (err error) {
|
|
|
return s.SetMembers([]*grds.Z{{
|
|
|
Score: 1 + s.timeAsc(asc),
|
|
|
Member: member,
|
|
|
}})
|
|
|
}
|
|
|
|
|
|
// SetMembers 设置用户
|
|
|
func (s *Ranker) SetMembers(members []*grds.Z) (err error) {
|
|
|
return s.cli.ZAdd(s.ctx, s.key, members...).Err()
|
|
|
}
|
|
|
|
|
|
// GetMemberRank 获取用户当前排名,如果返回9,是第10名的意思
|
|
|
func (s *Ranker) GetMemberRank(member string) (rank int64, err error) {
|
|
|
return s.cli.ZRevRank(s.ctx, s.key, member).Result()
|
|
|
}
|
|
|
|
|
|
// Incr 追加分数
|
|
|
func (s *Ranker) Incr(member string) (err error) {
|
|
|
return s.IncrBy(1, member)
|
|
|
}
|
|
|
|
|
|
// IncrBy 追加特定分数
|
|
|
func (s *Ranker) IncrBy(increment float64, member string) (err error) {
|
|
|
return s.cli.ZIncrBy(s.ctx, s.key, increment, member).Err()
|
|
|
}
|
|
|
|
|
|
// 子活动排行榜初始化
|
|
|
func (s *Ranker) invite(build *RankBuild) (err error) {
|
|
|
return lockdo.ExistDo(s.ctx, s.key, func(exist bool) (err error) {
|
|
|
if exist {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
m := dao.FissionChildUser
|
|
|
var list []*model.FissionChildUser
|
|
|
err = m.WithContext(s.ctx).Where(m.CampaignID.Eq(build.CpnId), m.InviteNum.Gt(0)).FindInBatches(&list, 200, func(tx gen.Dao, batch int) error {
|
|
|
var members = make([]*grds.Z, 0, len(list))
|
|
|
for _, v := range list {
|
|
|
members = append(members, &grds.Z{
|
|
|
Score: float64(v.InviteNum) + s.timeAsc(v.CreatedAt),
|
|
|
Member: fmt.Sprint(v.UserID),
|
|
|
})
|
|
|
}
|
|
|
return s.SetMembers(members)
|
|
|
})
|
|
|
|
|
|
s.setExp()
|
|
|
|
|
|
return
|
|
|
})
|
|
|
}
|
|
|
|
|
|
func (s *Ranker) setExp() {
|
|
|
s.cli.Expire(s.ctx, s.key, s.exp)
|
|
|
}
|
|
|
|
|
|
func (s *Ranker) timeAsc(asc int64) float64 {
|
|
|
return 1 - float64(asc)/1e13
|
|
|
}
|