AGENTS.md 11 KB

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

构建命令

# 初始化项目(安装protoc-gen-go)
make init

# 设置脚本权限
make chmod

# 构建完整项目(交互模式)
make build
# 非交互模式: make build <env> <update_excel> <publish>
# 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框架

测试命令

# 运行所有测试
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节点即可进行本地调试。

# 版本查看
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/*)
  • 使用别名提高可读性(如ctimecfacadecprotoclog
  • 不同组之间用空行分隔
  • 内部包按功能分组:类型 → 常量 → 组件 → pb → 名称别名

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

命名规范

  • 包名: 小写,尽量使用单单词(playergameinternal
  • 变量: 驼峰命名(局部变量小写开头,导出使用PascalCase)
  • 常量: 大写下划线命名(如OKErrorPlayerNotFound)或PascalCase(如NodeCenter
  • 函数: 导出函数使用PascalCase,未导出使用camelCase
  • 方法接收者: 统一使用p作为接收者名称
  • ID字段: 正确大小写(如HeroID而非HeroIdHeroid
  • 错误码: 语义化命名,格式为模块_操作_状态(如HeroIDNotFoundPlayerNotLogin

类型规范

  • 整数: 始终使用int32int64,绝不使用int
  • 布尔值: 统一使用bool类型
  • 字符串: 文本数据使用string
  • 自定义类型: 在internal/types/types.go中注册(如AssetsPairMap
  • 时间: 使用ctime.Now().ToMillisecond()获取毫秒时间戳

错误处理

  • 错误作为最后一个参数返回
  • 使用internal/code包中的结构化错误码
  • 使用cherry的日志记录错误和上下文

    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()方法提交保存

    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示例

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

远程调用

// 调用远程服务
result, errCode := cherryApp.Call(
    targetPath,    // 目标Actor路径
    methodName,    // 方法名(使用nameRemote中的常量)
    request,       // 请求消息
)

// 使用nameRemote包中的常量
// internal/name/remote/player.go: Player_SendGM, Player_AssetAdd等

名称常量规范

所有字符串常量必须定义在internal/name/目录下,按类型分组:

// 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,按模块分组:

// 基础错误码
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包

    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等