diff --git a/.gitignore b/.gitignore index 321ac30c..e65e11c5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ src/server/gamedata/config/*.json src/server/unit_test.go src/server/teLog/* src/server/teLog/log.2024-11-28 +src/server/logs/ga_log/*.log diff --git a/src/server/conf/json.go b/src/server/conf/json.go index 936a4f4c..471018f0 100644 --- a/src/server/conf/json.go +++ b/src/server/conf/json.go @@ -32,6 +32,11 @@ var Server struct { RedisPwd string RedisDb int + RedisWriteAddr string // 主写地址(host:port 或 单独 host, 仍兼容旧 RedisAddr/RedisPort) + RedisReadAddrs string // 只读地址,逗号分隔(host:port,...) + RedisMasterName string // 哨兵模式下的 master 名称 + RedisConnType string // "Direct" 或 "Sentinel" + GameName string ServerType string diff --git a/src/server/conf/language/languageCfg.go b/src/server/conf/language/languageCfg.go index 979bebf9..b6782225 100644 --- a/src/server/conf/language/languageCfg.go +++ b/src/server/conf/language/languageCfg.go @@ -21,6 +21,8 @@ func GetLanguage(lang msg.LANG_TYPE, key string) string { switch lang { case msg.LANG_TYPE_LANG_EN: return gamedata.GetStringValue(data, "English") + case msg.LANG_TYPE_LANG_PTBR: + return gamedata.GetStringValue(data, "pt_BR") default: return key } diff --git a/src/server/conf/server.json b/src/server/conf/server.json index 4f0f78d4..e139badd 100644 --- a/src/server/conf/server.json +++ b/src/server/conf/server.json @@ -25,9 +25,15 @@ "ServerCenter" : 1, "GameConfPath": "D:/Github/pet_home_server/src/server/gamedata/config/", - "RedisAddr":"127.0.0.1", - "RedisPort" :"6379", + "RedisAddr":"127.0.0.1", + "RedisPort" :"6379", "RedisPwd" :"", + + "RedisWriteAddr":"127.0.0.1:6379", + "RedisReadAddrs":"127.0.0.1:6379", + "RedisMasterName":"mymaster", + "RedisConnType":"Direct", + "GoogleVerify":false, "RemoteAddr":"host.docker.internal:9001", "Partition":3, diff --git a/src/server/db/Mysql.go b/src/server/db/Mysql.go index 309d5cdc..43f835ca 100644 --- a/src/server/db/Mysql.go +++ b/src/server/db/Mysql.go @@ -1,7 +1,6 @@ package db import ( - //"database/sql" "fmt" "reflect" "server/GoUtil" @@ -9,6 +8,8 @@ import ( "server/conf" "server/pkg/github.com/name5566/leaf/log" "strings" + "sync" + "time" // "server/game" _ "github.com/go-sql-driver/mysql" @@ -23,14 +24,60 @@ type user struct { } 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() { //"用户名:密码@[连接方式](主机名:端口号)/数据库名" - 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) - SqlDb = sqlx.MustConnect("mysql", connect) // 设置连接数据库的参数 - SqlDb.SetMaxOpenConns(20) // 设置最大打开的连接数 + db, err := connectMySQL() + if err != nil { + log.Debug("connect mysql failed: %v", err) + return + } + SqlDb = db 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) { diff --git a/src/server/db/Redis.go b/src/server/db/Redis.go index 850f2ec0..f8f59896 100644 --- a/src/server/db/Redis.go +++ b/src/server/db/Redis.go @@ -4,6 +4,7 @@ import ( "context" "server/conf" "server/pkg/github.com/name5566/leaf/log" + "strings" "time" "github.com/redis/go-redis/v9" @@ -11,34 +12,118 @@ import ( 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{ - Addr: conf.Server.RedisAddr + ":" + conf.Server.RedisPort, - Password: conf.Server.RedisPwd, // no password set + Addr: addr, + Password: conf.Server.RedisPwd, DB: conf.Server.RedisDb, }) - - _, err := rdb.Ping(ctx).Result() - if err != nil { - log.Debug("连接redis出错,错误信息:%v", err) - return + if _, err := rdb.Ping(ctx).Result(); err != nil { + return nil, err } - log.Debug("成功连接redis") - Rdb = rdb + return rdb, nil } +// 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) { - 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 { 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 { - 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 { log.Debug("redis lock failed, err:%v\n", err) return false @@ -46,8 +131,12 @@ func RedisLock(key string, value string, expiration time.Duration) bool { return ok } -// 释放锁 +// 释放锁(写) func RedisUnlock(key string, value string) bool { + if RdbWrite == nil { + log.Debug("redis write client is nil") + return false + } script := ` if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) @@ -55,7 +144,7 @@ func RedisUnlock(key string, value string) bool { return 0 end ` - result, err := Rdb.Eval(ctx, script, []string{key}, value).Result() + result, err := RdbWrite.Eval(ctx, script, []string{key}, value).Result() if err != nil { log.Debug("redis unlock failed, err:%v\n", err) return false @@ -63,8 +152,12 @@ func RedisUnlock(key string, value string) bool { return result.(int64) == 1 } +// 读操作使用 RdbRead 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 { return "", err } @@ -72,21 +165,32 @@ func RedisGetKey(key string) (string, error) { } 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 { log.Debug("redis del failed, err:%v\n", err) } } 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 { log.Debug("redis zadd failed, err:%v\n", err) } } 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 { 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) { - 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 { 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) { - 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 { return 0, 0, err } - score, err := Rdb.ZScore(ctx, key, member).Result() + score, err := RdbRead.ZScore(ctx, key, member).Result() if err != nil { return 0, 0, err } @@ -114,7 +224,11 @@ func RedisZRankWithScores(key, member string) (int64, float64, error) { } 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 { log.Debug("redis del failed, err:%v\n", err) } diff --git a/src/server/db/SqlStruct.go b/src/server/db/SqlStruct.go index 2526a52f..ecfe8238 100644 --- a/src/server/db/SqlStruct.go +++ b/src/server/db/SqlStruct.go @@ -481,6 +481,9 @@ type SqlServerMailStruct struct { SubTitleEn string `db:"subTitle_en"` TitleEn string `db:"title_en"` ContentEn string `db:"content_en"` + TitlePtBr string `db:"title_ptbr"` + SubTitlePtBr string `db:"subTitle_ptbr"` + ContentPtBr string `db:"content_ptbr"` Items string `db:"items"` Start_time int64 `db:"start_time"` Register_time int64 `db:"register_time"` diff --git a/src/server/ga/log.go b/src/server/ga/log.go new file mode 100644 index 00000000..18fc609f --- /dev/null +++ b/src/server/ga/log.go @@ -0,0 +1,36 @@ +package ga + +import ( + "fmt" + + 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] = fmt.Sprintf("%v", v) + } + properties = newProperties + glogger. + GetEntry(event). + SetDeviceID(deviceID). + SetUserID(userID). + SetProperties(properties). + Flush() +} diff --git a/src/server/galog/.gitignore b/src/server/galog/.gitignore new file mode 100644 index 00000000..e3c5d3e4 --- /dev/null +++ b/src/server/galog/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.vscode/ +.idea/ +*.log +logs/ +ga_log/ +asset_log/ \ No newline at end of file diff --git a/src/server/galog/README.md b/src/server/galog/README.md new file mode 100644 index 00000000..70e08d0b --- /dev/null +++ b/src/server/galog/README.md @@ -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%。 + +- 监控结果如下: +![img2.png](img/WX20240226-183250@2x.png) +![img2.png](img/WX20240226-184752@2x.png) + +## 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 | \ No newline at end of file diff --git a/src/server/galog/buffer.go b/src/server/galog/buffer.go new file mode 100644 index 00000000..51b0e15d --- /dev/null +++ b/src/server/galog/buffer.go @@ -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) +} diff --git a/src/server/galog/entry.go b/src/server/galog/entry.go new file mode 100644 index 00000000..f0a43587 --- /dev/null +++ b/src/server/galog/entry.go @@ -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 +} diff --git a/src/server/galog/examples/hey.go b/src/server/galog/examples/hey.go new file mode 100644 index 00000000..80c7ffe8 --- /dev/null +++ b/src/server/galog/examples/hey.go @@ -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...] + +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) +} diff --git a/src/server/galog/examples/main.go b/src/server/galog/examples/main.go new file mode 100644 index 00000000..c8944256 --- /dev/null +++ b/src/server/galog/examples/main.go @@ -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) + } +} diff --git a/src/server/galog/examples/worker/print.go b/src/server/galog/examples/worker/print.go new file mode 100644 index 00000000..9e3f0bd7 --- /dev/null +++ b/src/server/galog/examples/worker/print.go @@ -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 }}` +) diff --git a/src/server/galog/examples/worker/report.go b/src/server/galog/examples/worker/report.go new file mode 100644 index 00000000..d644e2a9 --- /dev/null +++ b/src/server/galog/examples/worker/report.go @@ -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 +} diff --git a/src/server/galog/examples/worker/worker.go b/src/server/galog/examples/worker/worker.go new file mode 100644 index 00000000..e5e4ef17 --- /dev/null +++ b/src/server/galog/examples/worker/worker.go @@ -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 +} diff --git a/src/server/galog/file.go b/src/server/galog/file.go new file mode 100644 index 00000000..f7412225 --- /dev/null +++ b/src/server/galog/file.go @@ -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) +} diff --git a/src/server/galog/galog.go b/src/server/galog/galog.go new file mode 100644 index 00000000..da1241c0 --- /dev/null +++ b/src/server/galog/galog.go @@ -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) +} diff --git a/src/server/galog/galog_test.go b/src/server/galog/galog_test.go new file mode 100644 index 00000000..805cc5c4 --- /dev/null +++ b/src/server/galog/galog_test.go @@ -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() + } +} diff --git a/src/server/galog/go.mod b/src/server/galog/go.mod new file mode 100644 index 00000000..f044e2b2 --- /dev/null +++ b/src/server/galog/go.mod @@ -0,0 +1,3 @@ +module tygit.tuyoo.com/gocomponents/galog + +go 1.20 diff --git a/src/server/galog/img/WX20240226-183250@2x.png b/src/server/galog/img/WX20240226-183250@2x.png new file mode 100644 index 00000000..8bab3f3c Binary files /dev/null and b/src/server/galog/img/WX20240226-183250@2x.png differ diff --git a/src/server/galog/img/WX20240226-184752@2x.png b/src/server/galog/img/WX20240226-184752@2x.png new file mode 100644 index 00000000..318933f6 Binary files /dev/null and b/src/server/galog/img/WX20240226-184752@2x.png differ diff --git a/src/server/galog/log.go b/src/server/galog/log.go new file mode 100644 index 00000000..4294bfb7 --- /dev/null +++ b/src/server/galog/log.go @@ -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() + } +} diff --git a/src/server/galog/suger.go b/src/server/galog/suger.go new file mode 100644 index 00000000..6d4a0bc7 --- /dev/null +++ b/src/server/galog/suger.go @@ -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 +} diff --git a/src/server/galog/suger_test.go b/src/server/galog/suger_test.go new file mode 100644 index 00000000..7ee2ebb8 --- /dev/null +++ b/src/server/galog/suger_test.go @@ -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) + } +} diff --git a/src/server/game/ChampshipMgr.go b/src/server/game/ChampshipMgr.go index 86a0db04..8b5d029f 100644 --- a/src/server/game/ChampshipMgr.go +++ b/src/server/game/ChampshipMgr.go @@ -254,6 +254,7 @@ func (c *ChampshipMgr) GetPreRankMsg(Uid int) *proto.ResChampshipPreRank { Avatar: int32(SimplePlayer.Avatar), Face: int32(SimplePlayer.Face), Level: int32(SimplePlayer.Level), + Type: int32(v.Type), } } } diff --git a/src/server/game/FriendMgr.go b/src/server/game/FriendMgr.go index ba7ecef6..677de96e 100644 --- a/src/server/game/FriendMgr.go +++ b/src/server/game/FriendMgr.go @@ -25,7 +25,6 @@ type FirendData struct { } func (f *FriendMgr) Init() { - gob.Register(card.CardInfo{}) gob.Register(item.Item{}) gob.Register([]*item.Item{}) // 注册 []*item.Item 类型 diff --git a/src/server/game/GameLogic.go b/src/server/game/GameLogic.go index 1fc2ff4e..373faf68 100644 --- a/src/server/game/GameLogic.go +++ b/src/server/game/GameLogic.go @@ -988,19 +988,6 @@ func NotifyPlayer(Uid int, m *MsgMod.Msg) { 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() { log.Debug("服务器下线") if G_GameLogicPtr != nil { diff --git a/src/server/game/Gm.go b/src/server/game/Gm.go index 2e92f530..d8c639c3 100644 --- a/src/server/game/Gm.go +++ b/src/server/game/Gm.go @@ -1,6 +1,7 @@ package game import ( + "bytes" "encoding/gob" "fmt" "os" @@ -509,7 +510,20 @@ func ReqGmCommand_(player *Player, Command string) error { BaseMod.Uid = player.M_DwUin BaseMod.NickName = player.PlayMod.getBaseMod().NickName 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 case "orderMerge": // 获取order订单的mergeId OrderMod := player.PlayMod.getOrderMod() diff --git a/src/server/game/MailMgr.go b/src/server/game/MailMgr.go index ce5195bf..b8b0ac84 100644 --- a/src/server/game/MailMgr.go +++ b/src/server/game/MailMgr.go @@ -30,6 +30,8 @@ type ServerMail struct { Content string TitleEn string ContentEn string + TitlePtBr string + ContentPtBr string Items []*item.Item Start_time int64 Register_time int64 @@ -80,6 +82,8 @@ func (r *MailMgr) LoadMail(msg *msg.Msg) (interface{}, error) { Content: v.Content, TitleEn: v.TitleEn, ContentEn: v.ContentEn, + TitlePtBr: v.TitlePtBr, + ContentPtBr: v.ContentPtBr, Items: item.ParseItem(items), Start_time: v.Start_time, Register_time: v.Register_time, diff --git a/src/server/game/Player.go b/src/server/game/Player.go index 5b32e2f6..4bb2a5b9 100644 --- a/src/server/game/Player.go +++ b/src/server/game/Player.go @@ -1,9 +1,6 @@ package game import ( - // "server/GoUtil" - // "server/MergeConst" - "context" "database/sql" "encoding/json" @@ -20,6 +17,7 @@ import ( miningCfg "server/conf/mining" playroomCfg "server/conf/playroom" "server/db" + "server/ga" "server/game/mod/activity" "server/game/mod/friend" "server/game/mod/item" @@ -980,9 +978,11 @@ func (p *Player) UpdateUserInfo() { simple.CardInfo = CardMod.GetCardList() simple.ActLog = p.PlayMod.getFriendMod().GetActLogLast() simple.Physiology = p.PlayMod.getPlayroomMod().GetPhysiologyList() + + //TODO 存储到redis 在新版本中将优化成gob进行压缩 value, _ := json.Marshal(simple) IdStr := strconv.Itoa(int(p.M_DwUin)) - db.RedisSetKey(IdStr, string(value), 0) + go db.RedisSetKeyBytes(IdStr, value, 0) } func (p *Player) HandleInUserRank() { @@ -1050,7 +1050,11 @@ func (p *Player) TeLog(Type string, Param map[string]interface{}) { Param["Ip"] = agent.RemoteAddr().String() } Param["#zone_offset"] = -5 - 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{}) { diff --git a/src/server/game/PlayerFunc.go b/src/server/game/PlayerFunc.go index c35a9ef5..3ff6f698 100644 --- a/src/server/game/PlayerFunc.go +++ b/src/server/game/PlayerFunc.go @@ -497,7 +497,16 @@ func SyncMailMsg(p *Player) { continue } MailMod.ServerMail = append(MailMod.ServerMail, v.Id) - MailMod.Send(v.Title, "", v.Content, v.TitleEn, "", v.ContentEn, v.Items, v.Mail_type) + MailMod.SendMail(&mail.MailStruct{ + Title: v.Title, + Content: v.Content, + TitleEn: v.TitleEn, + ContentEn: v.ContentEn, + Items: v.Items, + Type: v.Mail_type, + TitlePtBr: v.TitlePtBr, + ContentPtBr: v.ContentPtBr, + }) } p.PushClientRes(MailMod.BackData()) } diff --git a/src/server/game/RankMgr.go b/src/server/game/RankMgr.go index df44cfc1..fc576794 100644 --- a/src/server/game/RankMgr.go +++ b/src/server/game/RankMgr.go @@ -3,6 +3,7 @@ package game import ( "fmt" "server/GoUtil" + "server/conf" "server/db" "server/game/mod/msg" "sort" @@ -33,7 +34,8 @@ const ( RANK_TYPE_USER = 1 // 玩家排行榜 RANK_TYPE_GLOBAL = 2 // 全球排行榜 - RANK_USER = "rank_user" // redis玩家排行榜 + RANK_USER = "rank_user" // redis玩家排行榜 + RANK_COUNTRY_USER = "rank_country_user" // redis国家排行榜 ) 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_INFO, r.getRankInfo) 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 { @@ -104,9 +124,14 @@ func (r *RankMgr) setRank(RankType int, data []*Rank) { // 获取排行榜信息 func (r *RankMgr) getRankInfo(m *msg.Msg) (interface{}, error) { data := m.Extra.(RankMsg) + // 全球排行榜 if data.RankType == RANK_TYPE_GLOBAL { return r.getRedisRankInfo(m) } + // 国家排行榜 + if data.RankType == RANK_TYPE_USER { + return r.getRedisCountryRankInfo(m) + } rankList := r.getRank(data.RankType) MyRank, MyScore := r.getMyRank(m.From, data.RankType) return &RankInfo{ @@ -116,6 +141,31 @@ func (r *RankMgr) getRankInfo(m *msg.Msg) (interface{}, error) { }, 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) { RedisList, err := db.RedisZRevRangeWithScores(RANK_USER, 0, 100) if err != nil { @@ -168,17 +218,19 @@ func (r *RankMgr) inRank(m *msg.Msg) (interface{}, error) { } return false }) - - // if len(rankList) >= 100 { - // rankList = rankList[:100] - // } r.setRank(data.RankType, rankList) if data.RankType == RANK_TYPE_USER { + // 全球玩家排行榜 Uid := strconv.Itoa(data.Uid) TimeSort := fmt.Sprintf("0.%d", RANK_TIME_SORT-GoUtil.Now()) TimeSortF, _ := strconv.ParseFloat(TimeSort, 64) 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 return nil, nil } diff --git a/src/server/game/RegisterNetworkFunc.go b/src/server/game/RegisterNetworkFunc.go index aa84aac9..6e1be934 100644 --- a/src/server/game/RegisterNetworkFunc.go +++ b/src/server/game/RegisterNetworkFunc.go @@ -515,7 +515,7 @@ func ReqDecorate(player *Player, buf []byte) error { } if DecorateMod.GetAreaId() != AreaId { // 解锁上报 player.TeLog("plot_unlock", map[string]interface{}{ - "plot_id": AreaId, + "plot_id": DecorateMod.GetAreaId(), }) } player.TeLog("finish_deco", map[string]interface{}{ @@ -2758,6 +2758,7 @@ func ReqCreateOrderSn(player *Player, buf []byte) error { // 订单发货 func ReqShippingOrder(player *Player, buf []byte) error { + return nil req := &msg.ReqShippingOrder{} proto.Unmarshal(buf, req) diff --git a/src/server/game/Trigger.go b/src/server/game/Trigger.go index 8186412b..f68c6ed8 100644 --- a/src/server/game/Trigger.go +++ b/src/server/game/Trigger.go @@ -14,6 +14,7 @@ import ( "server/db" "server/game/mod/chess" "server/game/mod/item" + "server/game/mod/mail" "server/game/mod/order" "server/game/mod/quest" "server/msg" @@ -50,7 +51,22 @@ func (player *Player) MailTrigger(Tr *quest.Trigger) bool { Content := languageCfg.GetLanguage(msg.LANG_TYPE_LANG_CN, v.Content) TitleEn := languageCfg.GetLanguage(msg.LANG_TYPE_LANG_EN, v.Title) ContentEn := languageCfg.GetLanguage(msg.LANG_TYPE_LANG_EN, v.Content) - MailMod.Send(Title, "", Content, TitleEn, "", ContentEn, v.Items, v.Type) + TitlePtBr := languageCfg.GetLanguage(msg.LANG_TYPE_LANG_PTBR, v.Title) + ContentPtBr := languageCfg.GetLanguage(msg.LANG_TYPE_LANG_PTBR, v.Content) + + MailMod.SendMail(&mail.MailStruct{ + Title: Title, + SubTitle: "", + Content: Content, + TitleEn: TitleEn, + SubTitleEn: "", + ContentEn: ContentEn, + TitlePtBr: TitlePtBr, + SubTitlePtBr: "", + ContentPtBr: ContentPtBr, + Items: v.Items, + Type: v.Type, + }) MailMod.AddTriggerMail(v.Id) tr = true } @@ -138,6 +154,9 @@ func TriggerShippingOrderOrigin(player *Player, req *msg.ReqShippingOrder) { "PayType": OrderData.PayType, } player.Kafka("pay", orderDataMap) + player.PushClientRes(&msg.ResShippingOrder{ + Code: msg.RES_CODE_SUCCESS, + }) player.SendClientRes() } diff --git a/src/server/game/UnitTest.go b/src/server/game/UnitTest.go index a8eb4b4f..513e40b0 100644 --- a/src/server/game/UnitTest.go +++ b/src/server/game/UnitTest.go @@ -298,6 +298,15 @@ func UnitTriggerMail(p *Player) error { 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 { PlayroomMod := p.PlayMod.getPlayroomMod() PlayroomMod.CreateOrderReward(100, p.PlayMod.getItemMod()) diff --git a/src/server/game/external.go b/src/server/game/external.go index bd3dd209..4cb6f5fa 100644 --- a/src/server/game/external.go +++ b/src/server/game/external.go @@ -133,13 +133,6 @@ func HandleClientReq(args []interface{}) { gl.PackResInfo(a, "ResRegisterAccount", data) break } - if strings.Count(detail.UserName, "")-1 < 6 { - ResRegisterAccount := &msg.ResRegisterAccount{} - ResRegisterAccount.ResultCode = MergeConst.Protocol_Error_Account_OR_PWD_Short - data, _ := proto.Marshal(ResRegisterAccount) - gl.PackResInfo(a, "ResRegisterAccount", data) - break - } gl.Db_AccountInfo.UserName = detail.UserName gl.Db_AccountInfo.UserPassword = detail.UserPwd if !gl.NewAccountInsertDataToDB() { @@ -184,6 +177,7 @@ func HandleClientReq(args []interface{}) { db.UpdateAccountInfoDeviceToDb(accountInfo) p, _ := internal.Agents.Load(a) if p != nil { + p.(*Player).PlayMod.getBaseMod().DiviceId = detail.Device //加锁 p.(*Player).PushClientRes(ResLogin) p.(*Player).LoginBackData() G_GameLogicPtr.AddLog(&Log{ diff --git a/src/server/game/mod/base/Base.go b/src/server/game/mod/base/Base.go index a060a802..9c155784 100644 --- a/src/server/game/mod/base/Base.go +++ b/src/server/game/mod/base/Base.go @@ -43,6 +43,7 @@ type Base struct { IdCardName string IdCardNum string AddCode string // 用于添加好友的code + DiviceId string // 设备id } func (b *Base) InitData(Uid int, Ip string) { diff --git a/src/server/game/mod/limitedTimeEvent/LimitedTimeEvent.go b/src/server/game/mod/limitedTimeEvent/LimitedTimeEvent.go index e53c9efa..bd40331c 100644 --- a/src/server/game/mod/limitedTimeEvent/LimitedTimeEvent.go +++ b/src/server/game/mod/limitedTimeEvent/LimitedTimeEvent.go @@ -27,6 +27,7 @@ const ( EVENT_TYPE_CAT_TRICK = 14 // 猫咪戏法 EVENT_TYPE_PAYBACK_DAY = 15 // 回收日 EVENT_TYPE_LITTLE_APPRENTICE = 16 // 小学徒 + EVENT_TYPE_CAT_DAY_SALE = 17 // 猫咪大甩卖 ) const ( @@ -46,6 +47,7 @@ type LimitedTimeEventMod struct { BonusNum int First bool FirstReward bool + CatDaySale bool // 是否参与猫咪大甩卖活动 } type LTEInfo struct { diff --git a/src/server/game/mod/mail/Mail.go b/src/server/game/mod/mail/Mail.go index 963920f9..b1cd2750 100644 --- a/src/server/game/mod/mail/Mail.go +++ b/src/server/game/mod/mail/Mail.go @@ -26,17 +26,22 @@ const ( ) type MailInfo struct { - Title string // 邮件标题 - SubTitle string // 邮件副标题 - Content string // 邮件内容 - TitleEn string // 邮件标题英文 - SubTitleEn string // 邮件副标题英文 - ContentEn string // 邮件内容英文 - Items []*item.Item // 邮件道具 - Type int //邮件类型 - Send int64 // 发送时间 - Del int64 // 删除时间 - Status int + Title string // 邮件标题 + SubTitle string // 邮件副标题 + Content string // 邮件内容 + TitleEn string // 邮件标题英文 + SubTitleEn string // 邮件副标题英文 + ContentEn string // 邮件内容英文 + // 葡萄牙语 巴西 + TitlePtBr string + SubTitlePtBr string + ContentPtBr string + + Items []*item.Item // 邮件道具 + Type int //邮件类型 + Send int64 // 发送时间 + Del int64 // 删除时间 + Status int } func (m *MailMod) InitData() { @@ -46,14 +51,20 @@ func (m *MailMod) InitData() { } type MailStruct struct { - Title string - SubTitle string - Content string + Title string + SubTitle string + Content string + // 英文 TitleEn string SubTitleEn string ContentEn string - Items []*item.Item - Type int + // 葡萄牙语 巴西 + TitlePtBr string + SubTitlePtBr string + ContentPtBr string + + Items []*item.Item + Type int } func (m *MailMod) SendMail(mail *MailStruct) int { @@ -62,16 +73,21 @@ func (m *MailMod) SendMail(mail *MailStruct) int { } m.AutoId++ m.List[m.AutoId] = &MailInfo{ - Title: mail.Title, - SubTitle: mail.SubTitle, - Content: mail.Content, + Title: mail.Title, + SubTitle: mail.SubTitle, + Content: mail.Content, + TitleEn: mail.TitleEn, SubTitleEn: mail.SubTitleEn, ContentEn: mail.ContentEn, - Items: mail.Items, - Send: GoUtil.Now(), - Type: mail.Type, - Status: MAIL_STATUS_IDLE, + + TitlePtBr: mail.TitlePtBr, + SubTitlePtBr: mail.SubTitlePtBr, + ContentPtBr: mail.ContentPtBr, + Items: mail.Items, + Send: GoUtil.Now(), + Type: mail.Type, + Status: MAIL_STATUS_IDLE, } return m.AutoId } @@ -141,17 +157,20 @@ func (m *MailMod) BackData() *msg.ResMailList { continue } res.MailList[int32(k)] = &msg.MailInfo{ - Id: int32(k), - Title: v.Title, - SubTitle: v.SubTitle, - Content: v.Content, - TitleEn: v.TitleEn, - SubTitleEn: v.SubTitleEn, - ContentEn: v.ContentEn, - Items: item.ItemToMsg(v.Items), - Status: int32(v.Status), - Time: int32(v.Send), - Type: int32(v.Type), + Id: int32(k), + Title: v.Title, + SubTitle: v.SubTitle, + Content: v.Content, + TitleEn: v.TitleEn, + SubTitleEn: v.SubTitleEn, + ContentEn: v.ContentEn, + TitlePtBr: v.TitlePtBr, + SubTitlePtBr: v.SubTitlePtBr, + ContentPtBr: v.ContentPtBr, + Items: item.ItemToMsg(v.Items), + Status: int32(v.Status), + Time: int32(v.Send), + Type: int32(v.Type), } } return res @@ -168,10 +187,15 @@ func (m *MailMod) NotifyMail(Id int) *msg.MailNotify { SubTitle: mailInfo.SubTitle, SubTitleEn: mailInfo.SubTitleEn, TitleEn: mailInfo.TitleEn, - Type: int32(mailInfo.Type), - Items: item.ItemToMsg(mailInfo.Items), - Status: int32(mailInfo.Status), - Time: int32(mailInfo.Send), + + TitlePtBr: mailInfo.TitlePtBr, + SubTitlePtBr: mailInfo.SubTitlePtBr, + ContentPtBr: mailInfo.ContentPtBr, + + Type: int32(mailInfo.Type), + Items: item.ItemToMsg(mailInfo.Items), + Status: int32(mailInfo.Status), + Time: int32(mailInfo.Send), }, } } diff --git a/src/server/go.mod b/src/server/go.mod index 0bc9f684..b5f48239 100644 --- a/src/server/go.mod +++ b/src/server/go.mod @@ -18,7 +18,6 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/segmentio/kafka-go v0.4.47 github.com/shirou/gopsutil v3.21.11+incompatible - github.com/vicanso/go-charts/v2 v2.6.10 google.golang.org/protobuf v1.36.2 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-xml v1.1.3 // 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/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.9 // 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/tklauser/go-sysconf v0.3.14 // 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 - golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -68,4 +63,7 @@ require ( github.com/google/uuid v1.6.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + github.com/tuyou/galog v0.0.0 ) + +replace github.com/tuyou/galog => ./galog diff --git a/src/server/go.sum b/src/server/go.sum index 886ebc10..8db7fcaf 100644 --- a/src/server/go.sum +++ b/src/server/go.sum @@ -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/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/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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 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-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/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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= 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/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 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.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= diff --git a/src/server/msg/Gameapi.pb.go b/src/server/msg/Gameapi.pb.go index f3e32924..fef7e27b 100644 --- a/src/server/msg/Gameapi.pb.go +++ b/src/server/msg/Gameapi.pb.go @@ -867,8 +867,9 @@ func (CHESS_EX_TYPE) EnumDescriptor() ([]byte, []int) { type LANG_TYPE int32 const ( - LANG_TYPE_LANG_CN LANG_TYPE = 0 // 中文 - LANG_TYPE_LANG_EN LANG_TYPE = 1 // 英文 + LANG_TYPE_LANG_CN LANG_TYPE = 0 // 中文 + LANG_TYPE_LANG_EN LANG_TYPE = 1 // 英文 + LANG_TYPE_LANG_PTBR LANG_TYPE = 2 // 葡萄牙语 ) // Enum value maps for LANG_TYPE. @@ -876,10 +877,12 @@ var ( LANG_TYPE_name = map[int32]string{ 0: "LANG_CN", 1: "LANG_EN", + 2: "LANG_PTBR", } LANG_TYPE_value = map[string]int32{ - "LANG_CN": 0, - "LANG_EN": 1, + "LANG_CN": 0, + "LANG_EN": 1, + "LANG_PTBR": 2, } ) @@ -13558,6 +13561,7 @@ type ResPlayerRank struct { Avatar int32 `protobuf:"varint,4,opt,name=Avatar,proto3" json:"Avatar,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"` + Type int32 `protobuf:"varint,7,opt,name=type,proto3" json:"type,omitempty"` // 排行类型 0:玩家 2:机器人 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -13634,6 +13638,13 @@ func (x *ResPlayerRank) GetScore() float32 { return 0 } +func (x *ResPlayerRank) GetType() int32 { + if x != nil { + return x.Type + } + return 0 +} + type ResFriendLog struct { state protoimpl.MessageState `protogen:"open.v1"` Player *ResPlayerSimple `protobuf:"bytes,1,opt,name=Player,proto3" json:"Player,omitempty"` @@ -16240,17 +16251,20 @@ func (x *ResMailList) GetMailList() map[int32]*MailInfo { type MailInfo struct { state protoimpl.MessageState `protogen:"open.v1"` - Id int32 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"` // 邮件id - Title string `protobuf:"bytes,2,opt,name=Title,proto3" json:"Title,omitempty"` // 标题 - Content string `protobuf:"bytes,3,opt,name=Content,proto3" json:"Content,omitempty"` // 内容 - Time int32 `protobuf:"varint,4,opt,name=Time,proto3" json:"Time,omitempty"` // 时间 - Status int32 `protobuf:"varint,5,opt,name=Status,proto3" json:"Status,omitempty"` // 0 未读 1 已读 2 已领取 3 已删除 - Items []*ItemInfo `protobuf:"bytes,6,rep,name=Items,proto3" json:"Items,omitempty"` // 奖励 - Type int32 `protobuf:"varint,7,opt,name=Type,proto3" json:"Type,omitempty"` //邮件类型 1普通邮件 2节日邮件 3 礼包邮件 - TitleEn string `protobuf:"bytes,8,opt,name=TitleEn,proto3" json:"TitleEn,omitempty"` // 英文标题 - ContentEn string `protobuf:"bytes,9,opt,name=ContentEn,proto3" json:"ContentEn,omitempty"` // 英文内容 - SubTitle string `protobuf:"bytes,10,opt,name=SubTitle,proto3" json:"SubTitle,omitempty"` // 子标题 - SubTitleEn string `protobuf:"bytes,11,opt,name=SubTitleEn,proto3" json:"SubTitleEn,omitempty"` // 英文子标题 + Id int32 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"` // 邮件id + Title string `protobuf:"bytes,2,opt,name=Title,proto3" json:"Title,omitempty"` // 标题 + Content string `protobuf:"bytes,3,opt,name=Content,proto3" json:"Content,omitempty"` // 内容 + Time int32 `protobuf:"varint,4,opt,name=Time,proto3" json:"Time,omitempty"` // 时间 + Status int32 `protobuf:"varint,5,opt,name=Status,proto3" json:"Status,omitempty"` // 0 未读 1 已读 2 已领取 3 已删除 + Items []*ItemInfo `protobuf:"bytes,6,rep,name=Items,proto3" json:"Items,omitempty"` // 奖励 + Type int32 `protobuf:"varint,7,opt,name=Type,proto3" json:"Type,omitempty"` //邮件类型 1普通邮件 2节日邮件 3 礼包邮件 + TitleEn string `protobuf:"bytes,8,opt,name=TitleEn,proto3" json:"TitleEn,omitempty"` // 英文标题 + ContentEn string `protobuf:"bytes,9,opt,name=ContentEn,proto3" json:"ContentEn,omitempty"` // 英文内容 + SubTitle string `protobuf:"bytes,10,opt,name=SubTitle,proto3" json:"SubTitle,omitempty"` // 子标题 + SubTitleEn string `protobuf:"bytes,11,opt,name=SubTitleEn,proto3" json:"SubTitleEn,omitempty"` // 英文子标题 + TitlePtBr string `protobuf:"bytes,12,opt,name=TitlePtBr,proto3" json:"TitlePtBr,omitempty"` // 葡萄牙标题 + ContentPtBr string `protobuf:"bytes,13,opt,name=ContentPtBr,proto3" json:"ContentPtBr,omitempty"` // 葡萄牙内容 + SubTitlePtBr string `protobuf:"bytes,14,opt,name=SubTitlePtBr,proto3" json:"SubTitlePtBr,omitempty"` // 葡萄牙子标题 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -16362,6 +16376,27 @@ func (x *MailInfo) GetSubTitleEn() string { return "" } +func (x *MailInfo) GetTitlePtBr() string { + if x != nil { + return x.TitlePtBr + } + return "" +} + +func (x *MailInfo) GetContentPtBr() string { + if x != nil { + return x.ContentPtBr + } + return "" +} + +func (x *MailInfo) GetSubTitlePtBr() string { + if x != nil { + return x.SubTitlePtBr + } + return "" +} + type MailNotify struct { state protoimpl.MessageState `protogen:"open.v1"` Info *MailInfo `protobuf:"bytes,1,opt,name=Info,proto3" json:"Info,omitempty"` @@ -28130,14 +28165,15 @@ const file_proto_Gameapi_proto_rawDesc = "" + "\x06ActLog\x12\x12\n" + "\x04Type\x18\x01 \x01(\x05R\x04Type\x12\x12\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" + "\x03Uid\x18\x01 \x01(\x03R\x03Uid\x12\x12\n" + "\x04Name\x18\x02 \x01(\tR\x04Name\x12\x12\n" + "\x04Face\x18\x03 \x01(\x05R\x04Face\x12\x16\n" + "\x06Avatar\x18\x04 \x01(\x05R\x06Avatar\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" + "\x06Player\x18\x01 \x01(\v2\x19.tutorial.ResPlayerSimpleR\x06Player\x12\x12\n" + "\x04Type\x18\x02 \x01(\x05R\x04Type\x12\x12\n" + @@ -28301,7 +28337,7 @@ const file_proto_Gameapi_proto_rawDesc = "" + "\bMailList\x18\x01 \x03(\v2#.tutorial.ResMailList.MailListEntryR\bMailList\x1aO\n" + "\rMailListEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\x05R\x03key\x12(\n" + - "\x05value\x18\x02 \x01(\v2\x12.tutorial.MailInfoR\x05value:\x028\x01\"\xa8\x02\n" + + "\x05value\x18\x02 \x01(\v2\x12.tutorial.MailInfoR\x05value:\x028\x01\"\x8c\x03\n" + "\bMailInfo\x12\x0e\n" + "\x02Id\x18\x01 \x01(\x05R\x02Id\x12\x14\n" + "\x05Title\x18\x02 \x01(\tR\x05Title\x12\x18\n" + @@ -28316,7 +28352,10 @@ const file_proto_Gameapi_proto_rawDesc = "" + " \x01(\tR\bSubTitle\x12\x1e\n" + "\n" + "SubTitleEn\x18\v \x01(\tR\n" + - "SubTitleEn\"4\n" + + "SubTitleEn\x12\x1c\n" + + "\tTitlePtBr\x18\f \x01(\tR\tTitlePtBr\x12 \n" + + "\vContentPtBr\x18\r \x01(\tR\vContentPtBr\x12\"\n" + + "\fSubTitlePtBr\x18\x0e \x01(\tR\fSubTitlePtBr\"4\n" + "\n" + "MailNotify\x12&\n" + "\x04Info\x18\x01 \x01(\v2\x12.tutorial.MailInfoR\x04Info\"\x1d\n" + @@ -29302,10 +29341,11 @@ const file_proto_Gameapi_proto_rawDesc = "" + "\fCHESS_EX_BOX\x10\x02\x12\x16\n" + "\x12CHESS_EX_QUICK_BUY\x10\x03\x12\x12\n" + "\x0eCHESS_EX_EVENT\x10\x04\x12$\n" + - " CHESS_EX_EVENT_LITTLE_APPRENTICE\x10\x05*%\n" + + " CHESS_EX_EVENT_LITTLE_APPRENTICE\x10\x05*4\n" + "\tLANG_TYPE\x12\v\n" + "\aLANG_CN\x10\x00\x12\v\n" + - "\aLANG_EN\x10\x01*x\n" + + "\aLANG_EN\x10\x01\x12\r\n" + + "\tLANG_PTBR\x10\x02*x\n" + "\x0fLimitEventParam\x12\f\n" + "\bLEP_NONE\x10\x00\x12\x14\n" + "\x10CAT_TRICK_ENERGY\x10\x01\x12\x12\n" +