2025-01-20 10:10:08 +08:00
package svc
import (
"context"
"encoding/json"
"errors"
2025-01-21 10:38:27 +08:00
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/config"
2025-01-24 18:36:46 +08:00
"gitea.youtukeji.com.cn/xiabin/youtu_server/game_open_api/internal/logic/rankings"
2025-01-25 18:19:07 +08:00
"gitea.youtukeji.com.cn/xiabin/youtu_server/gorm-gen/dao/query"
2025-01-25 18:05:37 +08:00
redisCacher "gitea.youtukeji.com.cn/xiabin/youtu_server/gorm-gen/redis"
2025-01-21 16:48:55 +08:00
helper "gitea.youtukeji.com.cn/youtu/openapi-helper"
2025-01-25 18:05:37 +08:00
"github.com/go-gorm/caches/v4"
2025-01-24 10:00:03 +08:00
"github.com/golang-jwt/jwt/v4"
2025-01-24 18:36:46 +08:00
"github.com/redis/go-redis/v9"
2025-01-21 16:48:55 +08:00
redisCache "github.com/silenceper/wechat/v2/cache"
2025-01-23 18:39:48 +08:00
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
2025-01-25 18:05:37 +08:00
"gorm.io/driver/mysql"
"gorm.io/gorm"
2025-01-24 10:00:03 +08:00
"math/rand"
"time"
2025-01-20 10:10:08 +08:00
)
type ServiceContext struct {
2025-01-24 18:36:46 +08:00
Config config . Config
DouyinCli * helper . DouYinOpenApiClient
WechatCli * helper . WechatApi
ZapLogger * zap . Logger
RedisRanking * rankings . Ranking
2025-01-25 18:05:37 +08:00
Query * query . Query
2025-01-20 10:10:08 +08:00
}
func NewServiceContext ( c config . Config ) * ServiceContext {
2025-01-24 18:36:46 +08:00
//初始化redis client
redisClient := redis . NewClient ( & redis . Options {
Addr : c . DWCache . Host ,
Password : c . DWCache . Password , // 没有密码,默认值
} )
2025-01-25 18:05:37 +08:00
//初始化数据库
db , err := gorm . Open ( mysql . Open ( c . DB . DataSource ) , & gorm . Config { } )
if err != nil {
panic ( err )
}
err = db . Use ( & caches . Caches { Conf : & caches . Config {
Easer : true ,
Cacher : redisCacher . New ( redisClient ) ,
} } )
if err != nil {
panic ( err )
}
svc := & ServiceContext {
Config : c ,
Query : query . Use ( db ) ,
}
//初始化排行榜对象
svc . InitRankings ( redisClient )
//初始化小程序配置
svc . InitDWClient ( c )
//初始化一个zap日志对象用于写入ecpm日志
svc . InitEcpmLog ( c )
return svc
}
func ( svc * ServiceContext ) InitRankings ( redisClient * redis . Client ) {
2025-01-24 18:36:46 +08:00
//初始化排行榜对象
svc . RedisRanking = rankings . NewRanking ( redisClient )
2025-01-25 18:05:37 +08:00
//获取所有不同的排行榜
gs := svc . Query . GameScore
r , err := gs . FindDistinctRanking ( )
if err != nil {
panic ( err )
}
//获取所有排行榜
for _ , ranking := range r {
scores , err := gs . Where ( gs . AppAccount . Eq ( ranking . AppAccount ) , gs . T . Eq ( ranking . T ) ) . Find ( )
if err != nil {
panic ( err )
}
data := make ( [ ] redis . Z , 0 , len ( scores ) )
2025-01-25 18:47:49 +08:00
2025-01-25 18:05:37 +08:00
for _ , score := range scores {
data = append ( data , redis . Z {
2025-01-25 18:47:49 +08:00
Score : float64 ( uint64 ( score . Score ) << 32 + uint64 ( score . UpdatedAt . Unix ( ) ) ) ,
2025-01-25 18:05:37 +08:00
Member : score . AppUserID ,
} )
}
svc . RedisRanking . SetList ( context . Background ( ) , rankings . GetRankingsCacheKey ( ranking . AppAccount , ranking . T ) , data ... )
}
}
func ( svc * ServiceContext ) InitDWClient ( c config . Config ) {
2025-01-21 16:48:55 +08:00
dwCache := redisCache . NewRedis ( context . Background ( ) , & redisCache . RedisOpts { Host : c . DWCache . Host , IdleTimeout : c . DWCache . IdleTimeout } )
svc . DouyinCli = helper . NewDouYinOpenApiClient ( )
svc . WechatCli = helper . NewWechatOpenApiClient ( )
2025-01-24 18:36:46 +08:00
2025-01-25 18:05:37 +08:00
//select `app_id`,`secret`,`ecpm_value`,`ecpm_view`,`type` from `app_account` left join douyin_ecpm_config on app_account.id = douyin_ecpm_config.app_account_id
var result [ ] struct {
ID int32 ` gorm:"column:id;type:int unsigned;primaryKey;autoIncrement:true" json:"id" `
Type int32 ` gorm:"column:type;type:tinyint unsigned;not null;comment:类型(0:抖音,1:微信)" json:"type" ` // 类型(0:抖音,1:微信)
AppID string ` gorm:"column:app_id;type:char(20);not null;uniqueIndex:app_id,priority:1" json:"app_id" `
Secret string ` gorm:"column:secret;type:char(40);not null" json:"secret" `
EcpmValue uint32 ` gorm:"column:ecpm_value;type:int unsigned;not null;comment:值" json:"ecpm_value" ` // 值
EcpmView uint32 ` gorm:"column:ecpm_view;type:int unsigned;not null;comment:浏览次数" json:"ecpm_view" ` // 浏览次数
}
2025-01-24 18:36:46 +08:00
//查询小程序配置(抖音&微信)
2025-01-25 18:05:37 +08:00
err := svc . Query . AppAccount . LeftJoin ( svc . Query . DouyinEcpmConfig , svc . Query . AppAccount . ID . EqCol ( svc . Query . DouyinEcpmConfig . AppAccountID ) ) . Scan ( & result )
2025-01-21 16:48:55 +08:00
if err != nil {
panic ( err )
}
2025-01-24 18:36:46 +08:00
//小程序配置
2025-01-25 18:05:37 +08:00
for _ , v := range result {
2025-01-21 16:48:55 +08:00
if v . Type == 0 {
2025-01-25 18:05:37 +08:00
svc . DouyinCli . NewAndStoreDouYinOpenApi ( v . AppID , v . Secret , v . EcpmValue , v . EcpmView , dwCache )
2025-01-21 16:48:55 +08:00
} else {
svc . WechatCli . NewAndStoreWechatOpenApi ( v . AppID , v . Secret , dwCache )
}
}
2025-01-25 18:05:37 +08:00
}
2025-01-21 16:48:55 +08:00
2025-01-25 18:05:37 +08:00
func ( svc * ServiceContext ) InitEcpmLog ( c config . Config ) {
2025-01-23 18:39:48 +08:00
svc . ZapLogger = zap . New ( zapcore . NewCore (
zapcore . NewJSONEncoder ( zapcore . EncoderConfig {
TimeKey : "ts" ,
LevelKey : "level" ,
NameKey : "logger" ,
CallerKey : "caller" ,
FunctionKey : zapcore . OmitKey ,
MessageKey : "msg" ,
StacktraceKey : "stacktrace" ,
LineEnding : zapcore . DefaultLineEnding ,
EncodeLevel : zapcore . LowercaseLevelEncoder ,
EncodeTime : zapcore . EpochTimeEncoder ,
EncodeDuration : zapcore . SecondsDurationEncoder ,
EncodeCaller : zapcore . ShortCallerEncoder ,
} ) ,
zapcore . NewMultiWriteSyncer ( zapcore . AddSync ( & lumberjack . Logger {
Filename : c . EcpmLogPath , // Log file path 日志文件的路径
MaxSize : 1024 , // Maximum size unit for each log file: M 每个日志最大大小
MaxBackups : 30 , // The maximum number of backups that can be saved for log files 可以保存的日志文件数量
MaxAge : 7 , // Maximum number of days the file can be saved 日志文件保存的最大天数
Compress : true , // Compression or not 是否对日志文件进行压缩
} ) ) ,
zapcore . InfoLevel ,
) )
2025-01-20 10:10:08 +08:00
}
type AccessToken struct {
2025-01-25 18:05:37 +08:00
AppId uint32 ` json:"appId" `
2025-01-20 10:10:08 +08:00
UserId uint64 ` json:"userId" `
AppIdStr string ` json:"appIdStr" `
OpenId string ` json:"openId" `
}
func UnmarshalAccessToken ( d any ) ( ac AccessToken , err error ) {
m , ok := d . ( map [ string ] interface { } )
if ! ok {
err = errors . New ( "invalid access token" )
return
}
appId , _ := m [ "appId" ] . ( json . Number ) . Int64 ( )
2025-01-25 18:05:37 +08:00
ac . AppId = uint32 ( appId )
2025-01-20 10:10:08 +08:00
userId , _ := m [ "userId" ] . ( json . Number ) . Int64 ( )
ac . UserId = uint64 ( userId )
ac . AppIdStr = m [ "appIdStr" ] . ( string )
ac . OpenId = m [ "openId" ] . ( string )
return
}
func GetCtxToken ( ctx context . Context ) ( ac AccessToken , err error ) {
return UnmarshalAccessToken ( ctx . Value ( "payload" ) )
}
2025-01-24 10:00:03 +08:00
// GenerateAccessToken 生成 JWT 认证的 token
// 会从ServiceContext中获取配置信息
func ( svc * ServiceContext ) GenerateAccessToken ( at * AccessToken ) ( token string , err error ) {
claims := make ( jwt . MapClaims )
claims [ "exp" ] = time . Now ( ) . Unix ( ) + svc . Config . Auth . AccessExpire
claims [ "iat" ] = svc . Config . Auth . AccessExpire
claims [ "payload" ] = at
t := jwt . New ( jwt . SigningMethodHS256 )
t . Claims = claims
token , err = t . SignedString ( [ ] byte ( svc . Config . Auth . AccessSecret ) )
return
}
var DefaultUsername = [ ] string {
"甜蜜糖果" ,
"糖果爱好者" ,
"软糖粉丝" ,
"巧克力糖果控" ,
"棒棒糖迷" ,
"小熊软糖达人" ,
"硬糖狂人" ,
"焦糖糖果控" ,
"水果糖行家" ,
"棉花糖达人" ,
}
// GetRandomUsername 随机获取一个糖果相关的用户名
func GetRandomUsername ( ) string {
// 初始化随机数种子
r := rand . New ( rand . NewSource ( time . Now ( ) . UnixNano ( ) ) )
// 生成一个 0 到列表长度减 1 之间的随机索引
randomIndex := r . Intn ( len ( DefaultUsername ) )
// 根据随机索引返回对应的用户名
return DefaultUsername [ randomIndex ]
}