排行榜

This commit is contained in:
xiabin 2025-01-24 18:36:46 +08:00
parent cf601f9466
commit 7f7a881d46
10 changed files with 342 additions and 22 deletions

37
docker-compose-dev.yaml Normal file
View File

@ -0,0 +1,37 @@
version: "3"
services:
mysql:
image: mysql
container_name: mysql
restart: always
command: [
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_general_ci',
'--explicit_defaults_for_timestamp=true',
'--lower_case_table_names=1'
]
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: youtu!0113
MYSQL_INITDB_SKIP_TZINFO: "Asia/Shanghai"
MYSQL_DATABASE: ecpm
volumes:
##初始化的脚本初始化我们存放的init.sql文件
- ./sql:/docker-entrypoint-initdb.d/
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-uyoutu", "-pyoutu!0113" ]
interval: 6s
timeout: 5s
retries: 10
#network_mode: host
redis:
image: redis
restart: always
hostname: redis
container_name: redis
privileged: true
ports:
- "6379:6379"
environment:
TZ: Asia/Shanghai

View File

@ -20,6 +20,7 @@ type Config struct {
DWCache struct {
Host string // 缓存服务器地址
Password string // 缓存服务器密码
IdleTimeout int
} // 抖音微信缓存配置

View File

@ -2,6 +2,7 @@ package game
import (
"context"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/logic/rankings"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/svc"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/types"
@ -32,28 +33,40 @@ func (l *RankingListLogic) RankingList(req *types.RankingListRequest) (resp *typ
l.Logger.Debugf("at: %+v", at)
l.Logger.Debugf("req: %+v", req)
resp.RankingData, err = l.svcCtx.GameScore.GetRankList(l.ctx, at.AppId, req.Type, req.PageBase)
cacheData, err := l.svcCtx.RedisRanking.GetList(l.ctx, rankings.GetRankingsCacheKey(at.AppId, req.Type), 0, 99)
if err != nil {
return nil, err
}
var (
flag bool
userRank types.RankingData
)
for i := range resp.RankingData {
if resp.RankingData[i].UserId == at.UserId {
resp.RankingData[i].Self = true
userRank = resp.RankingData[i]
l.Logger.Debugf("userRank: %+v", userRank)
resp.RankingData = append(resp.RankingData, userRank)
var flag bool
resp.RankingData = make([]types.RankingData, len(cacheData))
var userRank types.RankingData
for i, datum := range cacheData {
if id, ok := datum.Member.(uint64); ok {
//查询用户数据FindOne带缓存
user, err := l.svcCtx.AppUser.FindOne(l.ctx, id)
if err != nil {
return nil, err
}
data := types.RankingData{
Nickname: user.Nickname,
Avatar: user.Avatar,
Score: uint32(datum.Score),
Rank: uint32(i), //todo
Self: user.Id == at.UserId,
}
if user.Id == at.UserId {
flag = true
break
userRank = data
}
resp.RankingData = append(resp.RankingData, data)
}
}
if !flag {
userRank, err := l.svcCtx.GameScore.GetUserRank(l.ctx, at.AppId, at.UserId, req.Type)
userRank, err = l.svcCtx.GameScore.GetUserRank(l.ctx, at.AppId, at.UserId, req.Type)
if err != nil {
return nil, err

View File

@ -3,7 +3,9 @@ package game
import (
"context"
"errors"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/logic/rankings"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/model"
"github.com/redis/go-redis/v9"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/svc"
@ -55,6 +57,15 @@ func (l *RankingSetScoreLogic) RankingSetScore(req *types.SetUserGameScoreReques
err = l.svcCtx.GameScore.CreateScore(l.ctx, oldScore)
}
if err != nil {
return
}
// 更新排行榜
l.svcCtx.RedisRanking.SetList(l.ctx, rankings.GetRankingsCacheKey(at.AppId, req.Type), redis.Z{
Member: at.UserId,
Score: float64(req.Score),
})
l.Logger.Debugf("GameScore: %+v", oldScore)
return

View File

@ -0,0 +1,45 @@
package rankings
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
// Ranking 排行榜结构体
type Ranking struct {
c *redis.Client
}
const EcpmRankingsListPrefix = "ecpm:rankings:"
// NewRanking 创建一个新的排行榜实例
func NewRanking(c *redis.Client) *Ranking {
return &Ranking{
c: c,
}
}
func GetRankingsCacheKey(appId, t uint64) string {
return fmt.Sprintf("%sappId:%d:type:%d", EcpmRankingsListPrefix, appId, t)
}
// SetList 向排行榜中添加成员及其分数
func (r *Ranking) SetList(ctx context.Context, key string, data ...redis.Z) {
r.c.ZAdd(ctx, EcpmRankingsListPrefix+key, data...)
}
// GetList 获取排行榜,按照分数从高到低排序
func (r *Ranking) GetList(ctx context.Context, key string, start, stop int64) (data []redis.Z, err error) {
return r.c.ZRevRangeWithScores(ctx, EcpmRankingsListPrefix+key, start, stop).Result()
}
// GetRank 获取指定成员在排行榜中的排名(排名从 0 开始,分数越高排名越靠前)
func (r *Ranking) GetRank(ctx context.Context, key, member string) (rank int64, err error) {
return r.c.ZRevRank(ctx, EcpmRankingsListPrefix+key, member).Result()
}
// GetScore 获取指定成员在排行榜中的分数
func (r *Ranking) GetScore(ctx context.Context, key, member string) (score float64, err error) {
return r.c.ZScore(ctx, EcpmRankingsListPrefix+key, member).Result()
}

View File

@ -5,9 +5,11 @@ import (
"encoding/json"
"errors"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/config"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/logic/rankings"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/model"
helper "gitea.youtukeji.com.cn/youtu/openapi-helper"
"github.com/golang-jwt/jwt/v4"
"github.com/redis/go-redis/v9"
redisCache "github.com/silenceper/wechat/v2/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"go.uber.org/zap"
@ -25,6 +27,7 @@ type ServiceContext struct {
DouyinCli *helper.DouYinOpenApiClient
WechatCli *helper.WechatApi
ZapLogger *zap.Logger
RedisRanking *rankings.Ranking
}
func NewServiceContext(c config.Config) *ServiceContext {
@ -35,15 +38,26 @@ func NewServiceContext(c config.Config) *ServiceContext {
AppAccount: model.NewAppAccountModel(sqlx.NewMysql(c.DB.DataSource), c.Cache),
}
//初始化redis client
redisClient := redis.NewClient(&redis.Options{
Addr: c.DWCache.Host,
Password: c.DWCache.Password, // 没有密码,默认值
})
//初始化排行榜对象
svc.RedisRanking = rankings.NewRanking(redisClient)
dwCache := redisCache.NewRedis(context.Background(), &redisCache.RedisOpts{Host: c.DWCache.Host, IdleTimeout: c.DWCache.IdleTimeout})
svc.DouyinCli = helper.NewDouYinOpenApiClient()
svc.WechatCli = helper.NewWechatOpenApiClient()
//查询小程序配置(抖音&微信)
result, err := svc.AppAccount.FindAll(context.Background())
if err != nil {
panic(err)
}
//小程序配置
for _, v := range *result {
if v.Type == 0 {
svc.DouyinCli.NewAndStoreDouYinOpenApi(v.AppID, v.Secret, v.EcpmValue.V, v.EcpmView.V, dwCache)
@ -52,6 +66,24 @@ func NewServiceContext(c config.Config) *ServiceContext {
}
}
//获取所有排行榜
rankList, err := svc.GameScore.FindAllRankList(context.Background())
if err != nil {
return nil
}
//添加排行榜数据
for s, scores := range rankList {
data := make([]redis.Z, len(scores))
for _, score := range scores {
data = append(data, redis.Z{
Score: float64(score.Score),
Member: score.AppUserId,
})
}
svc.RedisRanking.SetList(context.Background(), s, data...)
}
//初始化一个zap日志对象用于写入ecpm日志
svc.ZapLogger = zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(zapcore.EncoderConfig{

View File

@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/logic/rankings"
"sync"
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/types"
@ -29,6 +30,8 @@ type (
GetUserRank(ctx context.Context, appId uint64, userId uint64, t uint64) (types.RankingData, error)
FindUserScore(ctx context.Context, appId, userId, t uint64) (*GameScore, error)
FindAllRankList(ctx context.Context) (resp map[string][]GameScore, err error)
UpdateScore(ctx context.Context, data *GameScore) error
CreateScore(ctx context.Context, data *GameScore) error
}
@ -66,6 +69,32 @@ func (m *customGameScoreModel) userRankCacheKey(userId, appId, t uint64) string
return fmt.Sprintf("%srank:userId:%d:appId:%v:t:%d", cacheEcpmGameScorePrefix, userId, appId, t)
}
// FindAllRankList 查询所有排行榜
func (m *customGameScoreModel) FindAllRankList(ctx context.Context) (resp map[string][]GameScore, err error) {
list := make([]GameScore, 0)
resp = make(map[string][]GameScore)
err = m.QueryRowNoCacheCtx(ctx, &list, "select DISTINCT app_account,t from game_score")
if err != nil && !errors.Is(err, sqlx.ErrNotFound) {
return nil, err
}
for _, rank := range list {
data, err := m.FindAllScore(ctx, rank.AppAccount, rank.T)
if err != nil {
return nil, err
}
if len(data) != 0 {
resp[rankings.GetRankingsCacheKey(rank.AppAccount, rank.T)] = data
}
}
return
}
func (m *customGameScoreModel) FindAllScore(ctx context.Context, appId uint64, t uint64) (resp []GameScore, err error) {
err = m.QueryRowNoCacheCtx(ctx, &resp, "select * from game_score where app_account = ? and t = ?", appId, t)
return
}
// GetRankList 获取排行榜列表
func (m *customGameScoreModel) GetRankList(ctx context.Context, appId uint64, t uint64, page types.PageBase) (resp []types.RankingData, err error) {
cacheKey := m.rankListCacheKey(appId, t)

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.23.5
require (
gitea.youtukeji.com.cn/youtu/openapi-helper v0.0.3-2
github.com/go-redis/redis v6.15.9+incompatible
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/silenceper/wechat/v2 v2.1.7
github.com/zeromicro/go-zero v1.7.6

2
go.sum
View File

@ -47,6 +47,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=

149
sql/ecpm_backup.sql Normal file

File diff suppressed because one or more lines are too long