Skip to content

错误码 ​

Cherry 的「错误」分两条独立的线,不要混用:

包用途取值范围
状态码github.com/cherry-game/cherry/code框架内部与业务返回给客户端的数值状态int32
errorgithub.com/cherry-game/cherry/errorGo 原生 error,用于函数返回值的错误传递—

状态码是给客户端看的,error 是给 Go 代码看的。

状态码 ​

import ccode "github.com/cherry-game/cherry/code"

框架状态码 ​

框架自己会产生这些码,覆盖会话、RPC、Actor 三类失败:

码常量含义
0OK成功
10SessionUIDNotBindsession 未绑定 UID
11DiscoveryNotFoundNode发现服务中找不到目标节点
12NodeRequestError节点请求失败
20RPCNetError网络错误
21RPCUnmarshalError反序列化错误
22RPCMarshalError序列化错误
23RPCRemoteExecuteError远程方法执行出错
24ActorPathIsNil目标 path 为空
25ActorFuncNameError函数名非法
26ActorConvertPathErrorpath 解析失败
27ActorMarshalError参数序列化失败
28ActorUnmarshalError参数反序列化失败
29ActorInvokeResultIsNilinvoke 结果为 nil
30ActorSourceEqualTarget源与目标相同
31ActorPublishRemoteError远程 publish 失败
32ActorChildIDNotFound子 Actor ID 未找到
33ActorCallTimeout调用超时
34ActorIDIsNilActor ID 为空
35ActorNotFoundActor 未找到
36ActorInvokeRemoteError远程 invoke 失败
37ActorResponseIsError响应是 error

判定辅助函数:

go
ccode.IsOK(code)   // code == 0
ccode.IsFail(code) // code != 0

状态码从哪来

Call / CallWait / CallType 的返回值就是这些码:

go
code := actor.CallWait(targetPath, "funcName", arg, &reply)
if ccode.IsFail(code) {
	clog.Warnf("调用失败, code = %d", code)
}

业务状态码 ​

业务码由你自己定义,和框架码不重叠即可(框架占用 0–37)。示例项目把业务码放在 internal/code:

go
const (
	OK = 0
	Error = 1

	PIDError = 100

	LoginError = 201
	AccountAuthFail = 202
	AccountBindFail = 203
	AccountTokenValidateFail = 204
	AccountNameIsExist = 205
	AccountRegisterError = 206
	AccountGetFail = 207

	PlayerDenyLogin = 301
	// ...
	PlayerIDError = 306
)

两套码共用一个 int32

框架码和业务码用的是同一个 int32 字段,靠取值不重叠来区分。业务码从 100 起就是为此。IsOK 只判断 == 0,所以两者可以混用同一个判定函数。

返回状态码给客户端 ​

在 Actor 里用 ResponseCode:

go
func (p *ActorPlayer) enter(session *cproto.Session, req *pb.EnterRequest) {
	newPlayerTable, errCode := db.CreatePlayer(session, req.PlayerName, serverId, row)
	if code.IsFail(errCode) {
		p.ResponseCode(session, errCode) // 把业务码回给客户端
		return
	}
	// ...
}

在连接层(gate 网关)用 Agent 的版本,第三个参数标记「这是错误响应」:

go
userToken, errCode := p.validateToken(req.Token)
if code.IsFail(errCode) {
	agent.Response(session, errCode)
	return
}

sdkRow := data.SdkConfig.Get(userToken.PID)
if sdkRow == nil {
	agent.ResponseCode(session, code.PIDError, true) // true = 错误响应
	return
}

ResponseCode 的第三个参数是 isError

两个同名方法签名不同,容易记错:

go
// Actor 内(pomelo.ActorBase)—— 没有第三个参数
func (p *ActorBase) ResponseCode(session *cproto.Session, statusCode int32)

// 连接层(pomelo.Agent)—— 可变参数,标记错误响应
func (a *Agent) ResponseCode(session *cproto.Session, statusCode int32, isError ...bool)

踢下线是另一个方法:Agent.Kick(reason, closed)。

状态码怎么传到客户端 ​

框架在 pomelo 层做分流:OK 走正常响应,非 OK 会把状态码塞进响应的 Code 字段并标记为错误:

go
func (p *Actor) response(rsp *cproto.PomeloResponse) {
	if ccode.IsOK(rsp.Code) {
		agent.ResponseMID(rsp.Mid, rsp.Data, false)
	} else {
		agent.ResponseMID(rsp.Mid, &cproto.Response{Code: rsp.Code}, true)
	}
}

客户端拿到的是 { code: <状态码>, data: ... } 结构。

状态码文案表 ​

数值状态码对玩家不友好,示例项目用一份配表做「码 → 文案」映射,由 data-config 组件 加载:

json
[
    { "code": 0,   "message": "ok" },
    { "code": 100, "message": "pid 错误" },
    { "code": 201, "message": "登录异常" },
    { "code": 204, "message": "token 校验失败" }
]

加载后注册进业务码包,HTTP 层用它渲染响应:

go
func RenderResult(c *cherryGin.Context, statusCode int32, data ...any) {
	result := NewDataResult(statusCode) // 内部用 GetMessage(code) 填 message
	c.JSON200(result)
}

查不到文案时 GetMessage 返回空字符串。

error 包 ​

import cerr "github.com/cherry-game/cherry/error"

构造错误 ​

只有四个函数:

go
func Error(text string) error                              // 同 errors.New
func Errorf(format string, a ...interface{}) error         // 格式化
func Wrap(err error, text string) error                    // 包装,产物形如 err:<err>, text:<text>
func Wrapf(err error, format string, a ...interface{}) error

没有 cerr.New

这个包没有 New 函数,别写成 cerr.New("..."),用 cerr.Error("...")。

go
return cerr.Error("配置缺失")
return cerr.Errorf("节点 %s 未找到", nodeID)
return cerr.Wrap(err, "读取配表失败")

预定义错误 ​

框架把常用的错误做成了可直接比较的哨兵值,按类别分组:

路由

变量含义
RouteFieldCantEmpty路由字段为空
RouteInvalid路由格式非法

数据包

变量含义
PacketWrongType包类型错误
PacketSizeExceed包大小超限
PacketConnectClosed连接已关闭
PacketInvalidHeader包头非法
PacketMsgSmallerThanExpected消息长度小于预期

消息

变量含义
MessageWrongType消息类型错误
MessageInvalid消息非法
MessageRouteNotFound路由未找到

集群

变量含义
ClusterClientIsStop集群客户端已停止
ClusterRequestTimeout集群请求超时
ClusterPacketMarshalFail集群包序列化失败
ClusterPacketUnmarshalFail集群包反序列化失败
ClusterPublishFail发布失败
ClusterRequestFail请求失败
ClusterNodeTypeIsNil节点类型为空
ClusterNodeTypeMemberNotFound该类型下无成员

其他

变量含义
DiscoveryNotFoundNode发现服务找不到节点
ActorPathErrorActor 路径错误
FuncIsNil / FuncTypeError反射调用相关
ProtobufWrongValueTypeprotobuf 值类型错误

用 errors.Is 判断:

go
if errors.Is(err, cerr.PacketSizeExceed) {
	clog.Warn("客户端发来的包超限")
}

排查建议 ​

现象可能是
客户端收到 code = 10消息在登录前就发出,session 还没绑定 UID
code = 35(ActorNotFound)Actor 路径写错,或目标 Actor 还没创建
code = 33(ActorCallTimeout)目标 Actor 处理超时,默认 3 秒,见 Actor 模型
code = 11目标节点没启动,或 master 没先启动导致发现服务里没有它
包超限见 网络数据包 - 包大小限制