Files
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

334 lines
11 KiB
Go

package framework
import (
"fmt"
"sort"
"strings"
)
// Role 表示框架边界中的服务角色。
type Role string
const (
FrontendRole Role = "frontend"
BackendRole Role = "backend"
MeshRole Role = "mesh"
)
// Contract 是可由工具校验的实时服务边界规范。
type Contract struct {
SchemaVersion int `json:"schema_version"`
Frontend Boundary `json:"frontend"`
Backends []Boundary `json:"backends"`
RemoteServices []RemoteService `json:"remote_services"`
GroupServices []GroupService `json:"group_services"`
Session SessionContract `json:"session"`
Invariants []Invariant `json:"invariants"`
}
// Boundary 描述单个服务边界、职责,以及它提供或消费的规范。
type Boundary struct {
Service string `json:"service"`
Role Role `json:"role"`
Responsibilities []string `json:"responsibilities"`
Provides []string `json:"provides"`
Consumes []string `json:"consumes"`
}
// RemoteService 描述跨服务规范及其背后的传输语义。
type RemoteService struct {
Name string `json:"name"`
Transport string `json:"transport"`
Provider string `json:"provider"`
Consumers []string `json:"consumers"`
Guarantees []string `json:"guarantees"`
}
// GroupService 描述多个后端服务共同使用的运行时分组规范。
type GroupService struct {
Name string `json:"name"`
Runtime string `json:"runtime"`
Backends []string `json:"backends"`
Semantics []string `json:"semantics"`
}
// SessionContract 定义前端会话状态的归属、定位和重放规则。
type SessionContract struct {
Owner string `json:"owner"`
Binding string `json:"binding"`
Locator string `json:"locator"`
Events []string `json:"events"`
ClosePolicy string `json:"close_policy"`
ReplayPolicy string `json:"replay_policy"`
}
// Invariant 记录服务和运行时演进中必须保持成立的规则。
type Invariant struct {
Name string `json:"name"`
Description string `json:"description"`
}
// DefaultRealtimeContract 返回 gateway/logic/playerd/scene/social 的默认实时边界规范。
func DefaultRealtimeContract() Contract {
contract := Contract{
SchemaVersion: 1,
Frontend: Boundary{
Service: "gateway",
Role: FrontendRole,
Responsibilities: []string{
"transport_accept",
"login_handshake",
"session_binding",
"backend_route_forward",
"server_push",
},
Provides: []string{
"frontend_session",
"gateway_push",
"transport_snapshot",
"presence_publish",
},
Consumes: []string{
"logic_route_manifest",
"logic_player_response",
"registry_backend_nodes",
},
},
Backends: []Boundary{
{
Service: "logic",
Role: BackendRole,
Responsibilities: []string{
"stateful_player_handlers",
"idempotent_request_guard",
"profile_runtime",
"session_event_projection",
},
Provides: []string{
"route_manifest",
"logic_player_response",
"remote_rpc_handlers",
},
Consumes: []string{
"gateway_client_packet",
"session_connected",
"session_disconnected",
},
},
{
Service: "playerd",
Role: BackendRole,
Responsibilities: []string{
"profile_flush",
"player_loop_worker",
"storage_deadletter_requeue",
},
Provides: []string{"player_storage_admin"},
Consumes: []string{"profile_dirty_event"},
},
{
Service: "scene",
Role: MeshRole,
Responsibilities: []string{"room_allocation", "scene_event_projection"},
Provides: []string{"room_allocated"},
Consumes: []string{"room_allocate_request"},
},
{
Service: "social",
Role: MeshRole,
Responsibilities: []string{"chat_broadcast", "social_event_projection"},
Provides: []string{"chat_broadcast"},
Consumes: []string{"chat_send_request"},
},
},
RemoteServices: []RemoteService{
{
Name: "reliable_rpc",
Transport: "event_bus",
Provider: "internal/platform/rpc.Endpoint",
Consumers: []string{
"gateway",
"logic",
"scene",
"social",
"playerd",
},
Guarantees: []string{
"request_id_correlation",
"timeout_bounded_pending",
"discovery_resolved_target",
"trace_span_capture",
},
},
},
GroupServices: []GroupService{
{
Name: "runtime_group",
Runtime: "internal/platform/group.Runtime",
Backends: []string{"memory", "redis"},
Semantics: []string{
"create",
"ttl_renew",
"membership",
"broadcast",
"multicast",
},
},
},
Session: SessionContract{
Owner: "gateway",
Binding: "one_active_session_per_account",
Locator: "presence_tracker + registry node metadata",
Events: []string{"OnBind", "AfterBind", "OnClose", "OnKick", "OnReconnect"},
ClosePolicy: "new same-account bind closes stale frontend session",
ReplayPolicy: "idempotent backend response cache replays duplicate stateful packets",
},
Invariants: []Invariant{
{Name: "frontend owns connection state", Description: "Only gateway owns sockets and session connection objects."},
{Name: "backend owns gameplay state", Description: "Logic/playerd/scene/social own state mutations and never write client sockets directly."},
{Name: "push crosses explicit contract", Description: "Backend push must use GatewayPush or RPC/event bus contracts."},
{Name: "stateful routes are idempotent", Description: "Stateful authorized player routes must declare route policy and use idempotency guard."},
},
}
return Normalize(contract)
}
func Normalize(contract Contract) Contract {
normalizeBoundary(&contract.Frontend)
for i := range contract.Backends {
normalizeBoundary(&contract.Backends[i])
}
sort.Slice(contract.Backends, func(i, j int) bool {
if contract.Backends[i].Role == contract.Backends[j].Role {
return contract.Backends[i].Service < contract.Backends[j].Service
}
return contract.Backends[i].Role < contract.Backends[j].Role
})
for i := range contract.RemoteServices {
contract.RemoteServices[i].Name = strings.TrimSpace(contract.RemoteServices[i].Name)
contract.RemoteServices[i].Transport = strings.TrimSpace(contract.RemoteServices[i].Transport)
contract.RemoteServices[i].Provider = strings.TrimSpace(contract.RemoteServices[i].Provider)
contract.RemoteServices[i].Consumers = cleanSorted(contract.RemoteServices[i].Consumers)
contract.RemoteServices[i].Guarantees = cleanSorted(contract.RemoteServices[i].Guarantees)
}
sort.Slice(contract.RemoteServices, func(i, j int) bool {
return contract.RemoteServices[i].Name < contract.RemoteServices[j].Name
})
for i := range contract.GroupServices {
contract.GroupServices[i].Name = strings.TrimSpace(contract.GroupServices[i].Name)
contract.GroupServices[i].Runtime = strings.TrimSpace(contract.GroupServices[i].Runtime)
contract.GroupServices[i].Backends = cleanSorted(contract.GroupServices[i].Backends)
contract.GroupServices[i].Semantics = cleanSorted(contract.GroupServices[i].Semantics)
}
sort.Slice(contract.GroupServices, func(i, j int) bool {
return contract.GroupServices[i].Name < contract.GroupServices[j].Name
})
contract.Session.Events = cleanSorted(contract.Session.Events)
return contract
}
func Validate(contract Contract) error {
contract = Normalize(contract)
var missing []string
if contract.SchemaVersion <= 0 {
missing = append(missing, "schema_version")
}
if err := validateBoundary("frontend", contract.Frontend, FrontendRole); err != nil {
missing = append(missing, err.Error())
}
services := map[string]struct{}{}
if contract.Frontend.Service != "" {
services[contract.Frontend.Service] = struct{}{}
}
if len(contract.Backends) == 0 {
missing = append(missing, "backends")
}
for _, backend := range contract.Backends {
if _, ok := services[backend.Service]; ok {
missing = append(missing, fmt.Sprintf("duplicate service %s", backend.Service))
}
if backend.Service != "" {
services[backend.Service] = struct{}{}
}
if backend.Role != BackendRole && backend.Role != MeshRole {
missing = append(missing, fmt.Sprintf("backend %s role must be backend or mesh", backend.Service))
}
if err := validateBoundary("backend", backend, ""); err != nil {
missing = append(missing, err.Error())
}
}
if len(contract.RemoteServices) == 0 {
missing = append(missing, "remote_services")
}
for _, remote := range contract.RemoteServices {
if remote.Name == "" || remote.Transport == "" || remote.Provider == "" {
missing = append(missing, "remote_service name/transport/provider")
}
for _, consumer := range remote.Consumers {
if _, ok := services[consumer]; !ok {
missing = append(missing, fmt.Sprintf("remote_service %s unknown consumer %s", remote.Name, consumer))
}
}
}
if len(contract.GroupServices) == 0 {
missing = append(missing, "group_services")
}
for _, group := range contract.GroupServices {
if group.Name == "" || group.Runtime == "" || len(group.Backends) == 0 {
missing = append(missing, "group_service name/runtime/backends")
}
}
if contract.Session.Owner == "" || contract.Session.Binding == "" || contract.Session.Locator == "" {
missing = append(missing, "session owner/binding/locator")
}
if len(missing) > 0 {
return fmt.Errorf("framework contract incomplete: %s", strings.Join(missing, "; "))
}
return nil
}
func validateBoundary(label string, boundary Boundary, role Role) error {
if boundary.Service == "" {
return fmt.Errorf("%s service", label)
}
if role != "" && boundary.Role != role {
return fmt.Errorf("%s %s role must be %s", label, boundary.Service, role)
}
if boundary.Role == "" {
return fmt.Errorf("%s %s role", label, boundary.Service)
}
if len(boundary.Responsibilities) == 0 {
return fmt.Errorf("%s %s responsibilities", label, boundary.Service)
}
if len(boundary.Provides) == 0 {
return fmt.Errorf("%s %s provides", label, boundary.Service)
}
return nil
}
func normalizeBoundary(boundary *Boundary) {
boundary.Service = strings.TrimSpace(boundary.Service)
boundary.Role = Role(strings.TrimSpace(string(boundary.Role)))
boundary.Responsibilities = cleanSorted(boundary.Responsibilities)
boundary.Provides = cleanSorted(boundary.Provides)
boundary.Consumes = cleanSorted(boundary.Consumes)
}
func cleanSorted(values []string) []string {
out := make([]string, 0, len(values))
seen := make(map[string]struct{}, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
out = append(out, value)
}
sort.Strings(out)
return out
}