# AGENTS.md - F1游戏服务器开发指南 ## 重要要求 **所有使用此文档的代理都必须使用中文回复用户。** 这是强制要求,不得使用其他语言回复。 ## 概述 基于Go语言和Cherry框架的F1游戏服务器,采用分布式Actor架构,核心玩法为地图沙盘系统。项目包含game、gate、center、master、web、chat、map等节点,通过NATS集群通信。 ## 环境要求 - Go 1.24+ - MongoDB 7.0+ - protoc v3.17.3 - protoc-gen-go v1.36.8 ## 构建命令 ```bash # 初始化项目(安装protoc-gen-go) make init # 设置脚本权限 make chmod # 构建完整项目(交互模式) make build # 非交互模式: make build # env: 1=dev, 2=test, 3=dev&test, 4=prod # update_excel: y/Y/1 更新Excel数据 # publish: y/Y/1 推送到发布仓库 # 仅构建源代码 make code # 构建Excel数据文件 make excel # 构建protobuf文件(包含route配置生成) make proto # 清理构建产物 make clean # 更新外部仓库 make pull-excel # 更新Excel配置 make pull-proto # 更新protobuf定义 make pull-cherry # 更新Cherry框架 ``` ## 测试命令 ```bash # 运行所有测试 go test ./... # 运行特定测试文件 go test ./tool/test/slice/slice_test.go # 运行特定包测试 go test ./internal/algorithm/astar/ # 运行单个测试函数 go test -run TestAstar ./internal/algorithm/astar/ # 详细输出 go test -v ./path/to/test # 运行基准测试 go test -bench=. ./path/to/test # 生成覆盖率报告 go test -cover ./path/to/package ``` ## 节点启动 **本地调试说明**: 内部已部署整套`dev环境`,开发人员仅需启动`game节点`即可进行本地调试。 ```bash # 版本查看 run version # 启动game节点 cd nodes/game && run -path=../../etc/profile/dev.json -node=1 # 启动其他节点类型 cd nodes/master && run -path=../../etc/profile/dev.json -node=master-1 cd nodes/center && run -path=../../etc/profile/dev.json -node=center-1 cd nodes/gate && run -path=../../etc/profile/dev.json -node=gate-1 cd nodes/web && run -path=../../etc/profile/dev.json -node=web-1 cd nodes/map && run -path=../../etc/profile/dev.json -node=map-1 cd nodes/chat && run -path=../../etc/profile/dev.json -node=chat-1 ``` ## 术语定义 - `node` - 节点,每个服务进程称为一个节点,通过节点类型归类 - `pid` - 平台id - `uid` - 游戏内用户唯一id - `playerID` - 玩家id - `openID` - 第三方用户唯一id ## 代码风格指南 ### Import组织 - 分组顺序:标准库 → 第三方库 → 内部包(f1-game/*) - 使用别名提高可读性(如`ctime`、`cfacade`、`cproto`、`clog`) - 不同组之间用空行分隔 - 内部包按功能分组:类型 → 常量 → 组件 → pb → 名称别名 ```go import ( "context" "fmt" "time" ctime "github.com/cherry-game/cherry/extend/time" cfacade "github.com/cherry-game/cherry/facade" clog "github.com/cherry-game/cherry/logger" cproto "github.com/cherry-game/cherry/net/proto" "go.mongodb.org/mongo-driver/v2/bson" capp "f1-game/internal/cherry_app" "f1-game/internal/component/db" "f1-game/internal/data" "f1-game/internal/enum" nameDB "f1-game/internal/name/db" nameRedis "f1-game/internal/name/redis" "f1-game/internal/pb" dbPlayer "f1-game/nodes/game/internal/db/data/player" ) ``` ### 命名规范 - **包名**: 小写,尽量使用单单词(`player`、`game`、`internal`) - **变量**: 驼峰命名(局部变量小写开头,导出使用PascalCase) - **常量**: 大写下划线命名(如`OK`、`Error`、`PlayerNotFound`)或PascalCase(如`NodeCenter`) - **函数**: 导出函数使用PascalCase,未导出使用camelCase - **方法接收者**: 统一使用`p`作为接收者名称 - **ID字段**: 正确大小写(如`HeroID`而非`HeroId`或`Heroid`) - **错误码**: 语义化命名,格式为`模块_操作_状态`(如`HeroIDNotFound`、`PlayerNotLogin`) ### 类型规范 - **整数**: 始终使用`int32`或`int64`,绝不使用`int` - **布尔值**: 统一使用`bool`类型 - **字符串**: 文本数据使用`string` - **自定义类型**: 在`internal/types/types.go`中注册(如`Assets`、`Pair`、`Map`) - **时间**: 使用`ctime.Now().ToMillisecond()`获取毫秒时间戳 ### 错误处理 - 错误作为最后一个参数返回 - 使用`internal/code`包中的结构化错误码 - 使用cherry的日志记录错误和上下文 ```go if err != nil { p.Error("[operation] failed. playerID=%d, err=%v", playerID, err) return code.ErrInvalidParam } if code.IsFail(errCode) { p.ResponseCode(session, errCode) return } // 错误码判断 if code.IsOK(errCode) { // 成功处理 } ``` ### 注释规范 - **不添加任何注释**,除非用户明确要求 - 代码应通过命名和结构自解释 ## 项目结构 ### 节点类型 - **game**: 核心游戏逻辑和玩家会话(滚服部署,可多实例) - **gate**: WebSocket/HTTP网关(可多实例,支持SLB负载均衡) - **center**: 账户管理和支付服务(单节点部署) - **master**: 节点发现服务(NATS模式,单节点部署) - **web**: HTTP API端点(可多实例) - **chat**: 聊天功能(可多实例) - **map**: 地图沙盘玩法(核心功能,可多实例) - **record**: 游戏数据记录 - **ops**: 运维接口(单节点部署) - **match**: 匹配功能 ### 关键目录 - `nodes/`: 各节点代码,每个目录是独立进程 - `internal/pb/`: 协议文件(从f1/protocol.git生成) - `internal/data/`: Excel配置生成的Go代码 - `etc/data/`: Excel导出的JSON配置文件 - `internal/name/`: 名称常量目录 - `actor/`: Actor名称常量 - `local/`: 本地函数名称常量 - `remote/`: 远程调用名称常量 - `event/`: 事件名称常量 - `route/`: 路由名称常量 - `redis/`: Redis键名常量 - `db/`: 数据库表名和字段名常量 - `key/`: 其他键名常量 - `internal/enum/`: 枚举常量定义 - `internal/code/`: 错误码定义 - `internal/types/`: 自定义类型定义 - `internal/component/`: 公共组件(redis、db、sdk、rate等) - `internal/algorithm/`: 算法实现(astar、aoi等) - `nodes/{node}/internal/db/`: MongoDB文档映射(GORM风格) - `nodes/{node}/internal/facade/`: 节点特有的Facade接口定义 ### 数据库表结构 - 每个表结构嵌入`dbFacade.TableBase` - 实现`TableName()`方法返回表名 - 实现`PrimaryValue()`方法返回主键 - 实现`CheckField()`方法检查字段 - 实现`Save2Queue()`方法提交保存 ```go type PlayerTable struct { PlayerID int64 `bson:"PlayerID,omitempty"` PID int32 `bson:"PID,omitempty"` dbPlayer.Data `bson:"Data,omitempty"` dbFacade.TableBase `bson:",inline"` } func (*PlayerTable) TableName() string { return nameDB.Table_Player } func (p *PlayerTable) PrimaryValue() any { return p.PlayerID } ``` ## Actor模式 ### 基本结构 - 使用`p`作为方法接收者名称 - 在`OnInit`方法中注册本地函数:`p.Local().Register(name, handler)` - 注册事件处理器:`p.EventRegister(eventName, handler)` - 响应客户端:`p.Response(session, response)` - 响应错误码:`p.ResponseCode(session, errCode)` ### Actor示例 ```go type actorPlayer struct { capp.ActorLogger playerID int64 isOnline bool } func (p *actorPlayer) OnInit() { p.Local().Register(nameLocal.Player_Time, p.time) p.EventRegister(nameEvent.Lord_LevelUp, p.playerLordLevelUp) } func (p *actorPlayer) time(session *cproto.Session, _ *pb.None) { p.Response(session, &pb.I64{Value: ctime.Now().ToMillisecond()}) } ``` ### 远程调用 ```go // 调用远程服务 result, errCode := cherryApp.Call( targetPath, // 目标Actor路径 methodName, // 方法名(使用nameRemote中的常量) request, // 请求消息 ) // 使用nameRemote包中的常量 // internal/name/remote/player.go: Player_SendGM, Player_AssetAdd等 ``` ## 名称常量规范 所有字符串常量必须定义在`internal/name/`目录下,按类型分组: ```go // internal/name/local/player.go - 本地函数名 const ( Player_Time = "time" Player_ItemUse = "itemUse" ) // internal/name/remote/player.go - 远程调用名 const ( Player_SendGM = "sendGM" Player_AssetAdd = "assetAdd" ) // internal/name/route/player.go - 路由名(节点.功能.方法) const ( GamePlayer_Time = "game.player.time" GamePlayer_ItemUse = "game.player.itemUse" ) // internal/name/event/event.go - 事件名 const ( Player_Create = "player_create" Lord_LevelUp = "lord_levelup" ) ``` ## 错误码规范 错误码定义在`internal/code/status_code.go`,按模块分组: ```go // 基础错误码 const ( OK int32 = 0 Error int32 = 1 ) // 错误码格式:(100 + 模块号) * 1000 + 错误号 // 例如:玩家模块(101xxx)、英雄模块(102xxx)、道具模块(103xxx) const ( PlayerNotFound int32 = 101004 // 玩家未找到 HeroIDNotFound int32 = 102001 // 英雄id未找到 ItemNotEnough int32 = 103006 // 道具数量不够 ) ``` ## 测试规范 - 测试文件以`_test.go`结尾 - 使用表驱动测试处理多测试用例 - 测试文件可放在被测包目录或`tool/test/`目录 - 使用标准testing包 ```go func TestFunction(t *testing.T) { tests := []struct { name string input *InputType expected *ExpectedType }{ {"case1", input1, expected1}, {"case2", input2, expected2}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := functionUnderTest(tt.input) if result != tt.expected { t.Errorf("got %v, want %v", result, tt.expected) } }) } } ``` ## 技术栈 - **框架**: Cherry v1.4.18(基于Actor模型的分布式框架) - **通信**: NATS集群 + Protocol Buffers - **数据库**: MongoDB v2.2.2(持久化)+ Redis v8(缓存) - **ORM**: GORM风格TableBase - **Go版本**: 1.24 ## 特殊约定 ### 名称常量 - 所有字符串常量定义在`internal/name/`目录下,按类型分组 - 路由格式:`{节点类型}.{功能模块}.{方法名}`(如`game.player.itemUse`) ### Excel数据 - 游戏配置通过Excel生成,位于`etc/data/`和`internal/data/` - 使用`make excel`更新数据 ### 协议文件 - 从f1/protocol.git生成,使用`make proto`更新 - 协议文件在`internal/pb/`目录 ### 数据库表 - 每个节点有自己的db目录,使用GORM风格的TableBase - 表名和字段名使用`internal/name/db/`中的常量 ### Git设置 - 关闭Auto Crlf convert - Goland设置Line Separator为Unix and Macos ### Facade模式 - 节点特有接口定义在`nodes/{node}/internal/facade/` - 使用接口定义服务能力,便于模块间解耦 ### 组件系统 - 公共组件位于`internal/component/` - 包括:redis、db、sdk、rate(限流)、func_load等