DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,89 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"longheng.io/server/pkg/serializer"
|
||||
)
|
||||
|
||||
var topicPayloadMakers = map[string]func() any{
|
||||
TopicServiceStarted: func() any { return &ServiceStarted{} },
|
||||
TopicClusterServiceStarted: func() any { return &ClusterServiceEvent{} },
|
||||
TopicClusterServiceState: func() any { return &ClusterServiceEvent{} },
|
||||
TopicClusterServiceBeat: func() any { return &ClusterServiceEvent{} },
|
||||
TopicClusterServiceStopped: func() any { return &ClusterServiceEvent{} },
|
||||
TopicGatewayClientPacket: func() any { return &GatewayClientPacket{} },
|
||||
TopicGatewayPush: func() any { return &GatewayPush{} },
|
||||
TopicLogicPlayerResponse: func() any { return &LogicPlayerResponse{} },
|
||||
TopicSessionConnected: func() any { return &SessionConnected{} },
|
||||
TopicSessionDisconnected: func() any { return &SessionDisconnected{} },
|
||||
TopicPlayerStateChange: func() any { return &PlayerStateChanged{} },
|
||||
TopicChatBroadcast: func() any { return &ChatBroadcast{} },
|
||||
TopicNoticePublished: func() any { return &NoticePublished{} },
|
||||
TopicRoomAllocated: func() any { return &RoomAllocated{} },
|
||||
}
|
||||
|
||||
// MarshalTopicPayload 使用框架默认 JSON 编码器序列化 topic 负载。
|
||||
func MarshalTopicPayload(payload any) ([]byte, error) {
|
||||
codec, err := serializer.DefaultRegistry.MustGet("json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return codec.Marshal(payload)
|
||||
}
|
||||
|
||||
// UnmarshalTopicPayload 按 topic 反序列化为已知契约结构,未知 topic 返回 map。
|
||||
func UnmarshalTopicPayload(topic string, data []byte) (any, error) {
|
||||
factory := topicPayloadFactory(topic)
|
||||
if factory == nil {
|
||||
var payload map[string]any
|
||||
if err := unmarshalJSON(data, &payload); err != nil {
|
||||
return nil, fmt.Errorf("decode topic %q: %w", topic, err)
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
target := factory()
|
||||
if err := unmarshalJSON(data, target); err != nil {
|
||||
return nil, fmt.Errorf("decode topic %q: %w", topic, err)
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(target)
|
||||
if value.Kind() == reflect.Pointer && !value.IsNil() {
|
||||
return value.Elem().Interface(), nil
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func topicPayloadFactory(topic string) func() any {
|
||||
if factory, ok := topicPayloadMakers[topic]; ok {
|
||||
return factory
|
||||
}
|
||||
switch {
|
||||
case IsLogicNodePacketTopic(topic):
|
||||
return func() any { return &GatewayClientPacket{} }
|
||||
case IsLogicSessConnTopic(topic):
|
||||
return func() any { return &SessionConnected{} }
|
||||
case IsLogicSessDiscTopic(topic):
|
||||
return func() any { return &SessionDisconnected{} }
|
||||
case IsGatewayNodePushTopic(topic):
|
||||
return func() any { return &GatewayPush{} }
|
||||
case IsGatewayNodeRespTopic(topic):
|
||||
return func() any { return &LogicPlayerResponse{} }
|
||||
case IsRPCNodeRequestTopic(topic):
|
||||
return func() any { return &RPCRequest{} }
|
||||
case IsRPCResponseTopic(topic):
|
||||
return func() any { return &RPCResponse{} }
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalJSON(data []byte, target any) error {
|
||||
codec, err := serializer.DefaultRegistry.MustGet("json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return codec.Unmarshal(data, target)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Package contracts 定义服务和 SDK 相关包共享的稳定消息、事件、指标和 RPC 契约。
|
||||
//
|
||||
// 这里的类型应该谨慎变更;它们是 gateway、logic、runtime 模块、生成客户端和运维工具之间的兼容层。
|
||||
package contracts
|
||||
@@ -0,0 +1,141 @@
|
||||
package contracts
|
||||
|
||||
import "time"
|
||||
|
||||
// ServiceStarted 描述单节点服务启动事件。
|
||||
type ServiceStarted struct {
|
||||
Service string `json:"service"`
|
||||
NodeID string `json:"node_id"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
}
|
||||
|
||||
// ClusterServiceEvent 描述服务在集群拓扑中的状态、地址和版本信息。
|
||||
type ClusterServiceEvent struct {
|
||||
ClusterID string `json:"cluster_id"`
|
||||
GID int64 `json:"gid,omitempty"`
|
||||
VirtualGID int64 `json:"virtual_gid,omitempty"`
|
||||
ShardID int64 `json:"shard_id,omitempty"`
|
||||
Service string `json:"service"`
|
||||
NodeID string `json:"node_id"`
|
||||
Environment string `json:"environment"`
|
||||
Zone string `json:"zone"`
|
||||
PublicAddr string `json:"public_addr,omitempty"`
|
||||
PrivateAddr string `json:"private_addr,omitempty"`
|
||||
AdminAddr string `json:"admin_addr,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
DataVersion string `json:"data_version,omitempty"`
|
||||
State string `json:"state"`
|
||||
Maintaining bool `json:"maintaining"`
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
// GatewayClientPacket 是 gateway 转发到 logic 的客户端请求包。
|
||||
type GatewayClientPacket struct {
|
||||
GatewayNodeID string `json:"gateway_node_id"`
|
||||
TargetLogicNodeID string `json:"target_logic_node_id,omitempty"`
|
||||
SessionID string `json:"session_id"`
|
||||
AccountID string `json:"account_id"`
|
||||
PacketID int32 `json:"packet_id"`
|
||||
MsgID uint32 `json:"msg_id"`
|
||||
Sequence uint64 `json:"sequence,omitempty"`
|
||||
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
||||
ClientRequestID string `json:"client_request_id,omitempty"`
|
||||
WireFormat string `json:"wire_format,omitempty"`
|
||||
Compressed bool `json:"compressed"`
|
||||
Body []byte `json:"body,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
ReceivedAt time.Time `json:"received_at"`
|
||||
}
|
||||
|
||||
// LogicPlayerResponse 是 logic 返回给 gateway 的玩家响应包。
|
||||
type LogicPlayerResponse struct {
|
||||
TargetGatewayNodeID string `json:"target_gateway_node_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
AccountID string `json:"account_id"`
|
||||
PacketID int32 `json:"packet_id"`
|
||||
MsgID uint32 `json:"msg_id"`
|
||||
Sequence uint64 `json:"sequence,omitempty"`
|
||||
WireFormat string `json:"wire_format,omitempty"`
|
||||
Compressed bool `json:"compressed"`
|
||||
Body []byte `json:"body,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// GatewayPush 是服务侧推送到 gateway 的单人或广播消息。
|
||||
type GatewayPush struct {
|
||||
TargetGatewayNodeID string `json:"target_gateway_node_id,omitempty"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
AccountID string `json:"account_id,omitempty"`
|
||||
Broadcast bool `json:"broadcast,omitempty"`
|
||||
PacketID int32 `json:"packet_id"`
|
||||
MsgID uint32 `json:"msg_id"`
|
||||
Sequence uint64 `json:"sequence,omitempty"`
|
||||
WireFormat string `json:"wire_format,omitempty"`
|
||||
Compressed bool `json:"compressed"`
|
||||
Body []byte `json:"body,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// SessionConnected 描述会话绑定到账号后的上线事件。
|
||||
type SessionConnected struct {
|
||||
TargetLogicNodeID string `json:"target_logic_node_id,omitempty"`
|
||||
SessionID string `json:"session_id"`
|
||||
AccountID string `json:"account_id"`
|
||||
ConnectedAt time.Time `json:"connected_at"`
|
||||
}
|
||||
|
||||
// SessionDisconnected 描述会话断开和下线原因。
|
||||
type SessionDisconnected struct {
|
||||
TargetLogicNodeID string `json:"target_logic_node_id,omitempty"`
|
||||
SessionID string `json:"session_id"`
|
||||
AccountID string `json:"account_id"`
|
||||
Reason string `json:"reason"`
|
||||
DisconnectedAt time.Time `json:"disconnected_at"`
|
||||
}
|
||||
|
||||
// PlayerStateChanged 描述玩家状态变化事件。
|
||||
type PlayerStateChanged struct {
|
||||
AccountID string `json:"account_id"`
|
||||
State string `json:"state"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ChatBroadcast 描述社交聊天广播事件。
|
||||
type ChatBroadcast struct {
|
||||
MessageID string `json:"message_id,omitempty"`
|
||||
Seq uint64 `json:"seq,omitempty"`
|
||||
Channel string `json:"channel"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to,omitempty"`
|
||||
Message string `json:"message"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
SentAt time.Time `json:"sent_at"`
|
||||
}
|
||||
|
||||
// NoticePublished 描述公告发布后的事件负载。
|
||||
type NoticePublished struct {
|
||||
NoticeID string `json:"notice_id"`
|
||||
Kind string `json:"kind"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
ShardID string `json:"shard_id,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
AttachmentRef string `json:"attachment_ref,omitempty"`
|
||||
Recipients []string `json:"recipients,omitempty"`
|
||||
DeliveryIDs []string `json:"delivery_ids,omitempty"`
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
}
|
||||
|
||||
// RoomAllocated 描述场景房间分配结果。
|
||||
type RoomAllocated struct {
|
||||
RoomID string `json:"room_id"`
|
||||
AccountID string `json:"account_id"`
|
||||
AllocatedAt time.Time `json:"allocated_at"`
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// MetricPrefixLongHeng 是所有框架指标名的统一前缀。
|
||||
MetricPrefixLongHeng = "longheng"
|
||||
|
||||
// MetricLabelService 标记服务名。
|
||||
MetricLabelService = "service"
|
||||
|
||||
// MetricLabelNode 标记节点 ID。
|
||||
MetricLabelNode = "node"
|
||||
|
||||
// MetricLabelRoute 标记协议或 RPC 路由。
|
||||
MetricLabelRoute = "route"
|
||||
|
||||
// MetricLabelResult 标记操作结果。
|
||||
MetricLabelResult = "result"
|
||||
|
||||
// MetricLabelStatus 标记状态值。
|
||||
MetricLabelStatus = "status"
|
||||
|
||||
// MetricLabelComponent 标记平台或运行时组件。
|
||||
MetricLabelComponent = "component"
|
||||
|
||||
// MetricLabelKind 标记对象类型。
|
||||
MetricLabelKind = "kind"
|
||||
|
||||
// MetricLabelGID 标记游戏区或战区 ID。
|
||||
MetricLabelGID = "gid"
|
||||
|
||||
// MetricLabelSID 标记分片 ID。
|
||||
MetricLabelSID = "sid"
|
||||
)
|
||||
|
||||
const (
|
||||
// MetricResultOK 表示操作成功。
|
||||
MetricResultOK = "ok"
|
||||
|
||||
// MetricResultError 表示操作失败。
|
||||
MetricResultError = "error"
|
||||
|
||||
// MetricResultDropped 表示请求或事件被丢弃。
|
||||
MetricResultDropped = "dropped"
|
||||
|
||||
// MetricResultTimeout 表示操作超时。
|
||||
MetricResultTimeout = "timeout"
|
||||
)
|
||||
|
||||
// MetricName 生成跨代码、告警和仪表盘共用的稳定指标名。
|
||||
func MetricName(parts ...string) string {
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
out = append(out, metricNameFields(part)...)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(out, "_")
|
||||
}
|
||||
|
||||
// PlatformMetric 生成平台核心能力指标名,例如 longheng_platform_bus_publish_total。
|
||||
func PlatformMetric(component string, parts ...string) string {
|
||||
return MetricName(append([]string{MetricPrefixLongHeng, "platform", component}, parts...)...)
|
||||
}
|
||||
|
||||
// RuntimeMetric 生成可选系统模块指标名,例如 longheng_runtime_matchmaker_ticket_total。
|
||||
func RuntimeMetric(component string, parts ...string) string {
|
||||
return MetricName(append([]string{MetricPrefixLongHeng, "runtime", component}, parts...)...)
|
||||
}
|
||||
|
||||
// ServiceMetric 生成服务层指标名,例如 longheng_service_gateway_connections_total。
|
||||
func ServiceMetric(service string, parts ...string) string {
|
||||
return MetricName(append([]string{MetricPrefixLongHeng, "service", service}, parts...)...)
|
||||
}
|
||||
|
||||
// MetricLabels 构造指标标签并跳过空 key,避免调用侧反复写样板代码。
|
||||
func MetricLabels(values ...string) map[string]string {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
labels := make(map[string]string, len(values)/2)
|
||||
for idx := 0; idx+1 < len(values); idx += 2 {
|
||||
key := strings.TrimSpace(values[idx])
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
labels[key] = strings.TrimSpace(values[idx+1])
|
||||
}
|
||||
if len(labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
// ShardLabels 是区服/分片指标的通用标签构造器。
|
||||
func ShardLabels(gid, sid int64) map[string]string {
|
||||
return MetricLabels(
|
||||
MetricLabelGID, strconv.FormatInt(gid, 10),
|
||||
MetricLabelSID, strconv.FormatInt(sid, 10),
|
||||
)
|
||||
}
|
||||
|
||||
func metricNameFields(part string) []string {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
return nil
|
||||
}
|
||||
fields := strings.FieldsFunc(part, func(r rune) bool {
|
||||
return !unicode.IsLetter(r) && !unicode.IsDigit(r)
|
||||
})
|
||||
out := make([]string, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
field = strings.ToLower(strings.Trim(field, "_"))
|
||||
if field != "" {
|
||||
out = append(out, field)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package contracts
|
||||
|
||||
import "time"
|
||||
|
||||
// RPCRequest 是跨节点 RPC 请求的总线契约。
|
||||
type RPCRequest struct {
|
||||
RequestID string `json:"request_id"`
|
||||
SourceService string `json:"source_service,omitempty"`
|
||||
SourceNodeID string `json:"source_node_id"`
|
||||
TargetNodeID string `json:"target_node_id"`
|
||||
Route string `json:"route"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Payload []byte `json:"payload,omitempty"`
|
||||
SentAt time.Time `json:"sent_at"`
|
||||
}
|
||||
|
||||
// RPCResponse 是跨节点 RPC 响应的总线契约。
|
||||
type RPCResponse struct {
|
||||
RequestID string `json:"request_id"`
|
||||
SourceNodeID string `json:"source_node_id,omitempty"`
|
||||
TargetNodeID string `json:"target_node_id"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Payload []byte `json:"payload,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
SentAt time.Time `json:"sent_at"`
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TopicScope 表示带作用域 topic 的层级类型。
|
||||
type TopicScope string
|
||||
|
||||
const (
|
||||
// TopicScopeService 表示服务作用域 topic。
|
||||
TopicScopeService TopicScope = "service"
|
||||
|
||||
// TopicScopeZone 表示区服作用域 topic。
|
||||
TopicScopeZone TopicScope = "zone"
|
||||
|
||||
// TopicScopeShard 表示分片作用域 topic。
|
||||
TopicScopeShard TopicScope = "shard"
|
||||
|
||||
// TopicServiceStarted 是单节点服务启动事件 topic。
|
||||
TopicServiceStarted = "service.started"
|
||||
|
||||
// TopicClusterServiceStarted 是集群服务启动事件 topic。
|
||||
TopicClusterServiceStarted = "cluster.service.started"
|
||||
|
||||
// TopicClusterServiceState 是集群服务状态变化事件 topic。
|
||||
TopicClusterServiceState = "cluster.service.state"
|
||||
|
||||
// TopicClusterServiceBeat 是集群服务心跳事件 topic。
|
||||
TopicClusterServiceBeat = "cluster.service.heartbeat"
|
||||
|
||||
// TopicClusterServiceStopped 是集群服务停止事件 topic。
|
||||
TopicClusterServiceStopped = "cluster.service.stopped"
|
||||
|
||||
// TopicGatewayClientPacket 是 gateway 收到客户端包后的事件 topic。
|
||||
TopicGatewayClientPacket = "gateway.client.packet"
|
||||
|
||||
// TopicGatewayPush 是推送到 gateway 的消息 topic。
|
||||
TopicGatewayPush = "gateway.push"
|
||||
|
||||
// TopicLogicPlayerResponse 是 logic 返回玩家响应的事件 topic。
|
||||
TopicLogicPlayerResponse = "logic.player.response"
|
||||
|
||||
// TopicSessionConnected 是会话上线事件 topic。
|
||||
TopicSessionConnected = "gateway.session.connected"
|
||||
|
||||
// TopicSessionDisconnected 是会话下线事件 topic。
|
||||
TopicSessionDisconnected = "gateway.session.disconnected"
|
||||
|
||||
// TopicPlayerStateChange 是玩家状态变化事件 topic。
|
||||
TopicPlayerStateChange = "logic.player.state.changed"
|
||||
|
||||
// TopicChatBroadcast 是聊天广播事件 topic。
|
||||
TopicChatBroadcast = "social.chat.broadcast"
|
||||
|
||||
// TopicNoticePublished 是公告发布事件 topic。
|
||||
TopicNoticePublished = "notice.published"
|
||||
|
||||
// TopicRoomAllocated 是场景房间分配事件 topic。
|
||||
TopicRoomAllocated = "scene.room.allocated"
|
||||
|
||||
topicLogicPrefix = "logic.node."
|
||||
logicPacketSuffix = ".packet"
|
||||
sessionOnSuffix = ".session.connected"
|
||||
sessionOffSuffix = ".session.disconnected"
|
||||
topicGwNodePrefix = "gateway.node."
|
||||
topicGwPushSuffix = ".push"
|
||||
topicGwRespSuffix = ".response"
|
||||
topicRPCNodePrefix = "rpc.node."
|
||||
topicRPCReqSuffix = ".request"
|
||||
topicRPCRespPrefix = "rpc.response."
|
||||
topicRPCRespSuffix = ".reply"
|
||||
)
|
||||
|
||||
// ScopedTopic 是解析后的作用域 topic 结构。
|
||||
type ScopedTopic struct {
|
||||
Scope TopicScope `json:"scope"`
|
||||
Service string `json:"service,omitempty"`
|
||||
ZoneID string `json:"zone_id,omitempty"`
|
||||
GroupID string `json:"group_id,omitempty"`
|
||||
ShardID string `json:"shard_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// LogicNodePacketTopic 返回指定 logic 节点接收客户端包的 topic。
|
||||
func LogicNodePacketTopic(nodeID string) string {
|
||||
return logicNodeTopic(nodeID, logicPacketSuffix)
|
||||
}
|
||||
|
||||
// LogicSessConnTopic 返回指定 logic 节点接收会话上线事件的 topic。
|
||||
func LogicSessConnTopic(nodeID string) string {
|
||||
return logicNodeTopic(nodeID, sessionOnSuffix)
|
||||
}
|
||||
|
||||
// LogicSessDiscTopic 返回指定 logic 节点接收会话下线事件的 topic。
|
||||
func LogicSessDiscTopic(nodeID string) string {
|
||||
return logicNodeTopic(nodeID, sessionOffSuffix)
|
||||
}
|
||||
|
||||
// IsLogicNodePacketTopic 判断 topic 是否为 logic 节点客户端包 topic。
|
||||
func IsLogicNodePacketTopic(topic string) bool {
|
||||
return isLogicNodeTopic(topic, logicPacketSuffix)
|
||||
}
|
||||
|
||||
// IsLogicSessConnTopic 判断 topic 是否为 logic 节点会话上线 topic。
|
||||
func IsLogicSessConnTopic(topic string) bool {
|
||||
return isLogicNodeTopic(topic, sessionOnSuffix)
|
||||
}
|
||||
|
||||
// IsLogicSessDiscTopic 判断 topic 是否为 logic 节点会话下线 topic。
|
||||
func IsLogicSessDiscTopic(topic string) bool {
|
||||
return isLogicNodeTopic(topic, sessionOffSuffix)
|
||||
}
|
||||
|
||||
// GatewayNodePushTopic 返回指定 gateway 节点接收推送的 topic。
|
||||
func GatewayNodePushTopic(nodeID string) string {
|
||||
return nodeTopic(topicGwNodePrefix, nodeID, topicGwPushSuffix)
|
||||
}
|
||||
|
||||
// IsGatewayNodePushTopic 判断 topic 是否为 gateway 节点推送 topic。
|
||||
func IsGatewayNodePushTopic(topic string) bool {
|
||||
return isNodeTopic(topic, topicGwNodePrefix, topicGwPushSuffix)
|
||||
}
|
||||
|
||||
// GatewayNodeRespTopic 返回指定 gateway 节点接收 logic 玩家响应的 topic。
|
||||
func GatewayNodeRespTopic(nodeID string) string {
|
||||
return nodeTopic(topicGwNodePrefix, nodeID, topicGwRespSuffix)
|
||||
}
|
||||
|
||||
// IsGatewayNodeRespTopic 判断 topic 是否为 gateway 节点玩家响应 topic。
|
||||
func IsGatewayNodeRespTopic(topic string) bool {
|
||||
return isNodeTopic(topic, topicGwNodePrefix, topicGwRespSuffix)
|
||||
}
|
||||
|
||||
// RPCNodeRequestTopic 返回指定节点接收 RPC 请求的 topic。
|
||||
func RPCNodeRequestTopic(nodeID string) string {
|
||||
return nodeTopic(topicRPCNodePrefix, nodeID, topicRPCReqSuffix)
|
||||
}
|
||||
|
||||
// RPCResponseTopic 返回指定节点接收 RPC 响应的 topic。
|
||||
func RPCResponseTopic(nodeID string) string {
|
||||
return nodeTopic(topicRPCRespPrefix, nodeID, topicRPCRespSuffix)
|
||||
}
|
||||
|
||||
// IsRPCNodeRequestTopic 判断 topic 是否为节点 RPC 请求 topic。
|
||||
func IsRPCNodeRequestTopic(topic string) bool {
|
||||
return isNodeTopic(topic, topicRPCNodePrefix, topicRPCReqSuffix)
|
||||
}
|
||||
|
||||
// IsRPCResponseTopic 判断 topic 是否为节点 RPC 响应 topic。
|
||||
func IsRPCResponseTopic(topic string) bool {
|
||||
return isNodeTopic(topic, topicRPCRespPrefix, topicRPCRespSuffix)
|
||||
}
|
||||
|
||||
// ServiceScopedTopic 构造服务作用域 topic。
|
||||
func ServiceScopedTopic(service string, groupID string, shardID string, name string) string {
|
||||
return scopedTopic("service", service, groupID, shardID, name)
|
||||
}
|
||||
|
||||
// ZoneScopedTopic 构造区服作用域 topic。
|
||||
func ZoneScopedTopic(zoneID string, name string) string {
|
||||
return scopedTopic("zone", zoneID, "", "", name)
|
||||
}
|
||||
|
||||
// ShardScopedTopic 构造分片作用域 topic。
|
||||
func ShardScopedTopic(shardID string, name string) string {
|
||||
return scopedTopic("shard", shardID, "", "", name)
|
||||
}
|
||||
|
||||
// ParseScopedTopic 解析服务、区服或分片作用域 topic。
|
||||
func ParseScopedTopic(topic string) (ScopedTopic, bool) {
|
||||
parts := strings.Split(strings.TrimSpace(topic), ".")
|
||||
if len(parts) < 2 || hasEmptyPart(parts) {
|
||||
return ScopedTopic{}, false
|
||||
}
|
||||
switch TopicScope(parts[0]) {
|
||||
case TopicScopeService:
|
||||
return parseScopedTopic(parts)
|
||||
case TopicScopeZone:
|
||||
return parseOneScopedTopic(TopicScopeZone, parts)
|
||||
case TopicScopeShard:
|
||||
return parseOneScopedTopic(TopicScopeShard, parts)
|
||||
default:
|
||||
return ScopedTopic{}, false
|
||||
}
|
||||
}
|
||||
|
||||
func logicNodeTopic(nodeID, suffix string) string {
|
||||
return nodeTopic(topicLogicPrefix, nodeID, suffix)
|
||||
}
|
||||
|
||||
func nodeTopic(prefix, nodeID, suffix string) string {
|
||||
nodeID = strings.TrimSpace(nodeID)
|
||||
if nodeID == "" {
|
||||
return ""
|
||||
}
|
||||
return prefix + nodeID + suffix
|
||||
}
|
||||
|
||||
func isLogicNodeTopic(topic, suffix string) bool {
|
||||
return isNodeTopic(topic, topicLogicPrefix, suffix)
|
||||
}
|
||||
|
||||
func isNodeTopic(topic, prefix, suffix string) bool {
|
||||
if prefix == "" || suffix == "" {
|
||||
return false
|
||||
}
|
||||
if !strings.HasPrefix(topic, prefix) || !strings.HasSuffix(topic, suffix) {
|
||||
return false
|
||||
}
|
||||
nodeID := strings.TrimSuffix(strings.TrimPrefix(topic, prefix), suffix)
|
||||
return strings.TrimSpace(nodeID) != ""
|
||||
}
|
||||
|
||||
func scopedTopic(scope string, first string, groupID string, shardID string, name string) string {
|
||||
parts := []string{strings.TrimSpace(scope), strings.TrimSpace(first)}
|
||||
if groupID = strings.TrimSpace(groupID); groupID != "" {
|
||||
parts = append(parts, "gid", groupID)
|
||||
}
|
||||
if shardID = strings.TrimSpace(shardID); shardID != "" {
|
||||
parts = append(parts, "sid", shardID)
|
||||
}
|
||||
if name = strings.TrimSpace(name); name != "" {
|
||||
parts = append(parts, name)
|
||||
}
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
func parseScopedTopic(parts []string) (ScopedTopic, bool) {
|
||||
if len(parts) < 2 {
|
||||
return ScopedTopic{}, false
|
||||
}
|
||||
out := ScopedTopic{Scope: TopicScopeService, Service: parts[1]}
|
||||
idx := 2
|
||||
for idx < len(parts) {
|
||||
switch parts[idx] {
|
||||
case "gid":
|
||||
if idx+1 >= len(parts) || out.GroupID != "" {
|
||||
return ScopedTopic{}, false
|
||||
}
|
||||
out.GroupID = parts[idx+1]
|
||||
idx += 2
|
||||
case "sid":
|
||||
if idx+1 >= len(parts) || out.ShardID != "" {
|
||||
return ScopedTopic{}, false
|
||||
}
|
||||
out.ShardID = parts[idx+1]
|
||||
idx += 2
|
||||
default:
|
||||
out.Name = strings.Join(parts[idx:], ".")
|
||||
return out, true
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
func parseOneScopedTopic(scope TopicScope, parts []string) (ScopedTopic, bool) {
|
||||
if len(parts) < 2 {
|
||||
return ScopedTopic{}, false
|
||||
}
|
||||
out := ScopedTopic{Scope: scope}
|
||||
if scope == TopicScopeZone {
|
||||
out.ZoneID = parts[1]
|
||||
} else {
|
||||
out.ShardID = parts[1]
|
||||
}
|
||||
if len(parts) > 2 {
|
||||
out.Name = strings.Join(parts[2:], ".")
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
func hasEmptyPart(parts []string) bool {
|
||||
for _, part := range parts {
|
||||
if strings.TrimSpace(part) == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in new issue
Block a user