package svc import ( "context" "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/gorm-gen/dao/query" redisCacher "gitea.youtukeji.com.cn/xiabin/youtu_server/gorm-gen/redis" helper "gitea.youtukeji.com.cn/youtu/openapi-helper" "github.com/go-gorm/caches/v4" "github.com/golang-jwt/jwt/v4" "github.com/redis/go-redis/v9" redisCache "github.com/silenceper/wechat/v2/cache" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" "gorm.io/driver/mysql" "gorm.io/gorm" "math/rand" "time" ) type ServiceContext struct { Config config.Config DouyinCli *helper.DouYinOpenApiClient WechatCli *helper.WechatApi ZapLogger *zap.Logger RedisRanking *rankings.Ranking Query *query.Query } func NewServiceContext(c config.Config) *ServiceContext { //初始化redis client redisClient := redis.NewClient(&redis.Options{ Addr: c.DWCache.Host, Password: c.DWCache.Password, // 没有密码,默认值 }) //初始化数据库 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) { //初始化排行榜对象 svc.RedisRanking = rankings.NewRanking(redisClient) //获取所有不同的排行榜 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)) for _, score := range scores { data = append(data, redis.Z{ Score: float64(uint64(score.Score)<<32 + uint64(score.UpdatedAt.Unix())), Member: score.AppUserID, }) } svc.RedisRanking.SetList(context.Background(), rankings.GetRankingsCacheKey(ranking.AppAccount, ranking.T), data...) } } func (svc *ServiceContext) InitDWClient(c config.Config) { dwCache := redisCache.NewRedis(context.Background(), &redisCache.RedisOpts{Host: c.DWCache.Host, IdleTimeout: c.DWCache.IdleTimeout}) svc.DouyinCli = helper.NewDouYinOpenApiClient() svc.WechatCli = helper.NewWechatOpenApiClient() //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"` // 浏览次数 } //查询小程序配置(抖音&微信) err := svc.Query.AppAccount.LeftJoin(svc.Query.DouyinEcpmConfig, svc.Query.AppAccount.ID.EqCol(svc.Query.DouyinEcpmConfig.AppAccountID)).Scan(&result) if err != nil { panic(err) } //小程序配置 for _, v := range result { if v.Type == 0 { svc.DouyinCli.NewAndStoreDouYinOpenApi(v.AppID, v.Secret, v.EcpmValue, v.EcpmView, dwCache) } else { svc.WechatCli.NewAndStoreWechatOpenApi(v.AppID, v.Secret, dwCache) } } } func (svc *ServiceContext) InitEcpmLog(c config.Config) { 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, )) } type AccessToken struct { AppId uint32 `json:"appId"` 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() ac.AppId = uint32(appId) 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")) } // 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] }