DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

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

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

No files matched your search

+391
View File
@@ -0,0 +1,391 @@
package adapters
import (
"fmt"
"sort"
"strings"
"longheng.io/server/internal/platform/config"
)
type Status string
const (
StatusStable Status = "stable"
StatusExperimental Status = "experimental"
StatusPlanned Status = "planned"
StatusSkeleton Status = "skeleton"
StatusInterfaceOnly Status = "interface_only"
)
type Matrix struct {
SchemaVersion int `json:"schema_version"`
Selected Selected `json:"selected"`
Capabilities []Capability `json:"capabilities"`
Gaps []Gap `json:"gaps,omitempty"`
}
type Selected struct {
Registry string `json:"registry"`
Config string `json:"config"`
EventBus string `json:"event_bus"`
Cache string `json:"cache"`
Lock string `json:"lock"`
Transports []string `json:"transports"`
}
type Capability struct {
Area string `json:"area"`
Backend string `json:"backend"`
Status Status `json:"status"`
ConfigKind string `json:"config_kind,omitempty"`
Features []string `json:"features"`
Smoke string `json:"smoke,omitempty"`
Notes []string `json:"notes,omitempty"`
}
type Gap struct {
Area string `json:"area"`
Backend string `json:"backend"`
Reason string `json:"reason"`
}
func MatrixFromConfig(cfg config.ServiceConfig) Matrix {
matrix := Matrix{
SchemaVersion: 1,
Selected: Selected{
Registry: normalizeKind(cfg.Registry.Kind, "memory"),
Config: "file",
EventBus: normalizeKind(cfg.Bus.Kind, "memory"),
Cache: normalizeKind(cfg.Cache.Kind, "memory"),
Lock: normalizeKind(cfg.Lock.Kind, "memory"),
Transports: selectedTransports(cfg.Gateway),
},
Capabilities: defaultCapabilities(),
}
matrix.Gaps = selectedGaps(matrix.Selected, matrix.Capabilities)
return matrix
}
func ValidateSelected(matrix Matrix) error {
var missing []string
available := make(map[string]map[string]Status)
for _, capability := range matrix.Capabilities {
area := strings.TrimSpace(capability.Area)
backend := strings.TrimSpace(capability.Backend)
if area == "" || backend == "" {
continue
}
if _, ok := available[area]; !ok {
available[area] = make(map[string]Status)
}
available[area][backend] = capability.Status
}
for _, selected := range []struct {
area string
backend string
}{
{area: "registry", backend: matrix.Selected.Registry},
{area: "config", backend: matrix.Selected.Config},
{area: "eventbus", backend: matrix.Selected.EventBus},
{area: "cache", backend: matrix.Selected.Cache},
{area: "lock", backend: matrix.Selected.Lock},
} {
missing = append(missing, validSelectedBackend(available, selected.area, normalizeKind(selected.backend, "memory"))...)
}
transports := matrix.Selected.Transports
if len(transports) == 0 {
transports = []string{"tcp"}
}
for _, transport := range transports {
missing = append(missing, validSelectedBackend(available, "transport", normalizeKind(transport, "tcp"))...)
}
if len(missing) > 0 {
return fmt.Errorf("adapter matrix invalid: %s", strings.Join(missing, "; "))
}
return nil
}
func defaultCapabilities() []Capability {
capabilities := []Capability{
{
Area: "registry",
Backend: "memory",
Status: StatusStable,
ConfigKind: "memory",
Features: []string{"register", "deregister", "list", "watch"},
Smoke: "go test ./internal/platform/registry",
},
{
Area: "registry",
Backend: "redis",
Status: StatusStable,
ConfigKind: "redis",
Features: []string{"register", "deregister", "list", "lease_ttl"},
Smoke: "LONGHENG_REDIS_INTEGRATION=1 go test ./internal/platform/registry",
},
{
Area: "registry",
Backend: "etcd",
Status: StatusExperimental,
ConfigKind: "etcd",
Features: []string{"register", "deregister", "list", "watch", "lease_keepalive"},
Smoke: "external etcd integration smoke",
},
{
Area: "config",
Backend: "file",
Status: StatusStable,
ConfigKind: "file",
Features: []string{"toml", "validate", "reload", "redaction"},
Smoke: "go test ./internal/platform/config ./internal/platform/reload",
},
{
Area: "config",
Backend: "etcd",
Status: StatusSkeleton,
ConfigKind: "etcd",
Features: []string{"watch", "versioned_snapshot"},
Notes: []string{"interface reserved; provider not wired"},
},
{
Area: "config",
Backend: "consul",
Status: StatusSkeleton,
ConfigKind: "consul",
Features: []string{"watch", "kv_prefix"},
Notes: []string{"interface reserved; provider not wired"},
},
{
Area: "config",
Backend: "nacos",
Status: StatusSkeleton,
ConfigKind: "nacos",
Features: []string{"watch", "namespace_group_dataid"},
Notes: []string{"interface reserved; provider not wired"},
},
{
Area: "eventbus",
Backend: "memory",
Status: StatusStable,
ConfigKind: "memory",
Features: []string{"publish", "subscribe", "panic_recovery"},
Smoke: "go test ./internal/platform/bus",
},
{
Area: "eventbus",
Backend: "redis",
Status: StatusStable,
ConfigKind: "redis",
Features: []string{"pubsub", "typed_payload", "reconnect"},
Smoke: "LONGHENG_REDIS_INTEGRATION=1 go test ./internal/platform/bus",
},
{
Area: "eventbus",
Backend: "nats",
Status: StatusStable,
ConfigKind: "nats",
Features: []string{"pubsub", "typed_payload", "drain"},
Smoke: "external nats integration smoke",
},
{
Area: "external_api",
Backend: "http_webhook",
Status: StatusExperimental,
ConfigKind: "external.webhook",
Features: []string{"http", "timeout", "custom_headers"},
Smoke: "go test ./internal/platform/adapters/external",
Notes: []string{"vendor-specific schemas live in project adapters"},
},
{
Area: "external_api",
Backend: "hmac_webhook",
Status: StatusExperimental,
ConfigKind: "external.webhook.hmac",
Features: []string{"http", "timeout", "custom_headers", "hmac_sha256"},
Smoke: "go test ./internal/platform/adapters/external",
Notes: []string{"critical events must use EventLog or Outbox before calling external APIs"},
},
{
Area: "cache",
Backend: "memory",
Status: StatusStable,
ConfigKind: "memory",
Features: []string{"get", "set", "delete", "ttl"},
Smoke: "go test ./internal/platform/cache",
},
{
Area: "cache",
Backend: "redis",
Status: StatusStable,
ConfigKind: "redis",
Features: []string{"get", "set", "delete", "ttl", "pool"},
Smoke: "LONGHENG_REDIS_INTEGRATION=1 go test ./internal/platform/cache",
},
{
Area: "lock",
Backend: "memory",
Status: StatusStable,
ConfigKind: "memory",
Features: []string{"token", "ttl", "safe_release"},
Smoke: "go test ./internal/platform/lock",
},
{
Area: "lock",
Backend: "redis",
Status: StatusStable,
ConfigKind: "redis",
Features: []string{"set_nx_px", "lua_safe_release", "owner_token"},
Smoke: "LONGHENG_REDIS_INTEGRATION=1 go test ./internal/platform/lock",
},
{
Area: "orchestrator",
Backend: "kubernetes",
Status: StatusExperimental,
ConfigKind: "k8s",
Features: []string{"list_pods", "watch_pods", "deployment_scale"},
Smoke: "go test ./internal/platform/adapters/k8s",
Notes: []string{"standard-library HTTP adapter; no client-go dependency in core"},
},
{
Area: "transport",
Backend: "tcp",
Status: StatusStable,
ConfigKind: "gateway.tcp_listen",
Features: []string{"packet", "frame_v1", "session"},
Smoke: "go run ./cmd/gwbot -transport tcp",
},
{
Area: "transport",
Backend: "websocket",
Status: StatusStable,
ConfigKind: "gateway.websocket_enabled",
Features: []string{"frame_v1", "browser_client", "session"},
Smoke: "go run ./cmd/gwbot -transport websocket",
},
{
Area: "transport",
Backend: "kcp",
Status: StatusStable,
ConfigKind: "gateway.kcp_enabled",
Features: []string{"frame_v1", "weaknet", "fec_optional"},
Smoke: "python3 scripts/run_gateway_weaknet.py",
},
{
Area: "transport",
Backend: "quic",
Status: StatusStable,
ConfigKind: "gateway.quic_enabled",
Features: []string{"frame_v1", "tls", "weaknet"},
Smoke: "python3 scripts/run_gateway_weaknet.py",
},
}
for i := range capabilities {
capabilities[i].Features = cleanSorted(capabilities[i].Features)
capabilities[i].Notes = cleanSorted(capabilities[i].Notes)
}
sort.Slice(capabilities, func(i, j int) bool {
if capabilities[i].Area == capabilities[j].Area {
return capabilities[i].Backend < capabilities[j].Backend
}
return capabilities[i].Area < capabilities[j].Area
})
return capabilities
}
func validSelectedBackend(available map[string]map[string]Status, area, backend string) []string {
status, ok := available[area][backend]
if !ok {
return []string{fmt.Sprintf("%s/%s unsupported", area, backend)}
}
switch status {
case StatusPlanned:
return []string{fmt.Sprintf("%s/%s planned only", area, backend)}
case StatusSkeleton, StatusInterfaceOnly:
return []string{fmt.Sprintf("%s/%s %s only", area, backend, status)}
}
return nil
}
func selectedGaps(selected Selected, capabilities []Capability) []Gap {
var gaps []Gap
statusByAreaBackend := make(map[string]Status, len(capabilities))
for _, capability := range capabilities {
statusByAreaBackend[capability.Area+"/"+capability.Backend] = capability.Status
}
for _, item := range []struct {
area string
backend string
}{
{area: "registry", backend: selected.Registry},
{area: "config", backend: selected.Config},
{area: "eventbus", backend: selected.EventBus},
{area: "cache", backend: selected.Cache},
{area: "lock", backend: selected.Lock},
} {
gaps = appendSelectedGap(gaps, statusByAreaBackend, item.area, normalizeKind(item.backend, "memory"))
}
transports := selected.Transports
if len(transports) == 0 {
transports = []string{"tcp"}
}
for _, transport := range transports {
gaps = appendSelectedGap(gaps, statusByAreaBackend, "transport", normalizeKind(transport, "tcp"))
}
return gaps
}
func appendSelectedGap(gaps []Gap, statusByAreaBackend map[string]Status, area, backend string) []Gap {
key := area + "/" + backend
status, ok := statusByAreaBackend[key]
if !ok {
return append(gaps, Gap{Area: area, Backend: backend, Reason: "unsupported backend"})
}
switch status {
case StatusPlanned:
return append(gaps, Gap{Area: area, Backend: backend, Reason: "planned backend not wired"})
case StatusSkeleton, StatusInterfaceOnly:
return append(gaps, Gap{Area: area, Backend: backend, Reason: string(status) + " backend not wired"})
}
return gaps
}
func selectedTransports(gateway config.GatewaySection) []string {
transports := []string{"tcp"}
if gateway.WebSocketEnabled {
transports = append(transports, "websocket")
}
if gateway.KCPEnabled {
transports = append(transports, "kcp")
}
if gateway.QUICEnabled {
transports = append(transports, "quic")
}
return cleanSorted(transports)
}
func normalizeKind(kind, fallback string) string {
kind = strings.ToLower(strings.TrimSpace(kind))
if kind == "" {
return fallback
}
return kind
}
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
}