Merge branch 'sdk' into online

This commit is contained in:
hahwu 2025-11-28 18:05:25 +08:00
commit 9a1449254d
44 changed files with 2389 additions and 140 deletions

1
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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,

View File

@ -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) {

View File

@ -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)
}

View File

@ -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"`

36
src/server/ga/log.go Normal file
View File

@ -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()
}

7
src/server/galog/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.DS_Store
.vscode/
.idea/
*.log
logs/
ga_log/
asset_log/

292
src/server/galog/README.md Normal file
View 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 |
| 并发=2qps=200 | 1% | 52% | 0.08% | 7 |
| 并发=5qps=5000 | 4% | 52% | 1% | 7 |
| 并发=5qps=50000(队列1000有阻塞) realqps=5000 | 40% | 52% | 30% | 7 |
| 并发=5qps=50000(队列10000有阻塞) realqps=6500 | 6% | 52% | 1% | 7 |
| 并发=20qps=50000(队列10000有阻塞) realqps=18000 | 10% | 52% | 3% | 7 |
| 并发=50qps=50000(队列10000) realqps=50000 | 35% | 52% | 10% | 7 |
| 并发=100qps=100000(队列10000) realqps=90000 | 75% | 52% | 16% | 7 |
- 压测结果:写入队列=10000单核支持最高写入QPS约5wCPU=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 |

View 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
View 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
}

View 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)
}

View 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)
}
}

View 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 }}`
)

View 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
}

View 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
View 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
View 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)
}

View 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
View File

@ -0,0 +1,3 @@
module tygit.tuyoo.com/gocomponents/galog
go 1.20

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

261
src/server/galog/log.go Normal file
View 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
View 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 项目IDGA侧分配必填
// opts.ClientID 客户端IDGA侧分配必填
// 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
}

View 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)
}
}

View File

@ -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),
}
}
}

View File

@ -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 类型

View File

@ -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 {

View File

@ -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()

View File

@ -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,

View File

@ -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{}) {

View File

@ -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())
}

View File

@ -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
}

View File

@ -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)

View File

@ -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()
}

View File

@ -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())

View File

@ -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{

View File

@ -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) {

View File

@ -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 {

View File

@ -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),
},
}
}

View File

@ -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

View File

@ -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=

View File

@ -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" +