Merge branch 'develop' into sdk
This commit is contained in:
commit
7c6a5b5497
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ src/server/gamedata/config/*.json
|
|||||||
src/server/unit_test.go
|
src/server/unit_test.go
|
||||||
src/server/teLog/*
|
src/server/teLog/*
|
||||||
src/server/teLog/log.2024-11-28
|
src/server/teLog/log.2024-11-28
|
||||||
|
src/server/logs/ga_log/*.log
|
||||||
|
|||||||
@ -32,6 +32,11 @@ var Server struct {
|
|||||||
RedisPwd string
|
RedisPwd string
|
||||||
RedisDb int
|
RedisDb int
|
||||||
|
|
||||||
|
RedisWriteAddr string // 主写地址(host:port 或 单独 host, 仍兼容旧 RedisAddr/RedisPort)
|
||||||
|
RedisReadAddrs string // 只读地址,逗号分隔(host:port,...)
|
||||||
|
RedisMasterName string // 哨兵模式下的 master 名称
|
||||||
|
RedisConnType string // "Direct" 或 "Sentinel"
|
||||||
|
|
||||||
GameName string
|
GameName string
|
||||||
ServerType string
|
ServerType string
|
||||||
|
|
||||||
|
|||||||
@ -25,9 +25,15 @@
|
|||||||
"ServerCenter" : 1,
|
"ServerCenter" : 1,
|
||||||
"GameConfPath": "D:/Github/pet_home_server/src/server/gamedata/config/",
|
"GameConfPath": "D:/Github/pet_home_server/src/server/gamedata/config/",
|
||||||
|
|
||||||
"RedisAddr":"127.0.0.1",
|
"RedisAddr":"127.0.0.1",
|
||||||
"RedisPort" :"6379",
|
"RedisPort" :"6379",
|
||||||
"RedisPwd" :"",
|
"RedisPwd" :"",
|
||||||
|
|
||||||
|
"RedisWriteAddr":"127.0.0.1:6379",
|
||||||
|
"RedisReadAddrs":"127.0.0.1:6379",
|
||||||
|
"RedisMasterName":"mymaster",
|
||||||
|
"RedisConnType":"Direct",
|
||||||
|
|
||||||
"GoogleVerify":false,
|
"GoogleVerify":false,
|
||||||
"RemoteAddr":"host.docker.internal:9001",
|
"RemoteAddr":"host.docker.internal:9001",
|
||||||
"Partition":3,
|
"Partition":3,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"server/GoUtil"
|
"server/GoUtil"
|
||||||
@ -9,6 +8,8 @@ import (
|
|||||||
"server/conf"
|
"server/conf"
|
||||||
"server/pkg/github.com/name5566/leaf/log"
|
"server/pkg/github.com/name5566/leaf/log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
// "server/game"
|
// "server/game"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
@ -23,14 +24,60 @@ type user struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var SqlDb *sqlx.DB
|
var SqlDb *sqlx.DB
|
||||||
|
var sqlDbMu sync.Mutex
|
||||||
|
|
||||||
|
// 封装创建连接
|
||||||
|
func connectMySQL() (*sqlx.DB, error) {
|
||||||
|
MysqlPwd, _ := GoUtil.Decrypt(conf.Server.MySqlPwd)
|
||||||
|
connect := fmt.Sprintf("%s:%s@(%s:%s)/%s", conf.Server.MySqlUsr, MysqlPwd, conf.Server.MySqlAddr, conf.Server.MySqlPort, conf.Server.DbName)
|
||||||
|
db, err := sqlx.Connect("mysql", connect)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db.SetMaxOpenConns(20)
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
func InitDB() {
|
func InitDB() {
|
||||||
//"用户名:密码@[连接方式](主机名:端口号)/数据库名"
|
//"用户名:密码@[连接方式](主机名:端口号)/数据库名"
|
||||||
MysqlPwd, _ := GoUtil.Decrypt(conf.Server.MySqlPwd)
|
db, err := connectMySQL()
|
||||||
connect := fmt.Sprintf("%s:%s@(%s:%s)/%s", conf.Server.MySqlUsr, MysqlPwd, conf.Server.MySqlAddr, conf.Server.MySqlPort, conf.Server.DbName)
|
if err != nil {
|
||||||
SqlDb = sqlx.MustConnect("mysql", connect) // 设置连接数据库的参数
|
log.Debug("connect mysql failed: %v", err)
|
||||||
SqlDb.SetMaxOpenConns(20) // 设置最大打开的连接数
|
return
|
||||||
|
}
|
||||||
|
SqlDb = db
|
||||||
log.Debug("connect mysql success")
|
log.Debug("connect mysql success")
|
||||||
|
|
||||||
|
// 定时检测与重连
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
sqlDbMu.Lock()
|
||||||
|
cur := SqlDb
|
||||||
|
sqlDbMu.Unlock()
|
||||||
|
if cur == nil || cur.Ping() != nil {
|
||||||
|
log.Debug("mysql ping failed, start reconnect")
|
||||||
|
ReconnectDB()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动重连
|
||||||
|
func ReconnectDB() {
|
||||||
|
sqlDbMu.Lock()
|
||||||
|
defer sqlDbMu.Unlock()
|
||||||
|
newDb, err := connectMySQL()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("mysql reconnect failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if SqlDb != nil {
|
||||||
|
_ = SqlDb.Close()
|
||||||
|
}
|
||||||
|
SqlDb = newDb
|
||||||
|
log.Debug("mysql reconnect success")
|
||||||
}
|
}
|
||||||
|
|
||||||
func SeriesTransaction(sqlstrs []string, params [][]any) (err error) {
|
func SeriesTransaction(sqlstrs []string, params [][]any) (err error) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"server/conf"
|
"server/conf"
|
||||||
"server/pkg/github.com/name5566/leaf/log"
|
"server/pkg/github.com/name5566/leaf/log"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
@ -11,34 +12,118 @@ import (
|
|||||||
|
|
||||||
var ctx = context.Background()
|
var ctx = context.Background()
|
||||||
|
|
||||||
var Rdb *redis.Client
|
var RdbWrite *redis.Client
|
||||||
|
var RdbRead *redis.Client
|
||||||
|
|
||||||
func InitRedis() {
|
// helper: 创建单个客户端(addr 可以为 host:port 或 host)
|
||||||
|
func connectClient(addr string) (*redis.Client, error) {
|
||||||
|
if addr == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// 如果没有端口且配置了旧的 RedisPort,则尝试补端口
|
||||||
|
if !strings.Contains(addr, ":") && conf.Server.RedisPort != "" {
|
||||||
|
addr = addr + ":" + conf.Server.RedisPort
|
||||||
|
}
|
||||||
rdb := redis.NewClient(&redis.Options{
|
rdb := redis.NewClient(&redis.Options{
|
||||||
Addr: conf.Server.RedisAddr + ":" + conf.Server.RedisPort,
|
Addr: addr,
|
||||||
Password: conf.Server.RedisPwd, // no password set
|
Password: conf.Server.RedisPwd,
|
||||||
DB: conf.Server.RedisDb,
|
DB: conf.Server.RedisDb,
|
||||||
})
|
})
|
||||||
|
if _, err := rdb.Ping(ctx).Result(); err != nil {
|
||||||
_, err := rdb.Ping(ctx).Result()
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
log.Debug("连接redis出错,错误信息:%v", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Debug("成功连接redis")
|
return rdb, nil
|
||||||
Rdb = rdb
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitRedis: 初始化读写分离客户端(向后兼容旧配置)
|
||||||
|
func InitRedis() {
|
||||||
|
// 决定写地址:优先使用 RedisWriteAddr,其次使用旧的 RedisAddr:RedisPort
|
||||||
|
writeAddr := conf.Server.RedisWriteAddr
|
||||||
|
if writeAddr == "" {
|
||||||
|
if conf.Server.RedisAddr != "" {
|
||||||
|
writeAddr = conf.Server.RedisAddr
|
||||||
|
if conf.Server.RedisPort != "" && !strings.Contains(writeAddr, ":") {
|
||||||
|
writeAddr = writeAddr + ":" + conf.Server.RedisPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 决定读地址:优先使用 RedisReadAddrs(逗号分隔),若为空则回退到写地址
|
||||||
|
readAddrs := conf.Server.RedisReadAddrs
|
||||||
|
if strings.TrimSpace(readAddrs) == "" {
|
||||||
|
readAddrs = writeAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取第一个可用的只读地址(简单实现)
|
||||||
|
var readClient *redis.Client
|
||||||
|
for _, a := range strings.Split(readAddrs, ",") {
|
||||||
|
a = strings.TrimSpace(a)
|
||||||
|
if a == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, err := connectClient(a)
|
||||||
|
if err == nil {
|
||||||
|
readClient = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Debug("connect read addr %s failed: %v", a, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有只读都不可用,尝试连接写地址作为回退
|
||||||
|
writeClient, err := connectClient(writeAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("连接redis写节点出错,错误信息:%v", err)
|
||||||
|
// 若读已连上则也作为写回退,否则返回
|
||||||
|
if readClient != nil {
|
||||||
|
RdbWrite = readClient
|
||||||
|
RdbRead = readClient
|
||||||
|
log.Debug("只有只读节点可用,读写共用该节点")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果读未连接成功,读回退到写
|
||||||
|
if readClient == nil {
|
||||||
|
readClient = writeClient
|
||||||
|
}
|
||||||
|
|
||||||
|
RdbWrite = writeClient
|
||||||
|
RdbRead = readClient
|
||||||
|
log.Debug("成功初始化 redis(读写分离),写: %v, 读: %v", writeAddr, readAddrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写操作使用 RdbWrite
|
||||||
func RedisSetKey(key string, value string, expiration time.Duration) {
|
func RedisSetKey(key string, value string, expiration time.Duration) {
|
||||||
err := Rdb.Set(ctx, key, value, expiration).Err()
|
if RdbWrite == nil {
|
||||||
|
log.Debug("redis write client is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := RdbWrite.Set(ctx, key, value, expiration).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("redis set failed, err:%v\n", err)
|
log.Debug("redis set failed, err:%v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取锁
|
// 新增:写入字节数据,避免 string 转换拷贝
|
||||||
|
func RedisSetKeyBytes(key string, value []byte, expiration time.Duration) {
|
||||||
|
if RdbWrite == nil {
|
||||||
|
log.Debug("redis write client is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := RdbWrite.Set(ctx, key, value, expiration).Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("redis set failed, err:%v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取锁(写)
|
||||||
func RedisLock(key string, value string, expiration time.Duration) bool {
|
func RedisLock(key string, value string, expiration time.Duration) bool {
|
||||||
ok, err := Rdb.SetNX(ctx, key, value, expiration).Result()
|
if RdbWrite == nil {
|
||||||
|
log.Debug("redis write client is nil")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ok, err := RdbWrite.SetNX(ctx, key, value, expiration).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("redis lock failed, err:%v\n", err)
|
log.Debug("redis lock failed, err:%v\n", err)
|
||||||
return false
|
return false
|
||||||
@ -46,8 +131,12 @@ func RedisLock(key string, value string, expiration time.Duration) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// 释放锁
|
// 释放锁(写)
|
||||||
func RedisUnlock(key string, value string) bool {
|
func RedisUnlock(key string, value string) bool {
|
||||||
|
if RdbWrite == nil {
|
||||||
|
log.Debug("redis write client is nil")
|
||||||
|
return false
|
||||||
|
}
|
||||||
script := `
|
script := `
|
||||||
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||||
return redis.call("DEL", KEYS[1])
|
return redis.call("DEL", KEYS[1])
|
||||||
@ -55,7 +144,7 @@ func RedisUnlock(key string, value string) bool {
|
|||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
`
|
`
|
||||||
result, err := Rdb.Eval(ctx, script, []string{key}, value).Result()
|
result, err := RdbWrite.Eval(ctx, script, []string{key}, value).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("redis unlock failed, err:%v\n", err)
|
log.Debug("redis unlock failed, err:%v\n", err)
|
||||||
return false
|
return false
|
||||||
@ -63,8 +152,12 @@ func RedisUnlock(key string, value string) bool {
|
|||||||
return result.(int64) == 1
|
return result.(int64) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读操作使用 RdbRead
|
||||||
func RedisGetKey(key string) (string, error) {
|
func RedisGetKey(key string) (string, error) {
|
||||||
val, err := Rdb.Get(ctx, key).Result()
|
if RdbRead == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
val, err := RdbRead.Get(ctx, key).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -72,21 +165,32 @@ func RedisGetKey(key string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RedisDelKey(key string) {
|
func RedisDelKey(key string) {
|
||||||
err := Rdb.Del(ctx, key).Err()
|
if RdbWrite == nil {
|
||||||
|
log.Debug("redis write client is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := RdbWrite.Del(ctx, key).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("redis del failed, err:%v\n", err)
|
log.Debug("redis del failed, err:%v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RedisZAdd(key string, member string, score float64) {
|
func RedisZAdd(key string, member string, score float64) {
|
||||||
err := Rdb.ZAdd(ctx, key, redis.Z{Score: score, Member: member}).Err()
|
if RdbWrite == nil {
|
||||||
|
log.Debug("redis write client is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := RdbWrite.ZAdd(ctx, key, redis.Z{Score: score, Member: member}).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("redis zadd failed, err:%v\n", err)
|
log.Debug("redis zadd failed, err:%v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RedisZRangeWithScores(key string, start, stop int64) ([]redis.Z, error) {
|
func RedisZRangeWithScores(key string, start, stop int64) ([]redis.Z, error) {
|
||||||
val, err := Rdb.ZRangeWithScores(ctx, key, start, stop).Result()
|
if RdbRead == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
val, err := RdbRead.ZRangeWithScores(ctx, key, start, stop).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -94,7 +198,10 @@ func RedisZRangeWithScores(key string, start, stop int64) ([]redis.Z, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RedisZRevRangeWithScores(key string, start, stop int64) ([]redis.Z, error) {
|
func RedisZRevRangeWithScores(key string, start, stop int64) ([]redis.Z, error) {
|
||||||
val, err := Rdb.ZRevRangeWithScores(ctx, key, start, stop).Result()
|
if RdbRead == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
val, err := RdbRead.ZRevRangeWithScores(ctx, key, start, stop).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -102,11 +209,14 @@ func RedisZRevRangeWithScores(key string, start, stop int64) ([]redis.Z, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RedisZRankWithScores(key, member string) (int64, float64, error) {
|
func RedisZRankWithScores(key, member string) (int64, float64, error) {
|
||||||
val, err := Rdb.ZRank(ctx, key, member).Result()
|
if RdbRead == nil {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
val, err := RdbRead.ZRank(ctx, key, member).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
score, err := Rdb.ZScore(ctx, key, member).Result()
|
score, err := RdbRead.ZScore(ctx, key, member).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
@ -114,7 +224,11 @@ func RedisZRankWithScores(key, member string) (int64, float64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RedisDel(key string) {
|
func RedisDel(key string) {
|
||||||
err := Rdb.Del(ctx, key).Err()
|
if RdbWrite == nil {
|
||||||
|
log.Debug("redis write client is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := RdbWrite.Del(ctx, key).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("redis del failed, err:%v\n", err)
|
log.Debug("redis del failed, err:%v\n", err)
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/server/ga/log.go
Normal file
34
src/server/ga/log.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package ga
|
||||||
|
|
||||||
|
import (
|
||||||
|
galog "github.com/tuyou/galog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PROJECT_ID = "20659"
|
||||||
|
CLIENT_ID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20659.googleplay.Meowment"
|
||||||
|
)
|
||||||
|
|
||||||
|
var glogger *galog.GALogger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
glog, err := galog.NewGALogger("logs", PROJECT_ID, CLIENT_ID, galog.LogTypeTrack)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
glogger = glog
|
||||||
|
}
|
||||||
|
|
||||||
|
func GAlogEvent(event string, userID string, deviceID string, properties map[string]interface{}) {
|
||||||
|
newProperties := make(map[string]interface{})
|
||||||
|
for k, v := range properties {
|
||||||
|
newProperties["proj_"+k] = v
|
||||||
|
}
|
||||||
|
properties = newProperties
|
||||||
|
glogger.
|
||||||
|
GetEntry(event).
|
||||||
|
SetDeviceID(deviceID).
|
||||||
|
SetUserID(userID).
|
||||||
|
SetProperties(properties).
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
7
src/server/galog/.gitignore
vendored
Normal file
7
src/server/galog/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
ga_log/
|
||||||
|
asset_log/
|
||||||
292
src/server/galog/README.md
Normal file
292
src/server/galog/README.md
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
# galog
|
||||||
|
|
||||||
|
galog是一个无三方依赖、支持单CPU最高5wQPS写入的ga-sdk组件,可直接用于GA事实日志和资产日志打点。
|
||||||
|
|
||||||
|
> 说明:默认是异步写入模式,异步队列长度=100,较大QPS时可自行调整,详见参数:
|
||||||
|
> - enableAsync
|
||||||
|
> - defaultAsyncMsgLen
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// ga日志初始化
|
||||||
|
projectID := "20433"
|
||||||
|
clientID := "Android_5.0_tyGuest,weixinPay,tyAccount.alipay.0-hall20433.tuyoo.sdktest"
|
||||||
|
glogger, err := galog.NewGALogger("logs", projectID, clientID, galog.LogTypeTrack)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ga日志打点调用
|
||||||
|
props := map[string]interface{}{
|
||||||
|
"ip_address": "127.0.0.1",
|
||||||
|
"proj_app_id": "10010",
|
||||||
|
"uuid": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts": tt.UnixNano(),
|
||||||
|
}
|
||||||
|
glogger.
|
||||||
|
GetEntry("sdk_s_login_succ").
|
||||||
|
SetDeviceID("device001").
|
||||||
|
SetUserID("10086").
|
||||||
|
SetProperties(props).
|
||||||
|
Flush()
|
||||||
|
```
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// asset日志初始化
|
||||||
|
projectID := "28"
|
||||||
|
clientID := "Android_4.827_tyGuest,nearme.nearme.0-hall28.oppo.bydzz"
|
||||||
|
glogger, err := galog.NewGALogger("logs", projectID, clientID, galog.LogTypeAsset)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// asset日志打点调用
|
||||||
|
asset := galog.AssetProperties{}
|
||||||
|
asset.
|
||||||
|
SetAssetID("13101").
|
||||||
|
SetAssetName("\u9501\u5b9a").
|
||||||
|
SetAssetType("6").
|
||||||
|
SetAssetFinal("2").
|
||||||
|
SetAssetAssociated("3").
|
||||||
|
SetAssetStartTime("0").
|
||||||
|
SetAssetTimeLimit("0").
|
||||||
|
SetAssetSource("").
|
||||||
|
SetKV("uuid", "uuid-v4")
|
||||||
|
|
||||||
|
glogger.
|
||||||
|
GetEntry("asset_increase").
|
||||||
|
SetDeviceID("").
|
||||||
|
SetUserID("10086").
|
||||||
|
SetProperties(asset).
|
||||||
|
Flush()
|
||||||
|
```
|
||||||
|
|
||||||
|
## suger usage
|
||||||
|
|
||||||
|
```golang
|
||||||
|
package chat_log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"tygit.tuyoo.com/gocomponents/galog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
slogger *galog.Logger
|
||||||
|
galogger *galog.GALogger
|
||||||
|
|
||||||
|
lerr error
|
||||||
|
)
|
||||||
|
|
||||||
|
// MustInitLoggerOnce 服务启动后初始化一次
|
||||||
|
func MustInitLoggerOnce() {
|
||||||
|
slogger, lerr = galog.NewServerLogger(&galog.ServerLogOptions{
|
||||||
|
LogDir: "run/logs",
|
||||||
|
EnableAsync: true,
|
||||||
|
AsyncQueueSize: 1000,
|
||||||
|
})
|
||||||
|
if lerr != nil {
|
||||||
|
log.Printf("MustInitLoggerOnce err: %s\n", lerr.Error())
|
||||||
|
panic("MustInitLoggerOnce err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustInitGALoggerOnce 服务启动后初始化一次
|
||||||
|
func MustInitGALoggerOnce() {
|
||||||
|
galogger, lerr = galog.NewServerGALogger(&galog.ServerGALogOptions{
|
||||||
|
LogDir: "run/bi",
|
||||||
|
EnableAsync: true,
|
||||||
|
AsyncQueueSize: 1000,
|
||||||
|
ProjectID: ProjectId,
|
||||||
|
ClientID: ClientId,
|
||||||
|
LogType: galog.LogTypeTrack,
|
||||||
|
})
|
||||||
|
if lerr != nil {
|
||||||
|
log.Printf("MustInitGALoggerOnce err: %s\n", lerr.Error())
|
||||||
|
panic("MustInitGALoggerOnce err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogToGANew(eventName, deviceId string, userId int64, prop map[string]interface{}, version string) {
|
||||||
|
lib := map[string]string{
|
||||||
|
"lib_type": "golang",
|
||||||
|
"lib_version": version,
|
||||||
|
}
|
||||||
|
|
||||||
|
galogger.
|
||||||
|
GetEntry(eventName).
|
||||||
|
SetDeviceID(deviceId).
|
||||||
|
SetUserID(strconv.FormatInt(userId, 10)).
|
||||||
|
SetLib(lib).
|
||||||
|
SetProperties(prop).
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 日志目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
{logDir}/ga_log/{日志类型}_{hostname}_{年月日}_{小时}.log
|
||||||
|
{logDir}/asset_log/{日志类型}_{hostname}_{年月日}_{小时}.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmark
|
||||||
|
|
||||||
|
- 测试机:Mac-M1 8c 16G
|
||||||
|
|
||||||
|
- 同步写入模式
|
||||||
|
```
|
||||||
|
goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
pkg: tygit.tuyoo.com/gocomponents/galog
|
||||||
|
BenchmarkGaLog-8 87565 12831 ns/op 7590 B/op 90 allocs/op
|
||||||
|
BenchmarkAssetLog-8 234127 5832 ns/op 3028 B/op 44 allocs/op
|
||||||
|
PASS
|
||||||
|
ok tygit.tuyoo.com/gocomponents/galog 3.190s
|
||||||
|
|
||||||
|
# 100w行日志文件,写入性能未见明显衰减
|
||||||
|
BenchmarkGaLog-8 89175 12481 ns/op 7590 B/op 90 allocs/op
|
||||||
|
BenchmarkAssetLog-8 195075 5933 ns/op 3028 B/op 44 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
- 异步写入模式(默认)
|
||||||
|
```
|
||||||
|
goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
pkg: tygit.tuyoo.com/gocomponents/galog
|
||||||
|
BenchmarkGaLog-8 95530 11331 ns/op 7599 B/op 90 allocs/op
|
||||||
|
BenchmarkAssetLog-8 337963 3512 ns/op 3032 B/op 44 allocs/op
|
||||||
|
PASS
|
||||||
|
ok tygit.tuyoo.com/gocomponents/galog 3.040s
|
||||||
|
|
||||||
|
# 120w行日志文件,写入性能未见明显衰减
|
||||||
|
BenchmarkGaLog-8 116559 10450 ns/op 7598 B/op 90 allocs/op
|
||||||
|
BenchmarkAssetLog-8 335670 3512 ns/op 3032 B/op 44 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
## Load Testing
|
||||||
|
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o galog examples/hey.go
|
||||||
|
|
||||||
|
- 测试机:sa101-ecs-bj4-pt57-test-wxy
|
||||||
|
- linux服务器配置:2c4G
|
||||||
|
- 测试时间:2024-02-26 17:45 ~ 18:45
|
||||||
|
|
||||||
|
```
|
||||||
|
./galog -n 1000000 -c 2 -q 100
|
||||||
|
```
|
||||||
|
|
||||||
|
| 压测说明 | CPU平台使用率 | 内存使用率 | IO平均使用率 | 进程打开的文件句柄数 |
|
||||||
|
|----- | ----- | ----- | ----- | ----- |
|
||||||
|
| 无任务 | 1% | 52% | 0.08% | 0 |
|
||||||
|
| 并发=2,qps=200 | 1% | 52% | 0.08% | 7 |
|
||||||
|
| 并发=5,qps=5000 | 4% | 52% | 1% | 7 |
|
||||||
|
| 并发=5,qps=50000(队列1000,有阻塞) realqps=5000 | 40% | 52% | 30% | 7 |
|
||||||
|
| 并发=5,qps=50000(队列10000,有阻塞) realqps=6500 | 6% | 52% | 1% | 7 |
|
||||||
|
| 并发=20,qps=50000(队列10000,有阻塞) realqps=18000 | 10% | 52% | 3% | 7 |
|
||||||
|
| 并发=50,qps=50000(队列10000) realqps=50000 | 35% | 52% | 10% | 7 |
|
||||||
|
| 并发=100,qps=100000(队列10000) realqps=90000 | 75% | 52% | 16% | 7 |
|
||||||
|
|
||||||
|
- 压测结果:写入队列=10000,单核支持最高写入QPS约5w,CPU=75%。
|
||||||
|
|
||||||
|
- 监控结果如下:
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## ga日志格式范式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"project_id": "20437",
|
||||||
|
"type": "track",
|
||||||
|
"event": "sdk_sword_holder_succ",
|
||||||
|
"event_time": 1708568434744,
|
||||||
|
"device_id": "",
|
||||||
|
"user_id": "908825698",
|
||||||
|
"client_id": "Android_5.1_tyGuest,weixinPay,tyAccount.alipay.0-hall20437.tuyoo.sdkonline",
|
||||||
|
"properties": {
|
||||||
|
"sdk_track_id": "3:6060:636978360:1708568434",
|
||||||
|
"sdk_sub_channel": "fish3d",
|
||||||
|
"country": "中国",
|
||||||
|
"proj_game_id": "",
|
||||||
|
"sdk_error_code": "",
|
||||||
|
"sdk_error_msg": "",
|
||||||
|
"city": "宁德市",
|
||||||
|
"sdk_sword_track_id": "807370ff-ceac-4ee3-b68f-50642ca4953c",
|
||||||
|
"sub_platform_id": "2",
|
||||||
|
"sdk_sword_holder_id": "320",
|
||||||
|
"sdk_sword_version": "v1.1.7",
|
||||||
|
"uuid": "56fdf0f9-9daa-4bf6-be69-88e15f05c36c",
|
||||||
|
"province": "福建省",
|
||||||
|
"sdk_login_channel_type": "",
|
||||||
|
"app_id": "20437",
|
||||||
|
"sdk_s_login_channel_type": "",
|
||||||
|
"sdk_main_channel": "official",
|
||||||
|
"game_id": "20437",
|
||||||
|
"sdk_s_route": "/api/sworder-server/rule/v1/getUserRisk",
|
||||||
|
"sdk_sword_holder_result": "999999",
|
||||||
|
"proj_cloud_id": "3",
|
||||||
|
"proj_app_id": "10010",
|
||||||
|
"ip_address": "59.58.58.18",
|
||||||
|
"sdk_sword_holder_type": "login",
|
||||||
|
"sdk_sword_holder_name": "非信任设备禁止登录",
|
||||||
|
"proj_client_id": "Android_5.505_tyGuest,tyAccount,yidunlogin.weixinPay,alipay,yinlian,jingdong,weixinShare.0-hall28.official.fish3d",
|
||||||
|
"sdk_sword_holder_process": "{\"330\":[\"[330-0]未命中\"]}",
|
||||||
|
"sdk_yidun_device_id": "UuCJztYTtxRETUVUAEaFoCQaw8H2qkWE",
|
||||||
|
"platform_id": "1",
|
||||||
|
"sub_channel_id": "sdkonline",
|
||||||
|
"proj_package_name": "",
|
||||||
|
"channel_id": "tuyoo"
|
||||||
|
},
|
||||||
|
"lib": {
|
||||||
|
"lib_version": "v1.0.0",
|
||||||
|
"lib_type": "go"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## asset日志格式范例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"project_id": "28",
|
||||||
|
"type": "asset",
|
||||||
|
"event": "asset_increase",
|
||||||
|
"event_time": 1706631881042,
|
||||||
|
"device_id": "",
|
||||||
|
"user_id": "928426614",
|
||||||
|
"client_id": "Android_4.827_tyGuest,nearme.nearme.0-hall28.oppo.bydzz",
|
||||||
|
"properties": {
|
||||||
|
"proj_ga_eventId": "STARTUP_QUEST_REWARD_COIN",
|
||||||
|
"proj_asset_value": "2",
|
||||||
|
"proj_asset_final": "2",
|
||||||
|
"proj_chip_type": "6",
|
||||||
|
"proj_asset_id": "13101",
|
||||||
|
"proj_asset_name": "\u9501\u5b9a",
|
||||||
|
"proj_asset_type": "free",
|
||||||
|
"proj_asset_time_limit": "0",
|
||||||
|
"proj_asset_start_time": "0",
|
||||||
|
"proj_asset_source": "",
|
||||||
|
"proj_tuyoo_order_id": "",
|
||||||
|
"game_id": "28",
|
||||||
|
"app_id": "10063",
|
||||||
|
"proj_project_id": "28",
|
||||||
|
"proj_client_id": "Android_4.827_tyGuest,nearme.nearme.0-hall28.oppo.bydzz",
|
||||||
|
"uuid": "77b907688c624076a84ca7b4b29668a5"
|
||||||
|
},
|
||||||
|
"lib": {
|
||||||
|
"lib_version": "v1.0.0",
|
||||||
|
"lib_type": "go"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
| 版本 | 修订说明 | 提交人 | 发布时间 |
|
||||||
|
|----- | ----- | ----- | ----- |
|
||||||
|
| v1.0.0 | galog 0依赖、支持异步写入、小时切割日志、ga和asset日志类型 | 田文 | 2024.02.22 |
|
||||||
|
| v1.1.0 | add suger | 田文 | 2025.06.30 |
|
||||||
78
src/server/galog/buffer.go
Normal file
78
src/server/galog/buffer.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package galog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultBufPool = newBufPool()
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_size = 1024
|
||||||
|
_defaultLineEnding = "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BufPool struct {
|
||||||
|
p *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBufPool() BufPool {
|
||||||
|
return BufPool{p: &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &Buffer{bs: make([]byte, 0, _size)}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a Buffer from the pool, creating one if necessary.
|
||||||
|
func (p BufPool) Get() *Buffer {
|
||||||
|
buf := p.p.Get().(*Buffer)
|
||||||
|
buf.Reset()
|
||||||
|
buf.pool = p
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p BufPool) put(buf *Buffer) {
|
||||||
|
p.p.Put(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer is a thin wrapper around a byte slice. It's intended to be pooled, so
|
||||||
|
// the only way to construct one is via a Pool.
|
||||||
|
type Buffer struct {
|
||||||
|
bs []byte
|
||||||
|
pool BufPool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendByte writes a single byte to the Buffer.
|
||||||
|
func (b *Buffer) AppendByte(v byte) {
|
||||||
|
b.bs = append(b.bs, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendString writes a string to the Buffer.
|
||||||
|
func (b *Buffer) AppendString(s string) {
|
||||||
|
b.bs = append(b.bs, s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns a mutable reference to the underlying byte slice.
|
||||||
|
func (b *Buffer) Bytes() []byte {
|
||||||
|
return b.bs
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string copy of the underlying byte slice.
|
||||||
|
func (b *Buffer) String() string {
|
||||||
|
return string(b.bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the underlying byte slice. Subsequent writes re-use the slice's
|
||||||
|
// backing array.
|
||||||
|
func (b *Buffer) Reset() {
|
||||||
|
b.bs = b.bs[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free returns the Buffer to its Pool.
|
||||||
|
//
|
||||||
|
// Callers must not retain references to the Buffer after calling Free.
|
||||||
|
func (b *Buffer) Free() {
|
||||||
|
b.pool.put(b)
|
||||||
|
}
|
||||||
139
src/server/galog/entry.go
Normal file
139
src/server/galog/entry.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package galog
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultLib = map[string]string{"lib_type": "go", "lib_version": "v1.0.0"}
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntryBean struct {
|
||||||
|
// ProjectID 独立项目ID
|
||||||
|
ProjectID string `json:"project_id"`
|
||||||
|
// LogType 日志类型 默认是track
|
||||||
|
LogType LogType `json:"type"`
|
||||||
|
// Event 事件ID
|
||||||
|
Event string `json:"event"`
|
||||||
|
// EventTime 毫秒级时间
|
||||||
|
EventTime int64 `json:"event_time"`
|
||||||
|
// DeviceID 设备ID
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
// UserID 用户ID
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
// ClientID clientID
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
// Properties 重要额外字段
|
||||||
|
Properties map[string]interface{} `json:"properties"`
|
||||||
|
// Lib 代码库
|
||||||
|
Lib map[string]string `json:"lib"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
logger *GALogger
|
||||||
|
bean *EntryBean
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) SetUserID(uid string) *Entry {
|
||||||
|
if uid == "" {
|
||||||
|
uid = "0"
|
||||||
|
}
|
||||||
|
e.bean.UserID = uid
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) SetDeviceID(did string) *Entry {
|
||||||
|
e.bean.DeviceID = did
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) SetLib(lib map[string]string) *Entry {
|
||||||
|
e.bean.Lib = lib
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) SetClientID(cid string) *Entry {
|
||||||
|
e.bean.ClientID = cid
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Entry) SetProperties(props map[string]interface{}) *Entry {
|
||||||
|
e.bean.Properties = props
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush 日志写入
|
||||||
|
func (e *Entry) Flush() {
|
||||||
|
defer e.logger.putEntry(e)
|
||||||
|
e.logger.biz(e.bean)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AssetProperties map[string]interface{}
|
||||||
|
|
||||||
|
// SetAssetID 资产id
|
||||||
|
func (ap AssetProperties) SetAssetID(assetID string) AssetProperties {
|
||||||
|
ap["proj_asset_id"] = assetID
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetName 资产名称
|
||||||
|
func (ap AssetProperties) SetAssetName(assetName string) AssetProperties {
|
||||||
|
ap["proj_asset_name"] = assetName
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetType 资产付费类型
|
||||||
|
func (ap AssetProperties) SetAssetType(assetType string) AssetProperties {
|
||||||
|
ap["proj_asset_type"] = assetType
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetValue 资产变化数量
|
||||||
|
func (ap AssetProperties) SetAssetValue(assetValue string) AssetProperties {
|
||||||
|
ap["proj_asset_value"] = assetValue
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetFinal 资产变化后的剩余数量
|
||||||
|
func (ap AssetProperties) SetAssetFinal(assetFinal string) AssetProperties {
|
||||||
|
ap["proj_asset_final"] = assetFinal
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetTimeLimit 持续性资产持续时间
|
||||||
|
func (ap AssetProperties) SetAssetTimeLimit(assetTimeLimit string) AssetProperties {
|
||||||
|
ap["proj_asset_time_limit"] = assetTimeLimit
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetStartTime 持续性资产生效时间
|
||||||
|
func (ap AssetProperties) SetAssetStartTime(assetStartTime string) AssetProperties {
|
||||||
|
ap["proj_asset_start_time"] = assetStartTime
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetSource 变化的原因
|
||||||
|
func (ap AssetProperties) SetAssetSource(assetSource string) AssetProperties {
|
||||||
|
ap["proj_asset_source"] = assetSource
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAssetAssociated 变化关联资产
|
||||||
|
func (ap AssetProperties) SetAssetAssociated(assetAssociated string) AssetProperties {
|
||||||
|
ap["proj_asset_associated"] = assetAssociated
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTuyooOrderID SDK订单号
|
||||||
|
func (ap AssetProperties) SetTuyooOrderID(tuyooOrderID string) AssetProperties {
|
||||||
|
ap["proj_tuyoo_order_id"] = tuyooOrderID
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOrderID 游戏服订单号
|
||||||
|
func (ap AssetProperties) SetOrderID(assetOrderID string) AssetProperties {
|
||||||
|
ap["proj_order_id"] = assetOrderID
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetKV 设置自定义字段必须通知到数据组,否则打点失败
|
||||||
|
func (ap AssetProperties) SetKV(k, v string) AssetProperties {
|
||||||
|
ap[k] = v
|
||||||
|
return ap
|
||||||
|
}
|
||||||
83
src/server/galog/examples/hey.go
Normal file
83
src/server/galog/examples/hey.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"tygit.tuyoo.com/gocomponents/galog/examples/worker"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
output = flag.String("o", "", "")
|
||||||
|
|
||||||
|
c = flag.Int("c", 50, "")
|
||||||
|
n = flag.Int("n", 200, "")
|
||||||
|
q = flag.Float64("q", 0, "")
|
||||||
|
|
||||||
|
cpus = flag.Int("cpus", runtime.GOMAXPROCS(-1), "")
|
||||||
|
)
|
||||||
|
|
||||||
|
var usage = `Usage: hey [options...] <url>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-n Number of requests to run. Default is 200.
|
||||||
|
-c Number of workers to run concurrently. Total number of requests cannot
|
||||||
|
be smaller than the concurrency level. Default is 50.
|
||||||
|
-q Rate limit, in queries per second (QPS) per worker. Default is no rate limit.
|
||||||
|
-o Output type. If none provided, a summary is printed.
|
||||||
|
"csv" is the only supported alternative. Dumps the response
|
||||||
|
metrics in comma-separated values format.
|
||||||
|
|
||||||
|
-cpus Number of used cpu cores.
|
||||||
|
(default for current machine is %d cores)
|
||||||
|
`
|
||||||
|
|
||||||
|
func MainHey() {
|
||||||
|
flag.Usage = func() {
|
||||||
|
fmt.Fprintf(os.Stderr, usage, runtime.NumCPU())
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
runtime.GOMAXPROCS(*cpus)
|
||||||
|
num := *n
|
||||||
|
conc := *c
|
||||||
|
q := *q
|
||||||
|
|
||||||
|
if num <= 0 || conc <= 0 {
|
||||||
|
usageAndExit("-n and -c cannot be smaller than 1.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if num < conc {
|
||||||
|
usageAndExit("-n cannot be less than -c.")
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &worker.Work{
|
||||||
|
N: num,
|
||||||
|
C: conc,
|
||||||
|
QPS: q,
|
||||||
|
Output: *output,
|
||||||
|
}
|
||||||
|
w.Init()
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
w.Stop()
|
||||||
|
}()
|
||||||
|
w.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func usageAndExit(msg string) {
|
||||||
|
if msg != "" {
|
||||||
|
fmt.Fprint(os.Stderr, msg)
|
||||||
|
fmt.Fprintf(os.Stderr, "\n\n")
|
||||||
|
}
|
||||||
|
flag.Usage()
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
107
src/server/galog/examples/main.go
Normal file
107
src/server/galog/examples/main.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tygit.tuyoo.com/gocomponents/galog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Println("log ga asset server every 5 seconds.")
|
||||||
|
go logGA()
|
||||||
|
go logAsset()
|
||||||
|
logServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func logGA() {
|
||||||
|
projectID := "20433"
|
||||||
|
clientID := "Android_5.0_tyGuest,weixinPay,tyAccount.alipay.0-hall20433.tuyoo.sdktest"
|
||||||
|
logger, err := galog.NewServerGALogger(&galog.ServerGALogOptions{
|
||||||
|
ProjectID: projectID,
|
||||||
|
ClientID: clientID,
|
||||||
|
LogDir: "ga_log",
|
||||||
|
LogType: galog.LogTypeTrack,
|
||||||
|
EnableAsync: true,
|
||||||
|
AsyncQueueSize: 1000,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
t := time.NewTicker(5 * time.Second)
|
||||||
|
|
||||||
|
for tt := range t.C {
|
||||||
|
log.Println("logGA")
|
||||||
|
logger.
|
||||||
|
GetEntry("sdk_s_login_succ").
|
||||||
|
SetDeviceID("device001").
|
||||||
|
SetUserID("10086").
|
||||||
|
SetProperties(map[string]interface{}{
|
||||||
|
"ip_address": "127.0.0.1",
|
||||||
|
"proj_app_id": "10010",
|
||||||
|
"uuid": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts": tt.UnixNano(),
|
||||||
|
}).
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logAsset() {
|
||||||
|
projectID := "28"
|
||||||
|
clientID := "Android_4.827_tyGuest,nearme.nearme.0-hall28.oppo.bydzz"
|
||||||
|
logger, err := galog.NewGALogger(".", projectID, clientID, galog.LogTypeAsset)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
t := time.NewTicker(5 * time.Second)
|
||||||
|
|
||||||
|
asset := galog.AssetProperties{}
|
||||||
|
asset.SetAssetID("13101").
|
||||||
|
SetAssetName("\u9501\u5b9a").
|
||||||
|
SetAssetType("6").
|
||||||
|
SetAssetFinal("2").
|
||||||
|
SetAssetAssociated("3").
|
||||||
|
SetAssetStartTime("0").
|
||||||
|
SetAssetTimeLimit("0").
|
||||||
|
SetAssetSource("").
|
||||||
|
SetKV("uuid", "uuid-v4")
|
||||||
|
|
||||||
|
for tt := range t.C {
|
||||||
|
log.Println("logAssert")
|
||||||
|
_ = tt
|
||||||
|
logger.
|
||||||
|
GetEntry("asset_increase").
|
||||||
|
SetDeviceID("").
|
||||||
|
SetUserID("10086").
|
||||||
|
SetProperties(asset).
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logServer() {
|
||||||
|
slogger, _ := galog.NewServerLogger(&galog.ServerLogOptions{
|
||||||
|
LogDir: "logs",
|
||||||
|
EnableAsync: true,
|
||||||
|
AsyncQueueSize: 1000,
|
||||||
|
})
|
||||||
|
|
||||||
|
t := time.NewTicker(5 * time.Second)
|
||||||
|
|
||||||
|
for tt := range t.C {
|
||||||
|
log.Println("logServer")
|
||||||
|
_ = tt
|
||||||
|
|
||||||
|
msgMap := map[string]interface{}{
|
||||||
|
"CreateTime": time.Now().UnixNano() / int64(time.Microsecond),
|
||||||
|
"Host": "host",
|
||||||
|
"AppId": "10010",
|
||||||
|
"UserId": "12345",
|
||||||
|
"Level": "Notice",
|
||||||
|
"Entry": "Login",
|
||||||
|
"Func": "HandleLogin",
|
||||||
|
"TraceMsg": "HandlerWSFriApplyList|HandlerWSFriApplyList",
|
||||||
|
"Params": "{\"isoCode\":\"CN\",\"P0\":[3,\"Total: 3, End: 0, \"],\"pf\":\"wx\",\"appVer\":\"1.0\",\"sdkVer\":\"1.0\",\"ip\":\"58.247.195.158\",\"clientId\":\"7abd64bf-3fb6-4fef-a25e-e6562b7fb857\",\"timeZone\":\"Asia/Shanghai\",\"loginMark\":\"\",\"st\":1472}",
|
||||||
|
}
|
||||||
|
slogger.BizErr(msgMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/server/galog/examples/worker/print.go
Normal file
43
src/server/galog/examples/worker/print.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTemplate(output string) *template.Template {
|
||||||
|
outputTmpl := output
|
||||||
|
switch outputTmpl {
|
||||||
|
case "":
|
||||||
|
outputTmpl = defaultTmpl
|
||||||
|
case "csv":
|
||||||
|
outputTmpl = csvTmpl
|
||||||
|
}
|
||||||
|
return template.Must(template.New("tmpl").Funcs(tmplFuncMap).Parse(outputTmpl))
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmplFuncMap = template.FuncMap{
|
||||||
|
"formatNumber": formatNumber,
|
||||||
|
"formatNumberInt": formatNumberInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatNumber(duration float64) string {
|
||||||
|
return fmt.Sprintf("%4.4f", duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatNumberInt(duration int) string {
|
||||||
|
return fmt.Sprintf("%d", duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultTmpl = `
|
||||||
|
Summary:
|
||||||
|
Total: {{ formatNumber .Total.Seconds }} secs
|
||||||
|
Requests/sec: {{ formatNumber .Rps }}
|
||||||
|
|
||||||
|
TotalNumRes: {{ .NumRes }}
|
||||||
|
AvgTotal: {{ .AvgTotal }}
|
||||||
|
`
|
||||||
|
csvTmpl = `{{ $connLats := .ConnLats }}{{ $dnsLats := .DnsLats }}{{ $dnsLats := .DnsLats }}{{ $reqLats := .ReqLats }}{{ $delayLats := .DelayLats }}{{ $resLats := .ResLats }}{{ $statusCodeLats := .StatusCodes }}{{ $offsets := .Offsets}}response-time,DNS+dialup,DNS,Request-write,Response-delay,Response-read,status-code,offset{{ range $i, $v := .Lats }}
|
||||||
|
{{ formatNumber $v }},{{ formatNumber (index $connLats $i) }},{{ formatNumber (index $dnsLats $i) }},{{ formatNumber (index $reqLats $i) }},{{ formatNumber (index $delayLats $i) }},{{ formatNumber (index $resLats $i) }},{{ formatNumberInt (index $statusCodeLats $i) }},{{ formatNumber (index $offsets $i) }}{{ end }}`
|
||||||
|
)
|
||||||
94
src/server/galog/examples/worker/report.go
Normal file
94
src/server/galog/examples/worker/report.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type report struct {
|
||||||
|
avgTotal float64
|
||||||
|
rps float64
|
||||||
|
|
||||||
|
results chan *result
|
||||||
|
done chan bool
|
||||||
|
total time.Duration
|
||||||
|
|
||||||
|
numRes int64
|
||||||
|
output string
|
||||||
|
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newReport(w io.Writer, results chan *result, output string, n int) *report {
|
||||||
|
return &report{
|
||||||
|
output: output,
|
||||||
|
results: results,
|
||||||
|
done: make(chan bool, 1),
|
||||||
|
w: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runReporter(r *report) {
|
||||||
|
// Loop will continue until channel is closed
|
||||||
|
for res := range r.results {
|
||||||
|
r.numRes++
|
||||||
|
r.avgTotal += res.duration.Seconds()
|
||||||
|
}
|
||||||
|
// Signal reporter is done.
|
||||||
|
r.done <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *report) finalize(total time.Duration) {
|
||||||
|
r.total = total
|
||||||
|
r.rps = float64(r.numRes) / r.total.Seconds()
|
||||||
|
r.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *report) print() {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if err := newTemplate(r.output).Execute(buf, r.snapshot()); err != nil {
|
||||||
|
log.Println("error:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.printf(buf.String())
|
||||||
|
|
||||||
|
r.printf("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *report) printf(s string, v ...interface{}) {
|
||||||
|
fmt.Fprintf(r.w, s, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *report) snapshot() Report {
|
||||||
|
snapshot := Report{
|
||||||
|
AvgTotal: r.avgTotal,
|
||||||
|
Rps: r.rps,
|
||||||
|
Total: r.total,
|
||||||
|
NumRes: r.numRes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
type Report struct {
|
||||||
|
AvgTotal float64
|
||||||
|
Rps float64
|
||||||
|
|
||||||
|
Total time.Duration
|
||||||
|
|
||||||
|
NumRes int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type LatencyDistribution struct {
|
||||||
|
Percentage int
|
||||||
|
Latency float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bucket struct {
|
||||||
|
Mark float64
|
||||||
|
Count int
|
||||||
|
Frequency float64
|
||||||
|
}
|
||||||
176
src/server/galog/examples/worker/worker.go
Normal file
176
src/server/galog/examples/worker/worker.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tygit.tuyoo.com/gocomponents/galog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var startTime = time.Now()
|
||||||
|
|
||||||
|
// now returns time.Duration using stdlib time
|
||||||
|
func now() time.Duration { return time.Since(startTime) }
|
||||||
|
|
||||||
|
// Max size of the buffer of result channel.
|
||||||
|
const maxResult = 1000000
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
offset time.Duration
|
||||||
|
duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Work struct {
|
||||||
|
// N is the total number of requests to make.
|
||||||
|
N int
|
||||||
|
|
||||||
|
// C is the concurrency level, the number of concurrent workers to run.
|
||||||
|
C int
|
||||||
|
|
||||||
|
// Qps is the rate limit in queries per second.
|
||||||
|
QPS float64
|
||||||
|
|
||||||
|
// Output represents the output type. If "csv" is provided, the
|
||||||
|
// output will be dumped as a csv stream.
|
||||||
|
Output string
|
||||||
|
|
||||||
|
// Writer is where results will be written. If nil, results are written to stdout.
|
||||||
|
Writer io.Writer
|
||||||
|
|
||||||
|
initOnce sync.Once
|
||||||
|
results chan *result
|
||||||
|
stopCh chan struct{}
|
||||||
|
start time.Duration
|
||||||
|
|
||||||
|
report *report
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Work) writer() io.Writer {
|
||||||
|
if b.Writer == nil {
|
||||||
|
return os.Stdout
|
||||||
|
}
|
||||||
|
return b.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes internal data-structures
|
||||||
|
func (b *Work) Init() {
|
||||||
|
b.initOnce.Do(func() {
|
||||||
|
b.results = make(chan *result, min(b.C*1000, maxResult))
|
||||||
|
b.stopCh = make(chan struct{}, b.C)
|
||||||
|
b.initLogger()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run makes all the requests, prints the summary. It blocks until
|
||||||
|
// all work is done.
|
||||||
|
func (b *Work) Run() {
|
||||||
|
b.Init()
|
||||||
|
b.start = now()
|
||||||
|
b.report = newReport(b.writer(), b.results, b.Output, b.N)
|
||||||
|
// Run the reporter first, it polls the result channel until it is closed.
|
||||||
|
go func() {
|
||||||
|
runReporter(b.report)
|
||||||
|
}()
|
||||||
|
b.runWorkers()
|
||||||
|
b.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Work) Stop() {
|
||||||
|
// Send stop signal so that workers can stop gracefully.
|
||||||
|
for i := 0; i < b.C; i++ {
|
||||||
|
b.stopCh <- struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Work) Finish() {
|
||||||
|
close(b.results)
|
||||||
|
total := now() - b.start
|
||||||
|
// Wait until the reporter is done.
|
||||||
|
<-b.report.done
|
||||||
|
b.report.finalize(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger *galog.GALogger
|
||||||
|
|
||||||
|
func (b *Work) initLogger() {
|
||||||
|
projectID := "28"
|
||||||
|
clientID := "Android_4.827_tyGuest,nearme.nearme.0-hall28.oppo.bydzz"
|
||||||
|
logger, _ = galog.NewGALogger("logs", projectID, clientID, galog.LogTypeAsset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Work) doSth() {
|
||||||
|
s := now()
|
||||||
|
|
||||||
|
// doSth here.
|
||||||
|
asset := galog.AssetProperties{}
|
||||||
|
asset.SetAssetID("13101").
|
||||||
|
SetAssetName("\u9501\u5b9a").
|
||||||
|
SetAssetType("6").
|
||||||
|
SetAssetFinal("2").
|
||||||
|
SetAssetAssociated("3").
|
||||||
|
SetAssetStartTime("0").
|
||||||
|
SetAssetTimeLimit("0").
|
||||||
|
SetAssetSource("").
|
||||||
|
SetKV("uuid", strconv.Itoa(int(time.Now().UnixNano())))
|
||||||
|
logger.
|
||||||
|
GetEntry("asset_increase").
|
||||||
|
SetDeviceID("").
|
||||||
|
SetUserID("10086").
|
||||||
|
SetProperties(asset).
|
||||||
|
Flush()
|
||||||
|
|
||||||
|
t := now()
|
||||||
|
finish := t - s
|
||||||
|
b.results <- &result{
|
||||||
|
offset: s,
|
||||||
|
duration: finish,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Work) runWorker(n int) {
|
||||||
|
var throttle <-chan time.Time
|
||||||
|
if b.QPS > 0 {
|
||||||
|
t := time.NewTicker(time.Duration(1e6/(b.QPS)) * time.Microsecond)
|
||||||
|
throttle = t.C
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
// Check if application is stopped. Do not send into a closed channel.
|
||||||
|
select {
|
||||||
|
case <-b.stopCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if b.QPS > 0 {
|
||||||
|
<-throttle
|
||||||
|
}
|
||||||
|
b.doSth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Work) runWorkers() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(b.C)
|
||||||
|
|
||||||
|
fmt.Printf("==RUN worker==> N:%d C:%d QPS:%v b.N / b.C:%v", b.N, b.C, b.QPS, b.N/b.C)
|
||||||
|
|
||||||
|
// Ignore the case where b.N % b.C != 0.
|
||||||
|
for i := 0; i < b.C; i++ {
|
||||||
|
go func() {
|
||||||
|
b.runWorker(b.N / b.C)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
264
src/server/galog/file.go
Normal file
264
src/server/galog/file.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
package galog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileLogWriter struct {
|
||||||
|
sync.RWMutex
|
||||||
|
|
||||||
|
Rotate bool `json:"rotate"`
|
||||||
|
Hourly bool `json:"hourly"`
|
||||||
|
|
||||||
|
// The opened file
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
fileWriter *os.File
|
||||||
|
|
||||||
|
// Rotate hourly
|
||||||
|
MaxHours int64 `json:"maxhours"` // 默认不删除日志
|
||||||
|
hourlyOpenDate int
|
||||||
|
hourlyOpenTime time.Time
|
||||||
|
|
||||||
|
// Permissions for log file
|
||||||
|
Perm string `json:"perm"`
|
||||||
|
// Permissions for directory if it is specified in FileName
|
||||||
|
DirPerm string `json:"dirperm"`
|
||||||
|
|
||||||
|
RotatePerm string `json:"rotateperm"`
|
||||||
|
|
||||||
|
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileWriter() LogProvider {
|
||||||
|
w := &fileLogWriter{
|
||||||
|
Rotate: true, // 开启日志轮转
|
||||||
|
Hourly: true, // 开启日志小时轮转
|
||||||
|
RotatePerm: "0440",
|
||||||
|
Perm: "0660",
|
||||||
|
DirPerm: "0770",
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init file logger with json config.
|
||||||
|
// jsonConfig like:
|
||||||
|
//
|
||||||
|
// {"filename":"logs/glog.log"}
|
||||||
|
func (w *fileLogWriter) Init(config string) error {
|
||||||
|
err := json.Unmarshal([]byte(config), w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w.Filename == "" {
|
||||||
|
return errors.New("jsonconfig must have filename")
|
||||||
|
}
|
||||||
|
w.suffix = filepath.Ext(w.Filename)
|
||||||
|
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||||
|
if w.suffix == "" {
|
||||||
|
w.suffix = ".log"
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.startLogger()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) startLogger() error {
|
||||||
|
file, err := w.createLogFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w.fileWriter != nil {
|
||||||
|
w.fileWriter.Close()
|
||||||
|
}
|
||||||
|
w.fileWriter = file
|
||||||
|
return w.initFd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) needRotateHourly(hour int) bool {
|
||||||
|
return w.Hourly && hour != w.hourlyOpenDate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||||
|
perm, err := strconv.ParseInt(w.Perm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dirperm, err := strconv.ParseInt(w.DirPerm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath := path.Dir(w.Filename)
|
||||||
|
os.MkdirAll(filepath, os.FileMode(dirperm))
|
||||||
|
|
||||||
|
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||||
|
if err == nil {
|
||||||
|
os.Chmod(w.Filename, os.FileMode(perm))
|
||||||
|
}
|
||||||
|
return fd, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) initFd() error {
|
||||||
|
w.hourlyOpenTime = time.Now()
|
||||||
|
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||||
|
if w.Hourly {
|
||||||
|
go w.hourlyRotate(w.hourlyOpenTime)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
|
||||||
|
y, m, d := openTime.Add(1 * time.Hour).Date()
|
||||||
|
h := openTime.Add(1 * time.Hour).Hour()
|
||||||
|
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
|
||||||
|
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
|
||||||
|
<-tm.C
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateHourly(time.Now().Hour()) {
|
||||||
|
if err := w.doRotate(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) doRotate() error {
|
||||||
|
fName := ""
|
||||||
|
format := ""
|
||||||
|
var openTime time.Time
|
||||||
|
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Lstat(w.Filename)
|
||||||
|
if err != nil {
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Hourly {
|
||||||
|
format = "20060102_15"
|
||||||
|
openTime = w.hourlyOpenTime
|
||||||
|
}
|
||||||
|
|
||||||
|
fName = w.fileNameOnly + fmt.Sprintf("_%s%s", openTime.Format(format), w.suffix)
|
||||||
|
_, err = os.Lstat(fName)
|
||||||
|
|
||||||
|
// return error if the last file checked still existed
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("rotate: cannot find free log number to rename %s", w.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.fileWriter.Close()
|
||||||
|
|
||||||
|
// Rename the file to its new found name
|
||||||
|
// even if occurs error,we MUST guarantee to restart new logger
|
||||||
|
err = os.Rename(w.Filename, fName)
|
||||||
|
if err != nil {
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(fName, os.FileMode(rotatePerm))
|
||||||
|
|
||||||
|
RESTART_LOGGER:
|
||||||
|
|
||||||
|
startLoggerErr := w.startLogger()
|
||||||
|
if w.MaxHours > 0 {
|
||||||
|
go w.deleteOldLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
if startLoggerErr != nil {
|
||||||
|
return fmt.Errorf("rotate startLogger: %s", startLoggerErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rotate: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) deleteOldLog() {
|
||||||
|
dir := filepath.Dir(w.Filename)
|
||||||
|
absolutePath, err := filepath.EvalSymlinks(w.Filename)
|
||||||
|
if err == nil {
|
||||||
|
dir = filepath.Dir(absolutePath)
|
||||||
|
}
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if info == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.Hourly {
|
||||||
|
if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||||
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg encode log msg
|
||||||
|
func (*fileLogWriter) EncodeMsg(lm *LogMsg) []byte {
|
||||||
|
buf := defaultBufPool.Get()
|
||||||
|
buf.AppendString(lm.Msg)
|
||||||
|
buf.AppendString(_defaultLineEnding)
|
||||||
|
msg := buf.Bytes()
|
||||||
|
buf.Free()
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write msg to log and rotate
|
||||||
|
func (w *fileLogWriter) WriteMsg(lm *LogMsg) error {
|
||||||
|
h := lm.When.Hour()
|
||||||
|
|
||||||
|
if w.Rotate {
|
||||||
|
w.RLock()
|
||||||
|
if w.needRotateHourly(h) {
|
||||||
|
w.RUnlock()
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateHourly(h) {
|
||||||
|
if err := w.doRotate(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
} else {
|
||||||
|
w.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := w.EncodeMsg(lm)
|
||||||
|
_, err := w.fileWriter.Write(msg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy close the file description, close file writer.
|
||||||
|
func (w *fileLogWriter) Destroy() {
|
||||||
|
w.fileWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flushes file logger.
|
||||||
|
func (w *fileLogWriter) Flush() {
|
||||||
|
w.fileWriter.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterFile, newFileWriter)
|
||||||
|
}
|
||||||
99
src/server/galog/galog.go
Normal file
99
src/server/galog/galog.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package galog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GALogger struct {
|
||||||
|
logger *Logger
|
||||||
|
projectID string
|
||||||
|
clientID string
|
||||||
|
logType LogType
|
||||||
|
entryPool sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LogTypeTrack 事实日志
|
||||||
|
LogTypeTrack LogType = "track"
|
||||||
|
// LogTypeAsset 资产日志
|
||||||
|
LogTypeAsset LogType = "asset"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// enableAsync 启用异步写入模式
|
||||||
|
enableAsync = true
|
||||||
|
// defaultAsyncMsgLen 异步写入模式的队列长度,QPS较大时可调整此值
|
||||||
|
defaultAsyncMsgLen int64 = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGALogger
|
||||||
|
//
|
||||||
|
// logDir: 日志根目录 logs | /home/tywork
|
||||||
|
// projectID: projectID
|
||||||
|
// clientID: clientID
|
||||||
|
// logType: galog.LogTypeTrack | galog.LogTypeAsset
|
||||||
|
func NewGALogger(logDir, projectID, clientID string, logType LogType) (*GALogger, error) {
|
||||||
|
l := newLogger()
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
filepath := fmt.Sprintf("ga_log/ga_%s.log", hostname)
|
||||||
|
if logType == LogTypeAsset {
|
||||||
|
filepath = fmt.Sprintf("asset_log/%s_%s.log", logType, hostname)
|
||||||
|
}
|
||||||
|
configs := fmt.Sprintf(`{"filename":"%s/%s"}`, strings.TrimRight(logDir, "/"), filepath)
|
||||||
|
err := l.setLogger(AdapterFile, configs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if enableAsync {
|
||||||
|
l.Async()
|
||||||
|
}
|
||||||
|
gaLogger := &GALogger{
|
||||||
|
logger: l,
|
||||||
|
projectID: projectID,
|
||||||
|
clientID: clientID,
|
||||||
|
logType: logType,
|
||||||
|
}
|
||||||
|
|
||||||
|
return gaLogger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntry
|
||||||
|
func (gl *GALogger) GetEntry(event string) *Entry {
|
||||||
|
e, ok := gl.entryPool.Get().(*Entry)
|
||||||
|
if ok {
|
||||||
|
e.logger = gl
|
||||||
|
e.bean.ProjectID = gl.projectID
|
||||||
|
e.bean.LogType = gl.logType
|
||||||
|
e.bean.ClientID = gl.clientID
|
||||||
|
e.bean.Event = event
|
||||||
|
e.bean.EventTime = time.Now().UnixMilli()
|
||||||
|
e.bean.Lib = defaultLib
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return &Entry{
|
||||||
|
logger: gl,
|
||||||
|
bean: &EntryBean{
|
||||||
|
ProjectID: gl.projectID,
|
||||||
|
LogType: gl.logType,
|
||||||
|
ClientID: gl.clientID,
|
||||||
|
Event: event,
|
||||||
|
EventTime: time.Now().UnixMilli(),
|
||||||
|
Lib: defaultLib,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gl *GALogger) biz(v interface{}) {
|
||||||
|
gl.logger.BizErr(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gl *GALogger) putEntry(entry *Entry) {
|
||||||
|
entry.bean = &EntryBean{}
|
||||||
|
gl.entryPool.Put(entry)
|
||||||
|
}
|
||||||
85
src/server/galog/galog_test.go
Normal file
85
src/server/galog/galog_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package galog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
gaLogger *GALogger
|
||||||
|
assetLogger *GALogger
|
||||||
|
)
|
||||||
|
|
||||||
|
// go test -bench . -benchmem
|
||||||
|
func init() {
|
||||||
|
gaLogger, _ = NewGALogger("logs", "20433", "Android_5.0_tyGuest,weixinPay,tyAccount.alipay.0-hall20433.tuyoo.sdktest", LogTypeTrack)
|
||||||
|
assetLogger, _ = NewGALogger("logs", "28", "Android_4.827_tyGuest,nearme.nearme.0-hall28.oppo.bydzz", LogTypeAsset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGaLog(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
props := map[string]interface{}{
|
||||||
|
"ip_address": "127.0.0.1",
|
||||||
|
"proj_app_id": "10010",
|
||||||
|
"uuid": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts": time.Now().UnixNano(),
|
||||||
|
"ip_address1": "127.0.0.1",
|
||||||
|
"proj_app_id1": "10010",
|
||||||
|
"uuid1": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts1": time.Now().UnixNano(),
|
||||||
|
"ip_address2": "127.0.0.1",
|
||||||
|
"proj_app_id2": "10010",
|
||||||
|
"uuid2": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts2": time.Now().UnixNano(),
|
||||||
|
"ip_address3": "127.0.0.1",
|
||||||
|
"proj_app_id3": "10010",
|
||||||
|
"uuid3": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts4": time.Now().UnixNano(),
|
||||||
|
"proj_app_id25": "10010",
|
||||||
|
"uuid21": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts21": time.Now().UnixNano(),
|
||||||
|
"ip_address31": "127.0.0.1",
|
||||||
|
"proj_app_id31": "10010",
|
||||||
|
"uuid31": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts11": time.Now().UnixNano(),
|
||||||
|
"uuid3111": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts4111": time.Now().UnixNano(),
|
||||||
|
"proj_app_id21115": "10010",
|
||||||
|
"uuid111": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts111": time.Now().UnixNano(),
|
||||||
|
"ip_a11ddress31": "127.0.0.1",
|
||||||
|
"proj1_app_id31": "10010",
|
||||||
|
"uuid131": "4951d472-2c46-4fe5-9c4f-c35b6fb53f67",
|
||||||
|
"ts1111": time.Now().UnixNano(),
|
||||||
|
}
|
||||||
|
gaLogger.
|
||||||
|
GetEntry("sdk_s_login_succ").
|
||||||
|
SetDeviceID("device001").
|
||||||
|
SetUserID("10086").
|
||||||
|
SetProperties(props).
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAssetLog(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
asset := make(AssetProperties)
|
||||||
|
asset.
|
||||||
|
SetAssetID("13101").
|
||||||
|
SetAssetName("\u9501\u5b9a").
|
||||||
|
SetAssetType("6").
|
||||||
|
SetAssetFinal("2").
|
||||||
|
SetAssetAssociated("3").
|
||||||
|
SetAssetStartTime("0").
|
||||||
|
SetAssetTimeLimit("0").
|
||||||
|
SetAssetSource("").
|
||||||
|
SetKV("uuid", "uuid-v4")
|
||||||
|
|
||||||
|
assetLogger.
|
||||||
|
GetEntry("asset_increase").
|
||||||
|
SetDeviceID("").
|
||||||
|
SetUserID("10086").
|
||||||
|
SetProperties(asset).
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/server/galog/go.mod
Normal file
3
src/server/galog/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module tygit.tuyoo.com/gocomponents/galog
|
||||||
|
|
||||||
|
go 1.20
|
||||||
BIN
src/server/galog/img/WX20240226-183250@2x.png
Normal file
BIN
src/server/galog/img/WX20240226-183250@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 234 KiB |
BIN
src/server/galog/img/WX20240226-184752@2x.png
Normal file
BIN
src/server/galog/img/WX20240226-184752@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
261
src/server/galog/log.go
Normal file
261
src/server/galog/log.go
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
package galog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AdapterFile = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogMsg struct {
|
||||||
|
Msg string
|
||||||
|
When time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger defines the behavior of a log provider.
|
||||||
|
type LogProvider interface {
|
||||||
|
Init(config string) error
|
||||||
|
WriteMsg(lm *LogMsg) error
|
||||||
|
Destroy()
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
type newLogProviderFunc func() LogProvider
|
||||||
|
|
||||||
|
var (
|
||||||
|
adapters = make(map[string]newLogProviderFunc)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register register a new log provider.
|
||||||
|
func Register(name string, log newLogProviderFunc) {
|
||||||
|
if log == nil {
|
||||||
|
panic("logs: Register provide is nil")
|
||||||
|
}
|
||||||
|
if _, dup := adapters[name]; dup {
|
||||||
|
panic("logs: Register called twice for provider " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = log
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
asynchronous bool
|
||||||
|
wg sync.WaitGroup
|
||||||
|
msgChanLen int64
|
||||||
|
msgChan chan *LogMsg
|
||||||
|
closeChan chan struct{}
|
||||||
|
flushChan chan struct{}
|
||||||
|
outputs []*nameLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
type nameLogger struct {
|
||||||
|
LogProvider
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var logMsgPool *sync.Pool
|
||||||
|
|
||||||
|
// newLogger return a new Logger.
|
||||||
|
func newLogger(channelLens ...int64) *Logger {
|
||||||
|
gl := new(Logger)
|
||||||
|
gl.msgChanLen = append(channelLens, 0)[0]
|
||||||
|
if gl.msgChanLen <= 0 {
|
||||||
|
gl.msgChanLen = defaultAsyncMsgLen
|
||||||
|
}
|
||||||
|
gl.flushChan = make(chan struct{}, 1)
|
||||||
|
gl.closeChan = make(chan struct{}, 1)
|
||||||
|
return gl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async sets the log to asynchronous and start the goroutine
|
||||||
|
func (gl *Logger) Async(msgLen ...int64) *Logger {
|
||||||
|
gl.lock.Lock()
|
||||||
|
defer gl.lock.Unlock()
|
||||||
|
if gl.asynchronous {
|
||||||
|
return gl
|
||||||
|
}
|
||||||
|
gl.asynchronous = true
|
||||||
|
if len(msgLen) > 0 && msgLen[0] > 0 {
|
||||||
|
gl.msgChanLen = msgLen[0]
|
||||||
|
}
|
||||||
|
gl.msgChan = make(chan *LogMsg, gl.msgChanLen)
|
||||||
|
logMsgPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &LogMsg{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
gl.wg.Add(1)
|
||||||
|
go gl.startLogger()
|
||||||
|
return gl
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger provides a given logger adapter into Logger with config string.
|
||||||
|
// config must in in JSON format like {"interval":360}}
|
||||||
|
func (gl *Logger) setLogger(adapterName string, configs ...string) error {
|
||||||
|
config := append(configs, "{}")[0]
|
||||||
|
|
||||||
|
logAdapter, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
lg := logAdapter()
|
||||||
|
|
||||||
|
err := lg.Init(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.outputs = append(gl.outputs, &nameLogger{name: adapterName, LogProvider: lg})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gl *Logger) writeToLoggers(lm *LogMsg) {
|
||||||
|
for _, l := range gl.outputs {
|
||||||
|
err := l.WriteMsg(lm)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (gl *Logger) Write(p []byte) (n int, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
// writeMsg will always add a '\n' character
|
||||||
|
if p[len(p)-1] == '\n' {
|
||||||
|
p = p[0 : len(p)-1]
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Msg: string(p),
|
||||||
|
When: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// set levelLoggerImpl to ensure all log message will be write out
|
||||||
|
err = gl.writeMsg(lm)
|
||||||
|
if err == nil {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gl *Logger) writeMsg(lm *LogMsg) error {
|
||||||
|
if gl.asynchronous {
|
||||||
|
logM := logMsgPool.Get().(*LogMsg)
|
||||||
|
logM.Msg = lm.Msg
|
||||||
|
logM.When = lm.When
|
||||||
|
|
||||||
|
if gl.outputs != nil {
|
||||||
|
gl.msgChan <- lm
|
||||||
|
} else {
|
||||||
|
logMsgPool.Put(lm)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gl.writeToLoggers(lm)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gl *Logger) startLogger() {
|
||||||
|
gameOver := false
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case bm, ok := <-gl.msgChan:
|
||||||
|
if ok {
|
||||||
|
gl.writeToLoggers(bm)
|
||||||
|
logMsgPool.Put(bm)
|
||||||
|
}
|
||||||
|
case <-gl.closeChan:
|
||||||
|
gl.flush()
|
||||||
|
for _, l := range gl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
gl.outputs = nil
|
||||||
|
gameOver = true
|
||||||
|
gl.wg.Done()
|
||||||
|
case <-gl.flushChan:
|
||||||
|
gl.flush()
|
||||||
|
gl.wg.Done()
|
||||||
|
}
|
||||||
|
if gameOver {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info Log INFO level message.
|
||||||
|
func (gl *Logger) Info(format string) {
|
||||||
|
lm := &LogMsg{
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BizErr Log a json-interface
|
||||||
|
func (gl *Logger) BizErr(v interface{}) error {
|
||||||
|
buf, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
str := *(*string)(unsafe.Pointer(&buf))
|
||||||
|
gl.Info(str)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flush all chan data.
|
||||||
|
func (gl *Logger) Flush() {
|
||||||
|
if gl.asynchronous {
|
||||||
|
gl.flushChan <- struct{}{}
|
||||||
|
gl.wg.Wait()
|
||||||
|
gl.wg.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gl.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close logger, flush all chan data and destroy all adapters in Logger.
|
||||||
|
func (gl *Logger) Close() {
|
||||||
|
if gl.asynchronous {
|
||||||
|
gl.closeChan <- struct{}{}
|
||||||
|
gl.wg.Wait()
|
||||||
|
close(gl.msgChan)
|
||||||
|
} else {
|
||||||
|
gl.flush()
|
||||||
|
for _, l := range gl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
gl.outputs = nil
|
||||||
|
}
|
||||||
|
close(gl.flushChan)
|
||||||
|
close(gl.closeChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gl *Logger) flush() {
|
||||||
|
if gl.asynchronous {
|
||||||
|
for {
|
||||||
|
if len(gl.msgChan) > 0 {
|
||||||
|
bm, ok := <-gl.msgChan
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gl.writeToLoggers(bm)
|
||||||
|
logMsgPool.Put(bm)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, l := range gl.outputs {
|
||||||
|
l.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/server/galog/suger.go
Normal file
122
src/server/galog/suger.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package galog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerLogOptions struct {
|
||||||
|
// LogDir 日志目录,默认logs
|
||||||
|
LogDir string
|
||||||
|
|
||||||
|
// EnableAsync 是否开启异步写日志,默认开启
|
||||||
|
EnableAsync bool
|
||||||
|
|
||||||
|
// AsyncQueueSize 异步队列大小,默认1000
|
||||||
|
AsyncQueueSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerLogger 业务日志实例,support JSON
|
||||||
|
//
|
||||||
|
// opts.LogDir 日志目录,推荐logs
|
||||||
|
// opts.EnableAsync 是否开启异步写日志,默认开启
|
||||||
|
// opts.AsyncQueueSize 异步队列大小,默认1000
|
||||||
|
// useage: logger.BizErr(map[string]interface{}{"type": "test"})
|
||||||
|
func NewServerLogger(opts *ServerLogOptions) (*Logger, error) {
|
||||||
|
if opts == nil {
|
||||||
|
opts = &ServerLogOptions{
|
||||||
|
LogDir: "logs",
|
||||||
|
EnableAsync: true,
|
||||||
|
AsyncQueueSize: 1000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opts.LogDir == "" {
|
||||||
|
opts.LogDir = "logs"
|
||||||
|
}
|
||||||
|
if opts.EnableAsync && opts.AsyncQueueSize == 0 {
|
||||||
|
opts.AsyncQueueSize = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
l := newLogger()
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
filename := fmt.Sprintf("server_%s.log", hostname)
|
||||||
|
configs := fmt.Sprintf(`{"filename":"%s/%s"}`, strings.TrimRight(opts.LogDir, "/"), filename)
|
||||||
|
err := l.setLogger(AdapterFile, configs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if opts.EnableAsync && opts.AsyncQueueSize > 0 {
|
||||||
|
l.Async(opts.AsyncQueueSize)
|
||||||
|
}
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerGALogOptions struct {
|
||||||
|
// LogDir 日志目录,默认galogs
|
||||||
|
LogDir string
|
||||||
|
|
||||||
|
// EnableAsync 是否开启异步写日志,默认开启
|
||||||
|
EnableAsync bool
|
||||||
|
|
||||||
|
// AsyncQueueSize 异步队列大小,默认1000
|
||||||
|
AsyncQueueSize int64
|
||||||
|
|
||||||
|
// GA侧分配ProjectID
|
||||||
|
ProjectID string
|
||||||
|
|
||||||
|
// GA侧分配ClientID
|
||||||
|
ClientID string
|
||||||
|
|
||||||
|
LogType LogType
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerGALogger GA日志实例,support JSON
|
||||||
|
//
|
||||||
|
// opts.ProjectID 项目ID,GA侧分配,必填
|
||||||
|
// opts.ClientID 客户端ID,GA侧分配,必填
|
||||||
|
// opts.LogType 日志类型,默认LogTypeTrack
|
||||||
|
// opts.LogDir 日志目录,默认galogs
|
||||||
|
// opts.EnableAsync 是否开启异步写日志,默认开启
|
||||||
|
// opts.AsyncQueueSize 异步队列大小,默认1000
|
||||||
|
func NewServerGALogger(opts *ServerGALogOptions) (*GALogger, error) {
|
||||||
|
if opts == nil {
|
||||||
|
return nil, errors.New("opts must not be empty")
|
||||||
|
}
|
||||||
|
if opts.EnableAsync && opts.AsyncQueueSize == 0 {
|
||||||
|
opts.AsyncQueueSize = 1000
|
||||||
|
}
|
||||||
|
if opts.LogDir == "" {
|
||||||
|
opts.LogDir = "galogs"
|
||||||
|
}
|
||||||
|
if opts.LogType == "" {
|
||||||
|
opts.LogType = LogTypeTrack
|
||||||
|
}
|
||||||
|
if opts.ProjectID == "" || opts.ClientID == "" {
|
||||||
|
return nil, errors.New("projectID or clientID must not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
l := newLogger()
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
filename := fmt.Sprintf("ga_%s.log", hostname)
|
||||||
|
if opts.LogType == LogTypeAsset {
|
||||||
|
filename = fmt.Sprintf("asset_%s.log", hostname)
|
||||||
|
}
|
||||||
|
configs := fmt.Sprintf(`{"filename":"%s/%s"}`, strings.TrimRight(opts.LogDir, "/"), filename)
|
||||||
|
err := l.setLogger(AdapterFile, configs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if opts.EnableAsync && opts.AsyncQueueSize > 0 {
|
||||||
|
l.Async(opts.AsyncQueueSize)
|
||||||
|
}
|
||||||
|
gaLogger := &GALogger{
|
||||||
|
logger: l,
|
||||||
|
projectID: opts.ProjectID,
|
||||||
|
clientID: opts.ClientID,
|
||||||
|
logType: opts.LogType,
|
||||||
|
}
|
||||||
|
|
||||||
|
return gaLogger, nil
|
||||||
|
}
|
||||||
34
src/server/galog/suger_test.go
Normal file
34
src/server/galog/suger_test.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package galog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var slogger *Logger
|
||||||
|
|
||||||
|
// go test -bench . -benchmem
|
||||||
|
func init() {
|
||||||
|
slogger, _ = NewServerLogger(&ServerLogOptions{
|
||||||
|
LogDir: "logs",
|
||||||
|
EnableAsync: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkServerLog-8 436682 3103 ns/op 1838 B/op 24 allocs/op
|
||||||
|
func BenchmarkServerLog(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
msgMap := map[string]interface{}{
|
||||||
|
"CreateTime": time.Now().UnixNano() / int64(time.Microsecond),
|
||||||
|
"Host": "host",
|
||||||
|
"AppId": "10010",
|
||||||
|
"UserId": "12345",
|
||||||
|
"Level": "Notice",
|
||||||
|
"Entry": "Login",
|
||||||
|
"Func": "HandleLogin",
|
||||||
|
"TraceMsg": "HandlerWSFriApplyList|HandlerWSFriApplyList",
|
||||||
|
"Params": "{\"isoCode\":\"CN\",\"P0\":[3,\"Total: 3, End: 0, \"],\"pf\":\"wx\",\"appVer\":\"1.0\",\"sdkVer\":\"1.0\",\"ip\":\"58.247.195.158\",\"clientId\":\"7abd64bf-3fb6-4fef-a25e-e6562b7fb857\",\"timeZone\":\"Asia/Shanghai\",\"loginMark\":\"\",\"st\":1472}",
|
||||||
|
}
|
||||||
|
slogger.BizErr(msgMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -254,6 +254,7 @@ func (c *ChampshipMgr) GetPreRankMsg(Uid int) *proto.ResChampshipPreRank {
|
|||||||
Avatar: int32(SimplePlayer.Avatar),
|
Avatar: int32(SimplePlayer.Avatar),
|
||||||
Face: int32(SimplePlayer.Face),
|
Face: int32(SimplePlayer.Face),
|
||||||
Level: int32(SimplePlayer.Level),
|
Level: int32(SimplePlayer.Level),
|
||||||
|
Type: int32(v.Type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,6 @@ type FirendData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FriendMgr) Init() {
|
func (f *FriendMgr) Init() {
|
||||||
|
|
||||||
gob.Register(card.CardInfo{})
|
gob.Register(card.CardInfo{})
|
||||||
gob.Register(item.Item{})
|
gob.Register(item.Item{})
|
||||||
gob.Register([]*item.Item{}) // 注册 []*item.Item 类型
|
gob.Register([]*item.Item{}) // 注册 []*item.Item 类型
|
||||||
|
|||||||
@ -988,19 +988,6 @@ func NotifyPlayer(Uid int, m *MsgMod.Msg) {
|
|||||||
p.Send(m)
|
p.Send(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setRedisLock(key string, Duration time.Duration) bool {
|
|
||||||
return db.RedisLock(key, "lock", Duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRedisLock(key string) error {
|
|
||||||
_, err := db.RedisGetKey(key)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func unsetRedisLock(key string) {
|
|
||||||
db.RedisUnlock(key, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Destroy() {
|
func Destroy() {
|
||||||
log.Debug("服务器下线")
|
log.Debug("服务器下线")
|
||||||
if G_GameLogicPtr != nil {
|
if G_GameLogicPtr != nil {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -506,7 +507,20 @@ func ReqGmCommand_(player *Player, Command string) error {
|
|||||||
BaseMod.Uid = player.M_DwUin
|
BaseMod.Uid = player.M_DwUin
|
||||||
BaseMod.NickName = player.PlayMod.getBaseMod().NickName
|
BaseMod.NickName = player.PlayMod.getBaseMod().NickName
|
||||||
BaseMod.LoginTime = GoUtil.Now()
|
BaseMod.LoginTime = GoUtil.Now()
|
||||||
player.PlayMod.mod_list = p1.PlayMod.mod_list
|
// deep copy p1.PlayMod.mod_list to avoid sharing internal pointers
|
||||||
|
var modCopy PlayerModList
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := gob.NewEncoder(&buf)
|
||||||
|
if err := enc.Encode(p1.PlayMod.mod_list); err != nil {
|
||||||
|
log.Error("failed to encode mod_list: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dec := gob.NewDecoder(&buf)
|
||||||
|
if err := dec.Decode(&modCopy); err != nil {
|
||||||
|
log.Error("failed to decode mod_list: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
player.PlayMod.mod_list = modCopy
|
||||||
player.PlayMod.mod_list.Base = *BaseMod
|
player.PlayMod.mod_list.Base = *BaseMod
|
||||||
case "orderMerge": // 获取order订单的mergeId
|
case "orderMerge": // 获取order订单的mergeId
|
||||||
OrderMod := player.PlayMod.getOrderMod()
|
OrderMod := player.PlayMod.getOrderMod()
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "server/GoUtil"
|
|
||||||
// "server/MergeConst"
|
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -20,6 +17,7 @@ import (
|
|||||||
miningCfg "server/conf/mining"
|
miningCfg "server/conf/mining"
|
||||||
playroomCfg "server/conf/playroom"
|
playroomCfg "server/conf/playroom"
|
||||||
"server/db"
|
"server/db"
|
||||||
|
"server/ga"
|
||||||
"server/game/mod/activity"
|
"server/game/mod/activity"
|
||||||
"server/game/mod/friend"
|
"server/game/mod/friend"
|
||||||
"server/game/mod/item"
|
"server/game/mod/item"
|
||||||
@ -980,9 +978,11 @@ func (p *Player) UpdateUserInfo() {
|
|||||||
simple.CardInfo = CardMod.GetCardList()
|
simple.CardInfo = CardMod.GetCardList()
|
||||||
simple.ActLog = p.PlayMod.getFriendMod().GetActLogLast()
|
simple.ActLog = p.PlayMod.getFriendMod().GetActLogLast()
|
||||||
simple.Physiology = p.PlayMod.getPlayroomMod().GetPhysiologyList()
|
simple.Physiology = p.PlayMod.getPlayroomMod().GetPhysiologyList()
|
||||||
|
|
||||||
|
//TODO 存储到redis 在新版本中将优化成gob进行压缩
|
||||||
value, _ := json.Marshal(simple)
|
value, _ := json.Marshal(simple)
|
||||||
IdStr := strconv.Itoa(int(p.M_DwUin))
|
IdStr := strconv.Itoa(int(p.M_DwUin))
|
||||||
db.RedisSetKey(IdStr, string(value), 0)
|
go db.RedisSetKeyBytes(IdStr, value, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) HandleInUserRank() {
|
func (p *Player) HandleInUserRank() {
|
||||||
@ -1049,7 +1049,11 @@ func (p *Player) TeLog(Type string, Param map[string]interface{}) {
|
|||||||
if agent != nil {
|
if agent != nil {
|
||||||
Param["Ip"] = agent.RemoteAddr().String()
|
Param["Ip"] = agent.RemoteAddr().String()
|
||||||
}
|
}
|
||||||
telog.Te.Track(p.GetPlayerBaseMod().GetName(), p.GetPlayerBaseMod().GetName(), Type, Param)
|
go telog.Te.Track(p.GetPlayerBaseMod().GetName(), p.GetPlayerBaseMod().GetName(), Type, Param)
|
||||||
|
BaseMod := p.PlayMod.getBaseMod()
|
||||||
|
|
||||||
|
//途游GA
|
||||||
|
go ga.GAlogEvent(Type, BaseMod.Account, "", Param)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Kafka(Type string, Param map[string]interface{}) {
|
func (p *Player) Kafka(Type string, Param map[string]interface{}) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package game
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"server/GoUtil"
|
"server/GoUtil"
|
||||||
|
"server/conf"
|
||||||
"server/db"
|
"server/db"
|
||||||
"server/game/mod/msg"
|
"server/game/mod/msg"
|
||||||
"sort"
|
"sort"
|
||||||
@ -33,7 +34,8 @@ const (
|
|||||||
RANK_TYPE_USER = 1 // 玩家排行榜
|
RANK_TYPE_USER = 1 // 玩家排行榜
|
||||||
RANK_TYPE_GLOBAL = 2 // 全球排行榜
|
RANK_TYPE_GLOBAL = 2 // 全球排行榜
|
||||||
|
|
||||||
RANK_USER = "rank_user" // redis玩家排行榜
|
RANK_USER = "rank_user" // redis玩家排行榜
|
||||||
|
RANK_COUNTRY_USER = "rank_country_user" // redis国家排行榜
|
||||||
)
|
)
|
||||||
|
|
||||||
type Rank struct {
|
type Rank struct {
|
||||||
@ -58,6 +60,24 @@ func (r *RankMgr) Init() {
|
|||||||
r.RegisterHandler(msg.HANDLE_TYPE_RANK, r.inRank)
|
r.RegisterHandler(msg.HANDLE_TYPE_RANK, r.inRank)
|
||||||
r.RegisterHandler(msg.HANDLE_TYPE_RANK_INFO, r.getRankInfo)
|
r.RegisterHandler(msg.HANDLE_TYPE_RANK_INFO, r.getRankInfo)
|
||||||
r.RegisterHandler(msg.SERVER_ZERO_UPDATE, r.ZeroUpdate)
|
r.RegisterHandler(msg.SERVER_ZERO_UPDATE, r.ZeroUpdate)
|
||||||
|
r.version()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RankMgr) version() error {
|
||||||
|
RedisKey := fmt.Sprintf("%s_%s", RANK_COUNTRY_USER, conf.Server.CountryCode)
|
||||||
|
RedisList, _ := db.RedisZRevRangeWithScores(RedisKey, 0, 100)
|
||||||
|
if len(RedisList) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rankList := r.getRank(RANK_TYPE_USER)
|
||||||
|
if len(rankList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, v := range rankList {
|
||||||
|
Uid := strconv.Itoa(v.Uid)
|
||||||
|
db.RedisZAdd(RedisKey, Uid, v.Score)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RankMgr) getData() *RankData {
|
func (r *RankMgr) getData() *RankData {
|
||||||
@ -104,9 +124,14 @@ func (r *RankMgr) setRank(RankType int, data []*Rank) {
|
|||||||
// 获取排行榜信息
|
// 获取排行榜信息
|
||||||
func (r *RankMgr) getRankInfo(m *msg.Msg) (interface{}, error) {
|
func (r *RankMgr) getRankInfo(m *msg.Msg) (interface{}, error) {
|
||||||
data := m.Extra.(RankMsg)
|
data := m.Extra.(RankMsg)
|
||||||
|
// 全球排行榜
|
||||||
if data.RankType == RANK_TYPE_GLOBAL {
|
if data.RankType == RANK_TYPE_GLOBAL {
|
||||||
return r.getRedisRankInfo(m)
|
return r.getRedisRankInfo(m)
|
||||||
}
|
}
|
||||||
|
// 国家排行榜
|
||||||
|
if data.RankType == RANK_TYPE_USER {
|
||||||
|
return r.getRedisCountryRankInfo(m)
|
||||||
|
}
|
||||||
rankList := r.getRank(data.RankType)
|
rankList := r.getRank(data.RankType)
|
||||||
MyRank, MyScore := r.getMyRank(m.From, data.RankType)
|
MyRank, MyScore := r.getMyRank(m.From, data.RankType)
|
||||||
return &RankInfo{
|
return &RankInfo{
|
||||||
@ -116,6 +141,31 @@ func (r *RankMgr) getRankInfo(m *msg.Msg) (interface{}, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RankMgr) getRedisCountryRankInfo(m *msg.Msg) (interface{}, error) {
|
||||||
|
RedisKey := fmt.Sprintf("%s_%s", RANK_COUNTRY_USER, conf.Server.CountryCode)
|
||||||
|
RedisList, err := db.RedisZRevRangeWithScores(RedisKey, 0, 100)
|
||||||
|
if err != nil {
|
||||||
|
return &RankInfo{}, nil
|
||||||
|
}
|
||||||
|
sort.Slice(RedisList, func(i, j int) bool { // 排序 从大到小
|
||||||
|
return RedisList[i].Score > RedisList[j].Score
|
||||||
|
})
|
||||||
|
rankList := make([]*Rank, 0)
|
||||||
|
for _, v := range RedisList {
|
||||||
|
rankList = append(rankList, &Rank{
|
||||||
|
Uid: GoUtil.Int(v.Member),
|
||||||
|
Score: v.Score,
|
||||||
|
Time: GoUtil.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
MyRank, MyScore, _ := db.RedisZRankWithScores(RANK_USER, strconv.Itoa(m.From))
|
||||||
|
return &RankInfo{
|
||||||
|
List: rankList,
|
||||||
|
MyRank: int(MyRank),
|
||||||
|
MyScore: MyScore,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RankMgr) getRedisRankInfo(m *msg.Msg) (interface{}, error) {
|
func (r *RankMgr) getRedisRankInfo(m *msg.Msg) (interface{}, error) {
|
||||||
RedisList, err := db.RedisZRevRangeWithScores(RANK_USER, 0, 100)
|
RedisList, err := db.RedisZRevRangeWithScores(RANK_USER, 0, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -168,17 +218,19 @@ func (r *RankMgr) inRank(m *msg.Msg) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
// if len(rankList) >= 100 {
|
|
||||||
// rankList = rankList[:100]
|
|
||||||
// }
|
|
||||||
r.setRank(data.RankType, rankList)
|
r.setRank(data.RankType, rankList)
|
||||||
if data.RankType == RANK_TYPE_USER {
|
if data.RankType == RANK_TYPE_USER {
|
||||||
|
// 全球玩家排行榜
|
||||||
Uid := strconv.Itoa(data.Uid)
|
Uid := strconv.Itoa(data.Uid)
|
||||||
TimeSort := fmt.Sprintf("0.%d", RANK_TIME_SORT-GoUtil.Now())
|
TimeSort := fmt.Sprintf("0.%d", RANK_TIME_SORT-GoUtil.Now())
|
||||||
TimeSortF, _ := strconv.ParseFloat(TimeSort, 64)
|
TimeSortF, _ := strconv.ParseFloat(TimeSort, 64)
|
||||||
db.RedisZAdd(RANK_USER, Uid, data.Score+TimeSortF)
|
db.RedisZAdd(RANK_USER, Uid, data.Score+TimeSortF)
|
||||||
|
|
||||||
|
// 地区玩家排行榜
|
||||||
|
RedisKey := fmt.Sprintf("%s_%s", RANK_COUNTRY_USER, conf.Server.CountryCode)
|
||||||
|
db.RedisZAdd(RedisKey, Uid, data.Score+TimeSortF)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.update = true
|
r.update = true
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -515,7 +515,7 @@ func ReqDecorate(player *Player, buf []byte) error {
|
|||||||
}
|
}
|
||||||
if DecorateMod.GetAreaId() != AreaId { // 解锁上报
|
if DecorateMod.GetAreaId() != AreaId { // 解锁上报
|
||||||
player.TeLog("plot_unlock", map[string]interface{}{
|
player.TeLog("plot_unlock", map[string]interface{}{
|
||||||
"plot_id": AreaId,
|
"plot_id": DecorateMod.GetAreaId(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
player.TeLog("finish_deco", map[string]interface{}{
|
player.TeLog("finish_deco", map[string]interface{}{
|
||||||
|
|||||||
@ -138,6 +138,9 @@ func TriggerShippingOrderOrigin(player *Player, req *msg.ReqShippingOrder) {
|
|||||||
"PayType": OrderData.PayType,
|
"PayType": OrderData.PayType,
|
||||||
}
|
}
|
||||||
player.Kafka("pay", orderDataMap)
|
player.Kafka("pay", orderDataMap)
|
||||||
|
player.PushClientRes(&msg.ResShippingOrder{
|
||||||
|
Code: msg.RES_CODE_SUCCESS,
|
||||||
|
})
|
||||||
player.SendClientRes()
|
player.SendClientRes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -298,6 +298,15 @@ func UnitTriggerMail(p *Player) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UnitDecorateErr(p *Player) error {
|
||||||
|
DecorateMod := p.PlayMod.getDecorateMod()
|
||||||
|
if len(DecorateMod.FinishList) != DecorateMod.Progress {
|
||||||
|
log.Debug("decorate error uid %d", p.M_DwUin)
|
||||||
|
}
|
||||||
|
log.Debug("decorate ok uid %d", p.M_DwUin)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func UnitPlayroomOrder(p *Player) error {
|
func UnitPlayroomOrder(p *Player) error {
|
||||||
PlayroomMod := p.PlayMod.getPlayroomMod()
|
PlayroomMod := p.PlayMod.getPlayroomMod()
|
||||||
PlayroomMod.CreateOrderReward(100, p.PlayMod.getItemMod())
|
PlayroomMod.CreateOrderReward(100, p.PlayMod.getItemMod())
|
||||||
|
|||||||
@ -184,6 +184,7 @@ func HandleClientReq(args []interface{}) {
|
|||||||
db.UpdateAccountInfoDeviceToDb(accountInfo)
|
db.UpdateAccountInfoDeviceToDb(accountInfo)
|
||||||
p, _ := internal.Agents.Load(a)
|
p, _ := internal.Agents.Load(a)
|
||||||
if p != nil {
|
if p != nil {
|
||||||
|
p.(*Player).PlayMod.getBaseMod().DiviceId = detail.Device //加锁
|
||||||
p.(*Player).PushClientRes(ResLogin)
|
p.(*Player).PushClientRes(ResLogin)
|
||||||
p.(*Player).LoginBackData()
|
p.(*Player).LoginBackData()
|
||||||
G_GameLogicPtr.AddLog(&Log{
|
G_GameLogicPtr.AddLog(&Log{
|
||||||
|
|||||||
@ -43,6 +43,7 @@ type Base struct {
|
|||||||
IdCardName string
|
IdCardName string
|
||||||
IdCardNum string
|
IdCardNum string
|
||||||
AddCode string // 用于添加好友的code
|
AddCode string // 用于添加好友的code
|
||||||
|
DiviceId string // 设备id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) InitData(Uid int, Ip string) {
|
func (b *Base) InitData(Uid int, Ip string) {
|
||||||
|
|||||||
@ -27,6 +27,7 @@ const (
|
|||||||
EVENT_TYPE_CAT_TRICK = 14 // 猫咪戏法
|
EVENT_TYPE_CAT_TRICK = 14 // 猫咪戏法
|
||||||
EVENT_TYPE_PAYBACK_DAY = 15 // 回收日
|
EVENT_TYPE_PAYBACK_DAY = 15 // 回收日
|
||||||
EVENT_TYPE_LITTLE_APPRENTICE = 16 // 小学徒
|
EVENT_TYPE_LITTLE_APPRENTICE = 16 // 小学徒
|
||||||
|
EVENT_TYPE_CAT_DAY_SALE = 17 // 猫咪大甩卖
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -46,6 +47,7 @@ type LimitedTimeEventMod struct {
|
|||||||
BonusNum int
|
BonusNum int
|
||||||
First bool
|
First bool
|
||||||
FirstReward bool
|
FirstReward bool
|
||||||
|
CatDaySale bool // 是否参与猫咪大甩卖活动
|
||||||
}
|
}
|
||||||
|
|
||||||
type LTEInfo struct {
|
type LTEInfo struct {
|
||||||
|
|||||||
@ -18,7 +18,6 @@ require (
|
|||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/segmentio/kafka-go v0.4.47
|
github.com/segmentio/kafka-go v0.4.47
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
github.com/vicanso/go-charts/v2 v2.6.10
|
|
||||||
google.golang.org/protobuf v1.36.2
|
google.golang.org/protobuf v1.36.2
|
||||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
|
||||||
)
|
)
|
||||||
@ -35,9 +34,7 @@ require (
|
|||||||
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
||||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.15.9 // indirect
|
github.com/klauspost/compress v1.15.9 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
@ -47,9 +44,7 @@ require (
|
|||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||||
github.com/wcharczuk/go-chart/v2 v2.1.0 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
|
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
|
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
@ -68,4 +63,7 @@ require (
|
|||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
github.com/tuyou/galog v0.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/tuyou/galog => ./galog
|
||||||
|
|||||||
@ -92,8 +92,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
@ -103,8 +101,6 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@ -189,10 +185,6 @@ github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZ
|
|||||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||||
github.com/vicanso/go-charts/v2 v2.6.10 h1:Nb2YBekEbUBPbvohnUO1oYMy31v75brUPk6n/fq+JXw=
|
|
||||||
github.com/vicanso/go-charts/v2 v2.6.10/go.mod h1:Ii2KDI3udTG1wPtiTnntzjlUBJVJTqNscMzh3oYHzUk=
|
|
||||||
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
|
|
||||||
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
|
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
@ -219,8 +211,6 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM
|
|||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
|
|
||||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
|||||||
@ -13558,6 +13558,7 @@ type ResPlayerRank struct {
|
|||||||
Avatar int32 `protobuf:"varint,4,opt,name=Avatar,proto3" json:"Avatar,omitempty"`
|
Avatar int32 `protobuf:"varint,4,opt,name=Avatar,proto3" json:"Avatar,omitempty"`
|
||||||
Level int32 `protobuf:"varint,5,opt,name=Level,proto3" json:"Level,omitempty"`
|
Level int32 `protobuf:"varint,5,opt,name=Level,proto3" json:"Level,omitempty"`
|
||||||
Score float32 `protobuf:"fixed32,6,opt,name=score,proto3" json:"score,omitempty"`
|
Score float32 `protobuf:"fixed32,6,opt,name=score,proto3" json:"score,omitempty"`
|
||||||
|
Type int32 `protobuf:"varint,7,opt,name=type,proto3" json:"type,omitempty"` // 排行类型 0:玩家 2:机器人
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@ -13634,6 +13635,13 @@ func (x *ResPlayerRank) GetScore() float32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ResPlayerRank) GetType() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Type
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
type ResFriendLog struct {
|
type ResFriendLog struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Player *ResPlayerSimple `protobuf:"bytes,1,opt,name=Player,proto3" json:"Player,omitempty"`
|
Player *ResPlayerSimple `protobuf:"bytes,1,opt,name=Player,proto3" json:"Player,omitempty"`
|
||||||
@ -28130,14 +28138,15 @@ const file_proto_Gameapi_proto_rawDesc = "" +
|
|||||||
"\x06ActLog\x12\x12\n" +
|
"\x06ActLog\x12\x12\n" +
|
||||||
"\x04Type\x18\x01 \x01(\x05R\x04Type\x12\x12\n" +
|
"\x04Type\x18\x01 \x01(\x05R\x04Type\x12\x12\n" +
|
||||||
"\x04Time\x18\x02 \x01(\x03R\x04Time\x12\x14\n" +
|
"\x04Time\x18\x02 \x01(\x03R\x04Time\x12\x14\n" +
|
||||||
"\x05Param\x18\x03 \x01(\tR\x05Param\"\x8d\x01\n" +
|
"\x05Param\x18\x03 \x01(\tR\x05Param\"\xa1\x01\n" +
|
||||||
"\rResPlayerRank\x12\x10\n" +
|
"\rResPlayerRank\x12\x10\n" +
|
||||||
"\x03Uid\x18\x01 \x01(\x03R\x03Uid\x12\x12\n" +
|
"\x03Uid\x18\x01 \x01(\x03R\x03Uid\x12\x12\n" +
|
||||||
"\x04Name\x18\x02 \x01(\tR\x04Name\x12\x12\n" +
|
"\x04Name\x18\x02 \x01(\tR\x04Name\x12\x12\n" +
|
||||||
"\x04Face\x18\x03 \x01(\x05R\x04Face\x12\x16\n" +
|
"\x04Face\x18\x03 \x01(\x05R\x04Face\x12\x16\n" +
|
||||||
"\x06Avatar\x18\x04 \x01(\x05R\x06Avatar\x12\x14\n" +
|
"\x06Avatar\x18\x04 \x01(\x05R\x06Avatar\x12\x14\n" +
|
||||||
"\x05Level\x18\x05 \x01(\x05R\x05Level\x12\x14\n" +
|
"\x05Level\x18\x05 \x01(\x05R\x05Level\x12\x14\n" +
|
||||||
"\x05score\x18\x06 \x01(\x02R\x05score\"\xa7\x01\n" +
|
"\x05score\x18\x06 \x01(\x02R\x05score\x12\x12\n" +
|
||||||
|
"\x04type\x18\a \x01(\x05R\x04type\"\xa7\x01\n" +
|
||||||
"\fResFriendLog\x121\n" +
|
"\fResFriendLog\x121\n" +
|
||||||
"\x06Player\x18\x01 \x01(\v2\x19.tutorial.ResPlayerSimpleR\x06Player\x12\x12\n" +
|
"\x06Player\x18\x01 \x01(\v2\x19.tutorial.ResPlayerSimpleR\x06Player\x12\x12\n" +
|
||||||
"\x04Type\x18\x02 \x01(\x05R\x04Type\x12\x12\n" +
|
"\x04Type\x18\x02 \x01(\x05R\x04Type\x12\x12\n" +
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user