Gogs преди 4 месеца
родител
ревизия
827705ce16

+ 5 - 0
bin/client_msg/msg.ts

@@ -0,0 +1,5 @@
+// 自动生成的消息ID映射
+export enum MsgID {
+    Hello = 0,
+    ReqLogin = 1,
+}

+ 2 - 2
bin/conf/server.json

@@ -1,6 +1,6 @@
 {
 	"LogLevel": "debug",
-	"LogPath": "",
-	"WSAddr": "127.0.0.1:3653",
+	"LogPath": "/home/zjh/teen_patti/bin/log",
+	"WSAddr": "0.0.0.0:8080",
 	"MaxConnNum": 20000
 }

+ 15 - 0
gen_proto.sh

@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# 设置 GOPATH
+export GOPATH=/home/zjh/teen_patti
+
+# 添加 GOPATH/bin 到 PATH
+export PATH=$PATH:$GOPATH/bin:/usr/local/bin:$(go env GOPATH)/bin
+
+# 进入 proto 文件目录
+cd src/server/msg
+
+# 生成 proto 文件
+protoc --go_out=. test.proto
+
+echo "Proto files generated successfully!" 

+ 5 - 0
src/server/conf/conf.go

@@ -22,3 +22,8 @@ var (
 	AsynCallLen        = 10000
 	ChanRPCLen         = 10000
 )
+
+var (
+	MongoDBAddr = "mongodb://localhost:27017"
+	MongoDBName = "teen_patti"
+)

+ 144 - 0
src/server/db/mongodb/mongodbmgr.go

@@ -0,0 +1,144 @@
+package mongodbmgr
+
+import (
+	"server/conf"
+	"time"
+
+	"github.com/name5566/leaf/db/mongodb"
+	"github.com/name5566/leaf/log"
+
+	// "gopkg.in/mgo.v2"
+	"gopkg.in/mgo.v2/bson"
+)
+
+// 连接消息
+var dialContext = new(mongodb.DialContext)
+
+func init() {
+	Connect()
+	test()
+}
+func Connect() {
+	c, err := mongodb.Dial(conf.MongoDBAddr, 10)
+	if err != nil {
+		log.Error(err.Error())
+		return
+	}
+	//defer c.Close()
+	// index
+	c.EnsureUniqueIndex(conf.MongoDBName, "login", []string{"user_id"})
+	log.Release("mongodb Connect success")
+	dialContext = c
+}
+
+// Collection 定义集合操作接口
+type Collection struct {
+	DB         string
+	Collection string
+}
+
+// NewCollection 创建集合操作实例
+func NewCollection(db, collection string) *Collection {
+	return &Collection{
+		DB:         db,
+		Collection: collection,
+	}
+}
+
+// Insert 插入文档
+func (c *Collection) Insert(doc interface{}) error {
+	s := dialContext.Ref()
+	defer dialContext.UnRef(s)
+
+	return s.DB(c.DB).C(c.Collection).Insert(doc)
+}
+
+// Delete 删除文档
+func (c *Collection) Delete(query bson.M) error {
+	s := dialContext.Ref()
+	defer dialContext.UnRef(s)
+
+	return s.DB(c.DB).C(c.Collection).Remove(query)
+}
+
+// Update 更新文档
+func (c *Collection) Update(query bson.M, update bson.M) error {
+	s := dialContext.Ref()
+	defer dialContext.UnRef(s)
+
+	return s.DB(c.DB).C(c.Collection).Update(query, bson.M{"$set": update})
+}
+
+// FindOne 查询单个文档
+func (c *Collection) FindOne(query bson.M, result interface{}) error {
+	s := dialContext.Ref()
+	defer dialContext.UnRef(s)
+
+	return s.DB(c.DB).C(c.Collection).Find(query).One(result)
+}
+
+// FindMany 查询多个文档
+func (c *Collection) FindMany(query bson.M, skip, limit int, result interface{}) error {
+	s := dialContext.Ref()
+	defer dialContext.UnRef(s)
+
+	return s.DB(c.DB).C(c.Collection).Find(query).Skip(skip).Limit(limit).All(result)
+}
+
+func test() {
+	// 1. 用户集合操作
+	type User struct {
+		UserID   int64  `bson:"user_id"`
+		Name     string `bson:"name"`
+		Password string `bson:"password"`
+	}
+
+	userColl := NewCollection(conf.MongoDBName, "users")
+
+	// 插入用户
+	user := &User{UserID: 1001, Name: "player1"}
+	err := userColl.Insert(user)
+	if err != nil {
+		log.Error(err.Error())
+	}
+
+	// 查询用户
+	findUser := new(User)
+	err = userColl.FindOne(bson.M{"user_id": 1001}, findUser)
+
+	if err != nil {
+		log.Error(err.Error())
+	}
+	// 2. 游戏记录集合操作
+	type GameRecord struct {
+		GameID    int64   `bson:"game_id"`
+		Players   []int64 `bson:"players"`
+		CreatedAt int64   `bson:"created_at"`
+	}
+
+	gameColl := NewCollection("teen_patti", "game_records")
+
+	// 插入游戏记录
+	record := &GameRecord{
+		GameID:    1,
+		Players:   []int64{1001, 1002},
+		CreatedAt: time.Now().Unix(),
+	}
+	err = gameColl.Insert(record)
+	if err != nil {
+		log.Error(err.Error())
+	}
+
+	// 查询游戏记录列表
+	var records []GameRecord
+	err = gameColl.FindMany(
+		bson.M{"players": 1001},
+		0,
+		10,
+		&records,
+	)
+	if err != nil {
+		log.Error(err.Error())
+	}
+	log.Error("records:", records)
+}

+ 134 - 0
src/server/db/mysql/mysqlmgr.go

@@ -0,0 +1,134 @@
+package mysqlmgr
+
+import (
+	"database/sql"
+	"time"
+
+	"github.com/name5566/leaf/log"
+
+	_ "github.com/go-sql-driver/mysql"
+)
+
+var (
+	db *sql.DB
+)
+
+func init() {
+	Connect()
+}
+
+// Connect 初始化数据库连接
+func Connect() {
+	var err error
+	db, err = sql.Open("mysql", "root:password@tcp(localhost:3306)/teen_patti?charset=utf8mb4&parseTime=True")
+	if err != nil {
+		panic(err)
+	}
+
+	// 设置连接池
+	db.SetMaxIdleConns(10)           // 最大空闲连接数
+	db.SetMaxOpenConns(100)          // 最大打开连接数
+	db.SetConnMaxLifetime(time.Hour) // 连接最大生命周期
+}
+
+// Insert 插入数据
+func Insert(query string, args ...interface{}) (int64, error) {
+	result, err := db.Exec(query, args...)
+	if err != nil {
+		return 0, err
+	}
+	return result.LastInsertId()
+}
+
+// Update 更新数据
+func Update(query string, args ...interface{}) (int64, error) {
+	result, err := db.Exec(query, args...)
+	if err != nil {
+		return 0, err
+	}
+	return result.RowsAffected()
+}
+
+// Delete 删除数据
+func Delete(query string, args ...interface{}) (int64, error) {
+	result, err := db.Exec(query, args...)
+	if err != nil {
+		return 0, err
+	}
+	return result.RowsAffected()
+}
+
+// QueryRow 查询单行
+func QueryRow(query string, args ...interface{}) *sql.Row {
+	return db.QueryRow(query, args...)
+}
+
+// Query 查询多行
+func Query(query string, args ...interface{}) (*sql.Rows, error) {
+	return db.Query(query, args...)
+}
+
+// 测试函数
+func test() {
+	// 创建用户表
+	createTable := `
+	CREATE TABLE IF NOT EXISTS users (
+		id INT AUTO_INCREMENT,
+		name VARCHAR(255) NOT NULL,
+		balance DECIMAL(10,2) DEFAULT 0,
+		created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+		PRIMARY KEY (id)
+	) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+	`
+	_, err := db.Exec(createTable)
+	if err != nil {
+		log.Debug("Create table error:", err)
+		return
+	}
+
+	// 插入数据
+	id, err := Insert("INSERT INTO users (name, balance) VALUES (?, ?)", "player1", 1000.00)
+	if err != nil {
+		log.Debug("Insert error:", err)
+		return
+	}
+	log.Debug("Inserted user ID: %d\n", id)
+
+	// 更新数据
+	affected, err := Update("UPDATE users SET balance = balance + ? WHERE id = ?", 100.00, id)
+	if err != nil {
+		log.Debug("Update error:", err)
+		return
+	}
+	log.Debug("Updated %d rows\n", affected)
+
+	// 查询单行
+	var name string
+	var balance float64
+	err = QueryRow("SELECT name, balance FROM users WHERE id = ?", id).Scan(&name, &balance)
+	if err != nil {
+		log.Debug("Query error:", err)
+		return
+	}
+	log.Debug("User: %s, Balance: %.2f\n", name, balance)
+
+	// 查询多行
+	rows, err := Query("SELECT id, name, balance FROM users")
+	if err != nil {
+		log.Debug("Query error:", err)
+		return
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		var id int
+		var name string
+		var balance float64
+		err := rows.Scan(&id, &name, &balance)
+		if err != nil {
+			log.Debug("Scan error:", err)
+			continue
+		}
+		log.Debug("ID: %d, Name: %s, Balance: %.2f\n", id, name, balance)
+	}
+}

+ 152 - 0
src/server/db/redis/redismgr.go

@@ -0,0 +1,152 @@
+package redismgr
+
+import (
+	"time"
+
+	"github.com/gomodule/redigo/redis"
+	"github.com/name5566/leaf/log"
+)
+
+var (
+	pool *redis.Pool
+)
+
+func init() {
+	Connect()
+}
+
+// Connect 初始化连接池
+func Connect() {
+	pool = &redis.Pool{
+		MaxIdle:     256,              // 最大空闲连接数
+		MaxActive:   0,                // 最大连接数,0表示不限制
+		IdleTimeout: time.Second * 60, // 空闲超时时间
+		Dial: func() (redis.Conn, error) {
+			return redis.Dial(
+				"tcp",
+				"localhost:6379",
+				redis.DialPassword(""), // 如果有密码
+				redis.DialDatabase(0),  // 选择数据库
+			)
+		},
+	}
+}
+
+// Set 设置键值对
+func Set(key string, value interface{}, expiration ...int) error {
+	conn := pool.Get()
+	defer conn.Close()
+
+	if len(expiration) > 0 {
+		_, err := conn.Do("SETEX", key, expiration[0], value)
+		return err
+	}
+	_, err := conn.Do("SET", key, value)
+	return err
+}
+
+// Get 获取值
+func Get(key string) (string, error) {
+	conn := pool.Get()
+	defer conn.Close()
+
+	reply, err := redis.String(conn.Do("GET", key))
+	if err == redis.ErrNil {
+		return "", nil
+	}
+	return reply, err
+}
+
+// Delete 删除键
+func Delete(key string) error {
+	conn := pool.Get()
+	defer conn.Close()
+
+	_, err := conn.Do("DEL", key)
+	return err
+}
+
+// Exists 检查键是否存在
+func Exists(key string) (bool, error) {
+	conn := pool.Get()
+	defer conn.Close()
+
+	return redis.Bool(conn.Do("EXISTS", key))
+}
+
+// HashSet 设置哈希表字段
+func HashSet(key, field string, value interface{}) error {
+	conn := pool.Get()
+	defer conn.Close()
+
+	_, err := conn.Do("HSET", key, field, value)
+	return err
+}
+
+// HashGet 获取哈希表字段
+func HashGet(key, field string) (string, error) {
+	conn := pool.Get()
+	defer conn.Close()
+
+	reply, err := redis.String(conn.Do("HGET", key, field))
+	if err == redis.ErrNil {
+		return "", nil
+	}
+	return reply, err
+}
+
+// ListPush 将值推入列表
+func ListPush(key string, value interface{}) error {
+	conn := pool.Get()
+	defer conn.Close()
+
+	_, err := conn.Do("LPUSH", key, value)
+	return err
+}
+
+// ListRange 获取列表范围内的元素
+func ListRange(key string, start, stop int) ([]string, error) {
+	conn := pool.Get()
+	defer conn.Close()
+
+	return redis.Strings(conn.Do("LRANGE", key, start, stop))
+}
+
+// 测试函数
+func test() {
+	// 测试字符串操作
+	err := Set("test_key", "test_value", 60)
+	if err != nil {
+		log.Debug("Set error:", err)
+	}
+
+	value, err := Get("test_key")
+	if err != nil {
+		log.Debug("Get error:", err)
+	}
+	log.Debug("Get value:", value)
+
+	// 测试哈希表操作
+	err = HashSet("user:1001", "name", "player1")
+	if err != nil {
+		log.Debug("HashSet error:", err)
+	}
+
+	name, err := HashGet("user:1001", "name")
+	if err != nil {
+		log.Debug("HashGet error:", err)
+	}
+	log.Debug("HashGet name:", name)
+
+	// 测试列表操作
+	err = ListPush("game:list", "game1")
+	if err != nil {
+		log.Debug("ListPush error:", err)
+	}
+
+	games, err := ListRange("game:list", 0, -1)
+	if err != nil {
+		log.Debug("ListRange error:", err)
+	}
+	log.Debug("Games:", games)
+}

+ 80 - 4
src/server/game/internal/chanrpc.go

@@ -1,7 +1,10 @@
 package internal
 
 import (
+	"time"
+
 	"github.com/name5566/leaf/gate"
+	"github.com/name5566/leaf/log"
 )
 
 func init() {
@@ -9,12 +12,85 @@ func init() {
 	skeleton.RegisterChanRPC("CloseAgent", rpcCloseAgent)
 }
 
+const (
+	ConnStatusUnauth = iota // 未认证
+	ConnStatusAuthed        // 已认证
+)
+
+// 未认证连接管理
+type PendingConn struct {
+	Agent      gate.Agent
+	CreateTime int64
+	Status     int
+}
+
+var (
+	// 未认证连接池
+	pendingConns = make(map[gate.Agent]*PendingConn)
+	// 已认证用户
+	authedUsers = make(map[int64]gate.Agent) // key: userID
+)
+
 func rpcNewAgent(args []interface{}) {
-	a := args[0].(gate.Agent)
-	_ = a
+	agent := args[0].(gate.Agent)
+	// 将新连接放入未认证池
+	pendingConns[agent] = &PendingConn{
+		Agent:      agent,
+		CreateTime: time.Now().Unix(),
+		Status:     ConnStatusUnauth,
+	}
+	go checkTimeout(agent)
 }
 
 func rpcCloseAgent(args []interface{}) {
-	a := args[0].(gate.Agent)
-	_ = a
+	agent := args[0].(gate.Agent)
+	// 清理未认证连接
+	_, exists := pendingConns[agent]
+	if exists {
+		delete(pendingConns, agent)
+	}
+	// 清理已认证连接
+	for userID, a := range authedUsers {
+		if a == agent {
+			delete(authedUsers, userID)
+			break
+		}
+	}
+}
+
+// 处理认证消息
+func handleAuth(agent gate.Agent, userID int64) {
+	// 找到未认证连接
+	if pending, exists := pendingConns[agent]; exists {
+		// 认证成功,移动到已认证池
+		delete(pendingConns, agent)
+		authedUsers[userID] = agent
+		pending.Status = ConnStatusAuthed
+
+		// 处理可能的重复登录
+		if oldAgent, exists := authedUsers[userID]; exists && oldAgent != agent {
+			oldAgent.Close()
+			delete(authedUsers, userID)
+		}
+	}
+}
+
+// 定时检查超时连接
+func checkTimeout(agent gate.Agent) {
+	timeout := 30 * time.Second
+	timer := time.NewTimer(timeout)
+	defer timer.Stop()
+
+	select {
+	case <-timer.C:
+		// 检查是否仍在未认证池中
+		if conn, exists := pendingConns[agent]; exists {
+			if conn.Status == ConnStatusUnauth {
+				// 超时未认证,关闭连接
+				log.Debug("Connection timeout without auth, closing...")
+				agent.Close()
+				delete(pendingConns, agent)
+			}
+		}
+	}
 }

+ 14 - 2
src/server/go.mod

@@ -2,6 +2,18 @@ module server
 
 go 1.23.1
 
-require github.com/name5566/leaf v0.0.0-20221021105039-af71eb082cda
+require (
+	github.com/go-sql-driver/mysql v1.8.1
+	github.com/gomodule/redigo v2.0.0+incompatible
+	github.com/name5566/leaf v0.0.0-20221021105039-af71eb082cda
+	gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
+)
 
-require github.com/gorilla/websocket v1.5.3 // indirect
+require (
+	filippo.io/edwards25519 v1.1.0 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
+	github.com/gorilla/websocket v1.5.3 // indirect
+	google.golang.org/protobuf v1.36.2 // indirect
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+)

+ 26 - 0
src/server/go.sum

@@ -1,4 +1,30 @@
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+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/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
+github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/name5566/leaf v0.0.0-20221021105039-af71eb082cda h1:5S+9luohX8Whu/4VreRe4E0bHSSuJ4hyIGJc1nvNQzQ=
 github.com/name5566/leaf v0.0.0-20221021105039-af71eb082cda/go.mod h1:JrOIxq3vDxvtuEI7Kmm2yqkuBfuT9DMLFMnCyYHLaKM=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
+google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
+gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 6 - 0
src/server/login/internal/handler.go

@@ -2,6 +2,8 @@ package internal
 
 import (
 	"reflect"
+	"server/game"
+	"server/msg"
 )
 
 func handleMsg(m interface{}, h interface{}) {
@@ -9,5 +11,9 @@ func handleMsg(m interface{}, h interface{}) {
 }
 
 func init() {
+	handleMsg(&msg.ReqLogin{}, loginHandler)
+}
 
+func loginHandler(args []interface{}) {
+	game.ChanRPC.Go("handleAuth", args...)
 }

+ 41 - 4
src/server/msg/msg.go

@@ -1,15 +1,52 @@
 package msg
 
 import (
-	"github.com/name5566/leaf/network/json"
+	"fmt"
+	"os"
+	"reflect"
+	"strings"
+
+	"github.com/name5566/leaf/log"
+	"github.com/name5566/leaf/network/protobuf"
 )
 
-var Processor = json.NewProcessor()
+var Processor = protobuf.NewProcessor()
+
+type MsgInfo struct {
+	ID   uint16
+	Name string
+}
+
+var msgList []MsgInfo
 
 func init() {
 	Processor.Register(&Hello{})
+	Processor.Register(&ReqLogin{})
+	Processor.Range(func(id uint16, t reflect.Type) {
+		log.Debug("消息ID: %d, 消息类型: %s\n", id, t.Elem().Name())
+		msgList = append(msgList, MsgInfo{
+			ID:   id,
+			Name: t.Elem().Name(),
+		})
+		// fmt.Printf("消息ID: %d, 消息类型: %s\n", id, t.Elem().Name())
+	})
+	generateTSCode()
 }
 
-type Hello struct {
-	Name string
+func generateTSCode() {
+	var tsCode strings.Builder
+	tsCode.WriteString("// 自动生成的消息ID映射\n")
+	tsCode.WriteString("export enum MsgID {\n")
+
+	for _, msg := range msgList {
+		tsCode.WriteString(fmt.Sprintf("    %s = %d,\n", msg.Name, msg.ID))
+	}
+
+	tsCode.WriteString("}\n")
+
+	// 写入文件
+	err := os.WriteFile("/home/zjh/teen_patti/bin/client_msg/msg.ts", []byte(tsCode.String()), 0644)
+	if err != nil {
+		log.Error("生成 TypeScript 代码失败: %v", err)
+	}
 }

+ 265 - 0
src/server/msg/test.pb.go

@@ -0,0 +1,265 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.28.1
+// 	protoc        v3.20.0
+// source: test.proto
+
+package msg
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Hello struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *Hello) Reset() {
+	*x = Hello{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_test_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Hello) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Hello) ProtoMessage() {}
+
+func (x *Hello) ProtoReflect() protoreflect.Message {
+	mi := &file_test_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Hello.ProtoReflect.Descriptor instead.
+func (*Hello) Descriptor() ([]byte, []int) {
+	return file_test_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Hello) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+type ReqLogin struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+}
+
+func (x *ReqLogin) Reset() {
+	*x = ReqLogin{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_test_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ReqLogin) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReqLogin) ProtoMessage() {}
+
+func (x *ReqLogin) ProtoReflect() protoreflect.Message {
+	mi := &file_test_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ReqLogin.ProtoReflect.Descriptor instead.
+func (*ReqLogin) Descriptor() ([]byte, []int) {
+	return file_test_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *ReqLogin) GetUserId() int64 {
+	if x != nil {
+		return x.UserId
+	}
+	return 0
+}
+
+type RespLogin struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+}
+
+func (x *RespLogin) Reset() {
+	*x = RespLogin{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_test_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *RespLogin) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RespLogin) ProtoMessage() {}
+
+func (x *RespLogin) ProtoReflect() protoreflect.Message {
+	mi := &file_test_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use RespLogin.ProtoReflect.Descriptor instead.
+func (*RespLogin) Descriptor() ([]byte, []int) {
+	return file_test_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *RespLogin) GetUserId() int64 {
+	if x != nil {
+		return x.UserId
+	}
+	return 0
+}
+
+var File_test_proto protoreflect.FileDescriptor
+
+var file_test_proto_rawDesc = []byte{
+	0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x6d, 0x73,
+	0x67, 0x22, 0x1b, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x23,
+	0x0a, 0x08, 0x52, 0x65, 0x71, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
+	0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65,
+	0x72, 0x49, 0x64, 0x22, 0x24, 0x0a, 0x09, 0x52, 0x65, 0x73, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
+	0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2f, 0x6d,
+	0x73, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_test_proto_rawDescOnce sync.Once
+	file_test_proto_rawDescData = file_test_proto_rawDesc
+)
+
+func file_test_proto_rawDescGZIP() []byte {
+	file_test_proto_rawDescOnce.Do(func() {
+		file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
+	})
+	return file_test_proto_rawDescData
+}
+
+var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_test_proto_goTypes = []interface{}{
+	(*Hello)(nil),     // 0: msg.Hello
+	(*ReqLogin)(nil),  // 1: msg.ReqLogin
+	(*RespLogin)(nil), // 2: msg.RespLogin
+}
+var file_test_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_test_proto_init() }
+func file_test_proto_init() {
+	if File_test_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Hello); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ReqLogin); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_test_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RespLogin); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_test_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   3,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_test_proto_goTypes,
+		DependencyIndexes: file_test_proto_depIdxs,
+		MessageInfos:      file_test_proto_msgTypes,
+	}.Build()
+	File_test_proto = out.File
+	file_test_proto_rawDesc = nil
+	file_test_proto_goTypes = nil
+	file_test_proto_depIdxs = nil
+}

+ 16 - 0
src/server/msg/test.proto

@@ -0,0 +1,16 @@
+syntax = "proto3";
+package msg;
+
+option go_package = "./msg";
+
+message Hello {
+	string name = 1;
+}
+
+message ReqLogin {
+	int64 user_id = 1;
+}
+
+message RespLogin {
+	int64 user_id = 1;
+}

+ 35 - 0
test.go

@@ -0,0 +1,35 @@
+package main
+
+import (
+	"encoding/binary"
+	"fmt"
+	"net"
+)
+
+func main() {
+	conn, err := net.Dial("tcp", "127.0.0.1:3563")
+	fmt.Print("test:")
+	if err != nil {
+		fmt.Print(err)
+		panic(err)
+	}
+
+	// Hello 消息(JSON 格式)
+	// 对应游戏服务器 Hello 消息结构体
+	data := []byte(`{
+		"Hello": {
+			"Name": "leaf"
+		}
+	}`)
+
+	// len + data
+	m := make([]byte, 2+len(data))
+
+	// 默认使用大端序
+	binary.BigEndian.PutUint16(m, uint16(len(data)))
+
+	copy(m[2:], data)
+
+	// 发送消息
+	conn.Write(m)
+}