Bläddra i källkod

update 新增logstash clickhouse配置;完善埋点数据结构;

Alvin 10 månader sedan
förälder
incheckning
d7dcfe85ec

+ 18 - 1
game/config/profile-gc.json

@@ -60,7 +60,7 @@
         "address": "",
         "__settings__": {
           "db_id_list": {
-            "game_db_id": "game_db_1"
+            "game_db_id": "gc_logstash"
           },
           "ref_logger": "logstash_log"
         },
@@ -230,6 +230,23 @@
       }
     ]
   },
+  "db": {
+    "logstash_db_group": [
+      {
+        "enable": true,
+        "db_id": "gc_logstash",
+        "db_type": "clickhouse",
+        "dsn": "clickhouse://default:123456@localhost:9000/default?dial_timeout=10s&read_timeout=20s",
+        "db_name": "default",
+        "host": "192.168.0.193:9000",
+        "user_name": "default",
+        "password": "123456",
+        "max_idle_connect": 4,
+        "max_open_connect": 8,
+        "log_mode": true
+      }
+    ]
+  },
   "redis": {
     "address": "127.0.0.1:6379",
     "password": "",

+ 15 - 0
game/game_cluster/internal/constant/constant.go

@@ -51,3 +51,18 @@ const (
 	RollTripleBet        = 3 // 豹子2345
 	RollSpecialTripleBet = 4 // 豹子16
 )
+
+type OperationTypeStr string
+
+const (
+	IncreaseOp OperationTypeStr = "increase"
+	DecreaseOp OperationTypeStr = "decrease"
+)
+
+type CurrencyTypeStr string
+
+const (
+	PointsCurrency CurrencyTypeStr = "POINTS" // 积分
+	TonCurrency    CurrencyTypeStr = "TON"
+	UsdtCurrency   CurrencyTypeStr = "USDT"
+)

+ 17 - 0
game/game_cluster/internal/mdb/component.go

@@ -4,6 +4,7 @@ import (
 	"github.com/go-redis/redis/v8"
 	mhayaGORM "github.com/mhaya/components/gorm"
 	mhayaMongo "github.com/mhaya/components/mongo"
+	"github.com/mhaya/game/game_cluster/internal/mdb/eventmodels"
 	clog "github.com/mhaya/logger"
 	cactor "github.com/mhaya/net/actor"
 	cprofile "github.com/mhaya/profile"
@@ -53,6 +54,8 @@ func (p *ActorDB) OnInit() {
 		if LogstashDB == nil {
 			clog.Panic("game_db_id not found")
 		}
+
+		LogstashMigrateTables()
 	}
 
 	redisConfig := cprofile.GetConfig("redis")
@@ -71,3 +74,17 @@ func (p *ActorDB) OnInit() {
 		SetIndex()
 	}
 }
+
+func LogstashMigrateTables() {
+	LogstashDB.AutoMigrate(&eventmodels.UserRegisterEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.UserLoginEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.UserUpdateInfoEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.UserWithdrawEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.AssetsEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.InviteEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.DiceEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.JoinChannelEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.FollowEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.TaskFinishEventContent{})
+	LogstashDB.AutoMigrate(&eventmodels.BackendOperationEventContent{})
+}

+ 19 - 0
game/game_cluster/internal/mdb/eventmodels/assetsEvent.go

@@ -0,0 +1,19 @@
+package eventmodels
+
+import "github.com/mhaya/game/game_cluster/internal/constant"
+
+// 资产变动事件
+type AssetsEventContent struct {
+	UserBasic
+	EventBasic
+	OperationType constant.OperationTypeStr `json:"operation_type"` // 操作类型 增加:increase;减少:decrease
+	Currency      constant.CurrencyTypeStr  `json:"currency"`       // 货币类型
+	Reason        string                    `json:"reason"`         // 变动原因
+	BeforeBalance int64                     `json:"before_balance"` // 变动前余额
+	Amount        int64                     `json:"amount"`         // 变动金额
+	AfterBalance  int64                     `json:"after_balance"`  // 变动后余额
+}
+
+func (e *AssetsEventContent) EventName() string {
+	return "AssetsChange"
+}

+ 18 - 0
game/game_cluster/internal/mdb/eventmodels/backendOperationEvent.go

@@ -0,0 +1,18 @@
+package eventmodels
+
+// 后台用户操作事件
+type BackendOperationEventContent struct {
+	UserBasic
+	EventBasic
+	RoleId       string `json:"role_id"`       // 角色ID
+	Path         string `json:"url"`           // 请求路径
+	Method       string `json:"method"`        // 请求方法
+	StatusCode   int    `json:"status_code"`   // HTTP状态码
+	Dur          int64  `json:"dur"`           // 请求耗时(毫秒)
+	ClientIP     string `json:"client_ip"`     // 客户端IP
+	ErrorMessage string `json:"error_message"` // 错误信息
+}
+
+func (e *BackendOperationEventContent) EventName() string {
+	return "BackendOperation"
+}

+ 18 - 0
game/game_cluster/internal/mdb/eventmodels/basic.go

@@ -0,0 +1,18 @@
+package eventmodels
+
+type EventBasic struct {
+	IsSuccess bool  `json:"is_uccess"` // 事件结果
+	CreateAt  int64 `json:"create_at"` // 事件创建时间戳
+}
+
+type UserBasic struct {
+	UserId            string `json:"user_id"`              // 用户id
+	UserName          string `json:"user_name"`            // 用户昵称
+	TgId              string `json:"tg_id"`                // 绑定tgid
+	XId               string `json:"x_id"`                 // 绑定推特ID
+	ParentUserId      string `json:"parent_user_id"`       // 上级用户id
+	IsRobot           bool   `json:"is_robot"`             // 是否为机器人
+	PointsRankSeq     int64  `json:"points_rank_seq"`      // 积分排名
+	ThrowDiceRankSeq  int64  `json:"throw_dice_rank_seq"`  // 投掷骰子次数排名
+	InviteUserRankSeq int64  `json:"invite_user_rank_seq"` // 邀请人数排名
+}

+ 11 - 0
game/game_cluster/internal/mdb/eventmodels/diceEvent.go

@@ -0,0 +1,11 @@
+package eventmodels
+
+// 骰子事件
+type DiceEventContent struct {
+	UserBasic
+	EventBasic
+}
+
+func (e *DiceEventContent) EventName() string {
+	return "Dice"
+}

+ 12 - 0
game/game_cluster/internal/mdb/eventmodels/followEvent.go

@@ -0,0 +1,12 @@
+package eventmodels
+
+// 关注事件
+type FollowEventContent struct {
+	UserBasic
+	EventBasic
+	ChannelName string `json:"channel"` // 渠道名称
+}
+
+func (e *FollowEventContent) EventName() string {
+	return "Follow"
+}

+ 15 - 0
game/game_cluster/internal/mdb/eventmodels/inviteEvent.go

@@ -0,0 +1,15 @@
+package eventmodels
+
+// 邀请事件
+type InviteEventContent struct {
+	UserBasic
+	EventBasic
+	InviterUserId string `json:"inviter_user_id"` // 邀请用户id
+	InviteUserId  string `json:"invite_user_id"`  // 被邀请用户id
+	Platform      string `json:"platform"`        // 平台
+	Channel       string `json:"channel"`         // 渠道
+}
+
+func (e *InviteEventContent) EventName() string {
+	return "Invite"
+}

+ 12 - 0
game/game_cluster/internal/mdb/eventmodels/joinChannelEvent.go

@@ -0,0 +1,12 @@
+package eventmodels
+
+// 加入频道事件
+type JoinChannelEventContent struct {
+	UserBasic
+	EventBasic
+	ChannelName string `json:"channel"` // 渠道名称
+}
+
+func (e *JoinChannelEventContent) EventName() string {
+	return "JoinChannel"
+}

+ 13 - 0
game/game_cluster/internal/mdb/eventmodels/taskFinishEvent.go

@@ -0,0 +1,13 @@
+package eventmodels
+
+// 任务完成事件
+type TaskFinishEventContent struct {
+	UserBasic
+	EventBasic
+	ActivityId string `json:"activity_id"` // 活动id
+	TaskId     string `json:"task_id"`     // 任务id
+}
+
+func (e *TaskFinishEventContent) EventName() string {
+	return "TaskFinish"
+}

+ 13 - 0
game/game_cluster/internal/mdb/eventmodels/turntableEvent.go

@@ -0,0 +1,13 @@
+package eventmodels
+
+// 转盘事件
+type TurntableEventContent struct {
+	UserBasic
+	EventBasic
+	PrizeName   string `json:"prize_name"`   // 抽奖奖品名
+	PrizeNumber int64  `json:"prize_number"` // 抽奖奖品数量
+}
+
+func (e *TurntableEventContent) EventName() string {
+	return "Turntable"
+}

+ 14 - 0
game/game_cluster/internal/mdb/eventmodels/userLoginEvent.go

@@ -0,0 +1,14 @@
+package eventmodels
+
+// 用户登录事件
+type UserLoginEventContent struct {
+	UserBasic
+	EventBasic
+	Ip       string `json:"ip"`       // IP
+	Platform string `json:"platform"` // 平台
+	Channel  string `json:"channel"`  // 渠道
+}
+
+func (e *UserLoginEventContent) EventName() string {
+	return "UserLogin"
+}

+ 19 - 0
game/game_cluster/internal/mdb/eventmodels/userRegisterEvent.go

@@ -0,0 +1,19 @@
+package eventmodels
+
+// 用户注册事件
+type UserRegisterEventContent struct {
+	UserBasic
+	EventBasic
+	Ip       string `json:"ip"`       // IP
+	Platform string `json:"platform"` // 平台
+	Channel  string `json:"channel"`  // 渠道
+	Source   string `json:"source"`   // 来源
+}
+
+func (e *UserRegisterEventContent) EventName() string {
+	return "UserRegister"
+}
+
+func (e *UserRegisterEventContent) Insert() error {
+	return nil
+}

+ 27 - 0
game/game_cluster/internal/mdb/eventmodels/userUpdateInfoEvent.go

@@ -0,0 +1,27 @@
+package eventmodels
+
+// 用户信息更新事件
+type UserUpdateInfoEventContent struct {
+	UserBasic
+	EventBasic
+	UserName    string `json:"user_name"`   // 用户昵称
+	TgId        string `json:"tg_id"`       // 绑定tgid
+	XId         string `json:"x_id"`        // 绑定推特ID
+	TonWall     string `json:"ton_wall"`    // 绑定钱包地址
+	Fingerprint string `json:"fingerprint"` // 绑定指纹数据
+	Email       string `json:"email"`       // 邮箱
+	Mobile      string `json:"mobile"`      // 手机号
+	Avatar      string `json:"avatar"`      // 头像
+	Birthday    string `json:"birthday"`    // 生日
+	Password    string `json:"password"`    // 账户密码 仅记录该操作,不记录内容
+	Salt        string `json:"salt"`        // 密码盐 仅记录该操作,不记录内容
+	IsLeader    bool   `json:"is_leader"`   // 是否是社区长
+	IsVip       bool   `json:"is_vip"`      // 是否为VIP
+	Level       int    `json:"level"`       // 等级
+	Exp         int    `json:"exp"`         // 经验
+	Gender      int    `json:"gender"`      // 性别
+}
+
+func (e *UserUpdateInfoEventContent) EventName() string {
+	return "UserUpdateInfo"
+}

+ 21 - 0
game/game_cluster/internal/mdb/eventmodels/userWithdrawEvent.go

@@ -0,0 +1,21 @@
+package eventmodels
+
+import "github.com/mhaya/game/game_cluster/internal/constant"
+
+// 用户提现事件
+type UserWithdrawEventContent struct {
+	UserBasic
+	EventBasic
+	Currency      constant.CurrencyTypeStr `json:"currency"`       // 货币类型
+	Ip            string                   `json:"ip"`             // IP
+	Platform      string                   `json:"platform"`       // 平台
+	Channel       string                   `json:"channel"`        // 渠道
+	Address       string                   `json:"address"`        // 提现地址
+	BeforeBalance int64                    `json:"before_balance"` // 提现前余额
+	Amount        int64                    `json:"amount"`         // 提现金额
+	AfterBalance  int64                    `json:"after_balance"`  // 提现后余额
+}
+
+func (e *UserWithdrawEventContent) EventName() string {
+	return "UserWithdraw"
+}

+ 2 - 2
game/game_cluster/nodes/logstash/logstash.go

@@ -3,7 +3,7 @@ package main
 import (
 	"github.com/mhaya"
 	mhayaCron "github.com/mhaya/components/cron"
-	mhayaMongo "github.com/mhaya/components/mongo"
+	mhayaGORM "github.com/mhaya/components/gorm"
 	"github.com/mhaya/game/game_cluster/internal/data"
 	"github.com/mhaya/game/game_cluster/internal/mdb"
 	"github.com/mhaya/game/game_cluster/nodes/center/module/ops"
@@ -21,7 +21,7 @@ func main() {
 	app.Register(mhayaCron.New())
 	app.Register(data.New())
 	// 注册db组件
-	app.Register(mhayaMongo.NewComponent())
+	app.Register(mhayaGORM.NewComponent())
 
 	app.AddActors(
 		&handlelog.HandleLogObject{},

+ 39 - 8
game/game_cluster/nodes/logstash/module/event/enent.go

@@ -2,7 +2,9 @@ package event
 
 import (
 	"sync"
+	"time"
 
+	"github.com/mhaya/game/game_cluster/internal/mdb/eventmodels"
 	mhayaLogger "github.com/mhaya/logger"
 )
 
@@ -10,14 +12,26 @@ type EventManage struct {
 	eventMap sync.Map
 }
 
+type Eventer interface {
+	EventName() string
+}
+
 var (
 	instance *EventManage
 	once     sync.Once
-)
 
-func GetEventIdMap() *EventManage {
-	return instance
-}
+	_ Eventer = &eventmodels.UserRegisterEventContent{}
+	_ Eventer = &eventmodels.UserLoginEventContent{}
+	_ Eventer = &eventmodels.UserUpdateInfoEventContent{}
+	_ Eventer = &eventmodels.UserWithdrawEventContent{}
+	_ Eventer = &eventmodels.AssetsEventContent{}
+	_ Eventer = &eventmodels.InviteEventContent{}
+	_ Eventer = &eventmodels.DiceEventContent{}
+	_ Eventer = &eventmodels.JoinChannelEventContent{}
+	_ Eventer = &eventmodels.FollowEventContent{}
+	_ Eventer = &eventmodels.TaskFinishEventContent{}
+	_ Eventer = &eventmodels.BackendOperationEventContent{}
+)
 
 func init() {
 	once.Do(func() {
@@ -26,7 +40,21 @@ func init() {
 		}
 	})
 
-	instance.AddEvent(new(PlayerLoginEventContent))
+	instance.AddEvent(new(eventmodels.UserRegisterEventContent))
+	instance.AddEvent(new(eventmodels.UserLoginEventContent))
+	instance.AddEvent(new(eventmodels.UserUpdateInfoEventContent))
+	instance.AddEvent(new(eventmodels.UserWithdrawEventContent))
+	instance.AddEvent(new(eventmodels.AssetsEventContent))
+	instance.AddEvent(new(eventmodels.InviteEventContent))
+	instance.AddEvent(new(eventmodels.DiceEventContent))
+	instance.AddEvent(new(eventmodels.JoinChannelEventContent))
+	instance.AddEvent(new(eventmodels.FollowEventContent))
+	instance.AddEvent(new(eventmodels.TaskFinishEventContent))
+	instance.AddEvent(new(eventmodels.BackendOperationEventContent))
+}
+
+func GetEventIdMap() *EventManage {
+	return instance
 }
 
 func (em *EventManage) AddEvent(e Eventer) {
@@ -43,11 +71,14 @@ func (em *EventManage) GetEvent(eventName string) Eventer {
 }
 
 func (em *EventManage) PrintAllEvent() {
-	mhayaLogger.Info("-------------------------------------------------")
+	// 延迟1S 以便集中输出内容
+	time.Sleep(time.Second)
+
+	mhayaLogger.Info("-------------------EventList Begin------------------------------")
 	em.eventMap.Range(func(key, value any) bool {
 		mhayaLogger.Infof("[event name = %s]", key)
-		mhayaLogger.Infof("[event content = %v]", value)
+		mhayaLogger.Infof("[event content = %#v]", value)
 		return true
 	})
-	mhayaLogger.Info("-------------------------------------------------")
+	mhayaLogger.Info("-------------------EventList End------------------------------")
 }

+ 0 - 17
game/game_cluster/nodes/logstash/module/event/eventContent.go

@@ -1,17 +0,0 @@
-package event
-
-type Eventer interface {
-	EventName() string
-}
-
-var (
-	_ Eventer = &PlayerLoginEventContent{}
-)
-
-type PlayerLoginEventContent struct {
-	// TODO 具体埋点数据
-}
-
-func (e *PlayerLoginEventContent) EventName() string {
-	return "PlayerLogin"
-}

+ 26 - 2
game/game_cluster/nodes/logstash/module/handlelog/actor_handle_log.go

@@ -1,3 +1,12 @@
+/*
+ * @Author: Alvin Alvin@qq.com
+ * @Date: 2024-09-24 18:54:19
+ * @LastEditors: Alvin Alvin@qq.com
+ * @LastEditTime: 2024-09-25 18:37:45
+ * @FilePath: \server\game\game_cluster\nodes\logstash\module\handlelog\actor_handle_log.go
+ * @Description:
+ * 处理其他服务发送的埋点日志
+ */
 package handlelog
 
 import (
@@ -5,6 +14,7 @@ import (
 	"strings"
 
 	"github.com/mhaya/game/game_cluster/internal/code"
+	"github.com/mhaya/game/game_cluster/internal/mdb/eventmodels"
 	"github.com/mhaya/game/game_cluster/internal/param"
 	"github.com/mhaya/game/game_cluster/nodes/logstash/module/event"
 	mhayaLogger "github.com/mhaya/logger"
@@ -24,6 +34,8 @@ func (p *HandleLogObject) AliasID() string {
 // OnInit logstash为后端节点,不直接与客户端通信,所以注册了一些remote函数,供RPC调用
 func (p *HandleLogObject) OnInit() {
 	p.Remote().Register("handlelog", p.handlelog)
+
+	event.GetEventIdMap().PrintAllEvent()
 }
 
 // handlelog 处理各个节点发送的日志记录
@@ -43,10 +55,22 @@ func (p *HandleLogObject) handlelog(req *param.HandleLogReq) int32 {
 	return p.handlelogContent(req, e)
 }
 
+// 根据不同的请求内容,分别处理不通的埋点日志
 func (p *HandleLogObject) handlelogContent(req *param.HandleLogReq, e event.Eventer) int32 {
 	switch e.(type) {
-	case *event.PlayerLoginEventContent:
-		content := &event.PlayerLoginEventContent{}
+	case *eventmodels.UserRegisterEventContent:
+		content := &eventmodels.UserRegisterEventContent{}
+		err := json.Unmarshal([]byte(req.JsonContent), &content)
+		if err != nil {
+			mhayaLogger.Warnf("handlelog Unmarshal err:%v", err)
+			return code.Error
+		}
+
+		// TODO 将流水日志写入clickhouse等等
+		content.Insert()
+
+	case *eventmodels.UserLoginEventContent:
+		content := &eventmodels.UserLoginEventContent{}
 		err := json.Unmarshal([]byte(req.JsonContent), &content)
 		if err != nil {
 			mhayaLogger.Warnf("handlelog Unmarshal err:%v", err)

+ 18 - 5
game/go.mod

@@ -15,6 +15,7 @@ require (
 	github.com/mhaya/components/data-config v1.3.13
 	github.com/mhaya/components/gin v1.3.13
 	github.com/mhaya/components/gops v1.3.13
+	github.com/mhaya/components/gorm v0.0.0-00010101000000-000000000000
 	github.com/mhaya/components/mongo v0.0.0-00010101000000-000000000000
 	github.com/oschwald/geoip2-golang v1.11.0
 	github.com/spf13/cast v1.5.1
@@ -28,17 +29,22 @@ require (
 
 require (
 	filippo.io/edwards25519 v1.1.0 // indirect
+	github.com/ClickHouse/ch-go v0.61.5 // indirect
+	github.com/ClickHouse/clickhouse-go/v2 v2.23.2 // indirect
+	github.com/andybalholm/brotli v1.1.0 // indirect
 	github.com/bytedance/sonic v1.11.6 // indirect
 	github.com/bytedance/sonic/loader v0.1.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/cloudwego/base64x v0.1.4 // indirect
 	github.com/cloudwego/iasm v0.2.0 // indirect
 	github.com/coreos/go-semver v0.3.0 // indirect
-	github.com/coreos/go-systemd/v22 v22.3.2 // indirect
+	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/gabriel-vasile/mimetype v1.4.5 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-faster/city v1.0.1 // indirect
+	github.com/go-faster/errors v0.7.1 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.20.0 // indirect
@@ -47,11 +53,13 @@ require (
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/gops v0.3.28 // indirect
+	github.com/google/uuid v1.6.0 // indirect
 	github.com/gorilla/websocket v1.5.0 // indirect
+	github.com/hashicorp/go-version v1.6.0 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
-	github.com/klauspost/compress v1.17.0 // indirect
+	github.com/klauspost/compress v1.17.8 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/lestrrat-go/strftime v1.0.6 // indirect
@@ -65,11 +73,14 @@ require (
 	github.com/nats-io/nkeys v0.4.5 // indirect
 	github.com/nats-io/nuid v1.0.1 // indirect
 	github.com/oschwald/maxminddb-golang v1.13.0 // indirect
+	github.com/paulmach/orb v0.11.1 // indirect
 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+	github.com/pierrec/lz4/v4 v4.1.21 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/radovskyb/watcher v1.0.7 // indirect
 	github.com/robfig/cron/v3 v3.0.1 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/segmentio/asm v1.2.0 // indirect
 	github.com/shopspring/decimal v1.4.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
@@ -81,19 +92,21 @@ require (
 	go.etcd.io/etcd/api/v3 v3.5.16 // indirect
 	go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect
 	go.etcd.io/etcd/client/v3 v3.5.16 // indirect
-	go.uber.org/multierr v1.10.0 // indirect
-	go.uber.org/zap v1.26.0 // indirect
+	go.opentelemetry.io/otel v1.26.0 // indirect
+	go.opentelemetry.io/otel/trace v1.26.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	go.uber.org/zap v1.27.0 // indirect
 	golang.org/x/arch v0.8.0 // indirect
 	golang.org/x/net v0.27.0 // indirect
 	golang.org/x/sync v0.8.0 // indirect
 	golang.org/x/sys v0.23.0 // indirect
 	golang.org/x/text v0.17.0 // indirect
-	golang.org/x/time v0.3.0 // indirect
 	google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
 	google.golang.org/grpc v1.59.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gorm.io/driver/clickhouse v0.6.1 // indirect
 )
 
 replace (

+ 67 - 17
game/go.sum

@@ -1,5 +1,11 @@
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4=
+github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg=
+github.com/ClickHouse/clickhouse-go/v2 v2.23.2 h1:+DAKPMnxLS7pduQZsrJc8OhdLS2L9MfDEJ2TS+hpYDM=
+github.com/ClickHouse/clickhouse-go/v2 v2.23.2/go.mod h1:aNap51J1OM3yxQJRgM+AlP/MPkGBCL8A74uQThoQhR0=
+github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
+github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
 github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
 github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
 github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
@@ -14,8 +20,8 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
-github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -25,14 +31,18 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
 github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
 github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
 github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
+github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
+github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
+github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -51,17 +61,25 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark=
 github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -74,14 +92,18 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
-github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
+github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -99,6 +121,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
 github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 github.com/nats-io/jwt/v2 v2.5.2 h1:DhGH+nKt+wIkDxM6qnVSKjokq5t59AZV5HRcFW0zJwU=
@@ -115,14 +138,19 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
-github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE=
+github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk=
 github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
 github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
 github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
 github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
+github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
+github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
+github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
 github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
+github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -131,10 +159,12 @@ github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8t
 github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
+github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
 github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
 github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
 github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
@@ -144,6 +174,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -151,6 +182,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@@ -159,8 +191,10 @@ github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
 github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
 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.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
 github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
+github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
 github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
@@ -176,14 +210,19 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSf
 go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E=
 go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE=
 go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50=
+go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
 go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8=
 go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
-go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
-go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
-go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
-go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
-go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
+go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
+go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
+go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
+go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
 golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -191,6 +230,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
 golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -201,12 +241,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
 golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -215,6 +257,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -226,6 +269,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
@@ -249,10 +293,14 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:
 google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
 google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
 google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
 google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -261,6 +309,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/clickhouse v0.6.1 h1:t7JMB6sLBXxN8hEO6RdzCbJCwq/jAEVZdwXlmQs1Sd4=
+gorm.io/driver/clickhouse v0.6.1/go.mod h1:riMYpJcGZ3sJ/OAZZ1rEP1j/Y0H6cByOAnwz7fo2AyM=
 gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
 gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
 gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=