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,381 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrClosed = errors.New("registry is closed")
|
||||
|
||||
type Node struct {
|
||||
Service string `json:"service"`
|
||||
NodeID string `json:"node_id"`
|
||||
GID int64 `json:"gid,omitempty"`
|
||||
VirtualGID int64 `json:"virtual_gid,omitempty"`
|
||||
ShardID int64 `json:"shard_id,omitempty"`
|
||||
PublicAddr string `json:"public_addr"`
|
||||
PrivateAddr string `json:"private_addr"`
|
||||
State string `json:"state,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
Routes []Route `json:"routes,omitempty"`
|
||||
Events []Event `json:"events,omitempty"`
|
||||
Services []string `json:"services,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
GMVisible bool `json:"gm_visible,omitempty"`
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
RegisteredAt time.Time `json:"registered_at"`
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
ID string `json:"id"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Internal bool `json:"internal,omitempty"`
|
||||
Stateful bool `json:"stateful,omitempty"`
|
||||
Authorized bool `json:"authorized,omitempty"`
|
||||
Idempotent bool `json:"idempotent,omitempty"`
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Registry interface {
|
||||
Register(context.Context, Node) error
|
||||
Deregister(context.Context, string, string) error
|
||||
List(context.Context, string) ([]Node, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type WatchEventType string
|
||||
|
||||
const (
|
||||
EventUpsert WatchEventType = "upsert"
|
||||
EventDelete WatchEventType = "delete"
|
||||
)
|
||||
|
||||
type WatchEvent struct {
|
||||
Type WatchEventType `json:"type"`
|
||||
Node Node `json:"node"`
|
||||
}
|
||||
|
||||
type Watcher interface {
|
||||
Watch(context.Context, string) (<-chan WatchEvent, error)
|
||||
}
|
||||
|
||||
type Memory struct {
|
||||
mu sync.RWMutex
|
||||
closed bool
|
||||
closedCh chan struct{}
|
||||
nodes map[string]map[string]Node
|
||||
watchers map[string]map[chan WatchEvent]struct{}
|
||||
}
|
||||
|
||||
func NewMemory() *Memory {
|
||||
return &Memory{
|
||||
closedCh: make(chan struct{}),
|
||||
nodes: make(map[string]map[string]Node),
|
||||
watchers: make(map[string]map[chan WatchEvent]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func contextOrBackground(ctx context.Context) context.Context {
|
||||
if ctx == nil {
|
||||
return context.Background()
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (r *Memory) Register(ctx context.Context, node Node) error {
|
||||
ctx = contextOrBackground(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
node.Service = strings.TrimSpace(node.Service)
|
||||
node.NodeID = strings.TrimSpace(node.NodeID)
|
||||
node.Tags = normalizeTags(node.Tags)
|
||||
if node.Service == "" || node.NodeID == "" {
|
||||
return fmt.Errorf("registry node service and node_id are required")
|
||||
}
|
||||
if r == nil {
|
||||
return ErrClosed
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.ensureReadyLocked()
|
||||
if r.closed {
|
||||
r.mu.Unlock()
|
||||
return ErrClosed
|
||||
}
|
||||
if _, ok := r.nodes[node.Service]; !ok {
|
||||
r.nodes[node.Service] = make(map[string]Node)
|
||||
}
|
||||
node.RegisteredAt = time.Now().UTC()
|
||||
node = cloneNode(node)
|
||||
r.nodes[node.Service][node.NodeID] = node
|
||||
r.publishLocked(ctx, node.Service, WatchEvent{Type: EventUpsert, Node: node})
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Memory) Check(ctx context.Context) error {
|
||||
ctx = contextOrBackground(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if r == nil {
|
||||
return ErrClosed
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if r.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Memory) Deregister(ctx context.Context, service, nodeID string) error {
|
||||
ctx = contextOrBackground(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
service = strings.TrimSpace(service)
|
||||
nodeID = strings.TrimSpace(nodeID)
|
||||
if service == "" || nodeID == "" {
|
||||
return nil
|
||||
}
|
||||
if r == nil {
|
||||
return ErrClosed
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.ensureReadyLocked()
|
||||
if r.closed {
|
||||
r.mu.Unlock()
|
||||
return ErrClosed
|
||||
}
|
||||
node := Node{Service: service, NodeID: nodeID}
|
||||
if serviceNodes, ok := r.nodes[service]; ok {
|
||||
if existing, exists := serviceNodes[nodeID]; exists {
|
||||
node = cloneNode(existing)
|
||||
}
|
||||
delete(serviceNodes, nodeID)
|
||||
if len(serviceNodes) == 0 {
|
||||
delete(r.nodes, service)
|
||||
}
|
||||
}
|
||||
r.publishLocked(ctx, service, WatchEvent{Type: EventDelete, Node: node})
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Memory) List(ctx context.Context, service string) ([]Node, error) {
|
||||
ctx = contextOrBackground(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service = strings.TrimSpace(service)
|
||||
if service == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if r == nil {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if r.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
serviceNodes := r.nodes[service]
|
||||
nodes := make([]Node, 0, len(serviceNodes))
|
||||
for _, node := range serviceNodes {
|
||||
nodes = append(nodes, cloneNode(node))
|
||||
}
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
return nodes[i].NodeID < nodes[j].NodeID
|
||||
})
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func ListGMVisible(ctx context.Context, reg Registry, service string) ([]Node, error) {
|
||||
if reg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
nodes, err := reg.List(ctx, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FilterGMVisible(nodes), nil
|
||||
}
|
||||
|
||||
func FilterGMVisible(nodes []Node) []Node {
|
||||
if len(nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]Node, 0, len(nodes))
|
||||
for _, node := range nodes {
|
||||
if !node.GMVisible {
|
||||
continue
|
||||
}
|
||||
out = append(out, cloneNode(node))
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
if out[i].Service != out[j].Service {
|
||||
return out[i].Service < out[j].Service
|
||||
}
|
||||
return out[i].NodeID < out[j].NodeID
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Memory) Close() error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.ensureReadyLocked()
|
||||
if r.closed {
|
||||
r.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
r.closed = true
|
||||
r.nodes = make(map[string]map[string]Node)
|
||||
r.watchers = make(map[string]map[chan WatchEvent]struct{})
|
||||
closedCh := r.closedCh
|
||||
r.mu.Unlock()
|
||||
|
||||
close(closedCh)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Memory) Watch(ctx context.Context, service string) (<-chan WatchEvent, error) {
|
||||
ctx = contextOrBackground(ctx)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service = strings.TrimSpace(service)
|
||||
if service == "" {
|
||||
return nil, fmt.Errorf("registry watch service is required")
|
||||
}
|
||||
if r == nil {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.ensureReadyLocked()
|
||||
if r.closed {
|
||||
r.mu.Unlock()
|
||||
return nil, ErrClosed
|
||||
}
|
||||
initial := make([]Node, 0, len(r.nodes[service]))
|
||||
for _, node := range r.nodes[service] {
|
||||
initial = append(initial, cloneNode(node))
|
||||
}
|
||||
sort.Slice(initial, func(i, j int) bool {
|
||||
return initial[i].NodeID < initial[j].NodeID
|
||||
})
|
||||
|
||||
ch := make(chan WatchEvent, len(initial)+16)
|
||||
for _, node := range initial {
|
||||
ch <- WatchEvent{Type: EventUpsert, Node: node}
|
||||
}
|
||||
if _, ok := r.watchers[service]; !ok {
|
||||
r.watchers[service] = make(map[chan WatchEvent]struct{})
|
||||
}
|
||||
r.watchers[service][ch] = struct{}{}
|
||||
closedCh := r.closedCh
|
||||
r.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-closedCh:
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
if serviceWatchers, ok := r.watchers[service]; ok {
|
||||
delete(serviceWatchers, ch)
|
||||
if len(serviceWatchers) == 0 {
|
||||
delete(r.watchers, service)
|
||||
}
|
||||
}
|
||||
// publishLocked 与这里共用 r.mu,保证删除后关闭时没有发送者仍持有 ch。
|
||||
close(ch)
|
||||
r.mu.Unlock()
|
||||
}()
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// ensureReadyLocked 补齐手工构造 Memory 时缺失的内部索引和关闭信号。
|
||||
// 调用方必须已经持有 r.mu 写锁。
|
||||
func (r *Memory) ensureReadyLocked() {
|
||||
if r.closedCh == nil {
|
||||
r.closedCh = make(chan struct{})
|
||||
}
|
||||
if r.nodes == nil {
|
||||
r.nodes = make(map[string]map[string]Node)
|
||||
}
|
||||
if r.watchers == nil {
|
||||
r.watchers = make(map[string]map[chan WatchEvent]struct{})
|
||||
}
|
||||
}
|
||||
|
||||
// publishLocked 只做非阻塞发送,调用方必须持有 r.mu 写锁。
|
||||
// channel 的发送与关闭由同一把锁保护,不能再用 recover 掩盖生命周期竞争。
|
||||
func (r *Memory) publishLocked(ctx context.Context, service string, event WatchEvent) {
|
||||
if ctx != nil && ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
for ch := range r.watchers[service] {
|
||||
next := event
|
||||
next.Node = cloneNode(event.Node)
|
||||
select {
|
||||
case ch <- next:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cloneNode(node Node) Node {
|
||||
node.Meta = cloneMeta(node.Meta)
|
||||
node.Routes = append([]Route(nil), node.Routes...)
|
||||
node.Events = append([]Event(nil), node.Events...)
|
||||
node.Services = append([]string(nil), node.Services...)
|
||||
node.Tags = append([]string(nil), node.Tags...)
|
||||
return node
|
||||
}
|
||||
|
||||
func normalizeTags(tags []string) []string {
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := make(map[string]struct{}, len(tags))
|
||||
out := make([]string, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
tag = strings.TrimSpace(strings.ToLower(tag))
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[tag]; ok {
|
||||
continue
|
||||
}
|
||||
seen[tag] = struct{}{}
|
||||
out = append(out, tag)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneMeta(meta map[string]string) map[string]string {
|
||||
if len(meta) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]string, len(meta))
|
||||
for key, value := range meta {
|
||||
out[key] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user