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

@@ -0,0 +1,205 @@
package hotreload
import (
"context"
"sync"
)
// DrainBarrier 在热切换前停止接收新调用,并等待已进入的调用自然结束。
type DrainBarrier struct {
mu sync.Mutex
initialized bool
accepting bool
active int
drainWaiters []chan struct{}
enterWaiters []chan struct{}
}
// BarrierSnapshot 描述热切换屏障当前的接入、活跃调用和等待队列状态。
type BarrierSnapshot struct {
Accepting bool `json:"accepting"`
Active int `json:"active"`
Waiters int `json:"waiters"`
DrainWaiters int `json:"drain_waiters,omitempty"`
EnterWaiters int `json:"enter_waiters,omitempty"`
}
// NewDrainBarrier 创建默认允许进入的热切换屏障。
func NewDrainBarrier() *DrainBarrier {
return &DrainBarrier{initialized: true, accepting: true}
}
// Enter 在屏障接受新调用时进入活跃区,并返回幂等的离开函数。
func (b *DrainBarrier) Enter(ctx context.Context) (func(), error) {
if b == nil {
return func() {}, nil
}
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return nil, err
}
b.mu.Lock()
b.ensureReadyLocked()
defer b.mu.Unlock()
if !b.accepting {
return nil, ErrBarrierDraining
}
b.active++
once := sync.Once{}
return func() {
once.Do(func() {
b.mu.Lock()
if b.active > 0 {
b.active--
}
b.notifyDrainLocked()
b.mu.Unlock()
})
}, nil
}
// EnterWhenReady 会等待屏障恢复接受新调用后进入活跃区。
func (b *DrainBarrier) EnterWhenReady(ctx context.Context) (func(), error) {
if b == nil {
return func() {}, nil
}
if ctx == nil {
ctx = context.Background()
}
for {
if err := ctx.Err(); err != nil {
return nil, err
}
b.mu.Lock()
b.ensureReadyLocked()
if b.accepting {
b.active++
b.mu.Unlock()
once := sync.Once{}
return func() {
once.Do(func() {
b.mu.Lock()
if b.active > 0 {
b.active--
}
b.notifyDrainLocked()
b.mu.Unlock()
})
}, nil
}
waiter := make(chan struct{})
b.enterWaiters = append(b.enterWaiters, waiter)
b.mu.Unlock()
select {
case <-waiter:
case <-ctx.Done():
b.mu.Lock()
b.enterWaiters = removeWaiter(b.enterWaiters, waiter)
b.mu.Unlock()
return nil, ctx.Err()
}
}
}
// Drain 停止接收新调用,并等待已进入的调用全部离开。
func (b *DrainBarrier) Drain(ctx context.Context) error {
if b == nil {
return nil
}
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return err
}
b.mu.Lock()
b.ensureReadyLocked()
b.accepting = false
for b.active > 0 {
waiter := make(chan struct{})
b.drainWaiters = append(b.drainWaiters, waiter)
b.mu.Unlock()
select {
case <-waiter:
case <-ctx.Done():
b.mu.Lock()
b.drainWaiters = removeWaiter(b.drainWaiters, waiter)
b.mu.Unlock()
return ctx.Err()
}
b.mu.Lock()
}
b.mu.Unlock()
return nil
}
// Resume 重新允许调用进入,并唤醒等待恢复的调用方。
func (b *DrainBarrier) Resume() {
if b == nil {
return
}
b.mu.Lock()
b.ensureReadyLocked()
b.accepting = true
b.notifyEnterLocked()
b.mu.Unlock()
}
// Snapshot 返回屏障状态快照,便于管理端观测热切换过程。
func (b *DrainBarrier) Snapshot() BarrierSnapshot {
if b == nil {
return BarrierSnapshot{Accepting: true}
}
b.mu.Lock()
b.ensureReadyLocked()
snapshot := BarrierSnapshot{
Accepting: b.accepting,
Active: b.active,
DrainWaiters: len(b.drainWaiters),
EnterWaiters: len(b.enterWaiters),
}
snapshot.Waiters = snapshot.DrainWaiters + snapshot.EnterWaiters
b.mu.Unlock()
return snapshot
}
func (b *DrainBarrier) ensureReadyLocked() {
if b.initialized {
return
}
b.initialized = true
b.accepting = true
}
func (b *DrainBarrier) notifyDrainLocked() {
if b.active > 0 {
return
}
waiters := b.drainWaiters
b.drainWaiters = nil
for _, waiter := range waiters {
close(waiter)
}
}
func (b *DrainBarrier) notifyEnterLocked() {
waiters := b.enterWaiters
b.enterWaiters = nil
for _, waiter := range waiters {
close(waiter)
}
}
func removeWaiter(waiters []chan struct{}, target chan struct{}) []chan struct{} {
for i, waiter := range waiters {
if waiter != target {
continue
}
copy(waiters[i:], waiters[i+1:])
waiters[len(waiters)-1] = nil
return waiters[:len(waiters)-1]
}
return waiters
}
@@ -0,0 +1,288 @@
package hotreload
import (
"context"
"errors"
"sort"
"sync"
"time"
)
// Registry 保存 route 到 handler 版本的映射,并负责原子切换当前版本。
type Registry struct {
mu sync.RWMutex
routes map[string]*routeSlot
now func() time.Time
}
type routeSlot struct {
mu sync.RWMutex
activateMu sync.Mutex
route string
modules map[string]Module
active string
swaps int64
updatedAt time.Time
}
// NewRegistry 创建热重载版本注册表。
func NewRegistry(options Options) *Registry {
registry := &Registry{now: options.Now}
_ = registry.ensureReady()
return registry
}
// Register 注册一个 route 的可切换实现版本。
func (r *Registry) Register(module Module) error {
module, err := normalizeModule(module)
if err != nil {
return err
}
if err := r.ensureReady(); err != nil {
return err
}
r.mu.Lock()
slot := r.routes[module.Route]
if slot == nil {
slot = &routeSlot{
route: module.Route,
modules: make(map[string]Module),
}
r.routes[module.Route] = slot
}
r.mu.Unlock()
slot.mu.Lock()
if _, ok := slot.modules[module.Version]; ok {
slot.mu.Unlock()
return ErrVersionExists
}
slot.modules[module.Version] = module
if slot.active == "" {
slot.active = module.Version
slot.updatedAt = r.now()
}
slot.mu.Unlock()
return nil
}
// Activate 将指定 route 原子切换到目标版本。
func (r *Registry) Activate(ctx context.Context, route, version string) (Result, error) {
route, version, err := normRouteVersion(route, version)
if err != nil {
return Result{}, err
}
if err := r.ensureReady(); err != nil {
return Result{}, err
}
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return Result{}, err
}
slot := r.routeSlot(route)
if slot == nil {
return Result{}, ErrRouteNotFound
}
var previous Module
var hasPrevious bool
unlock, err := slot.lockActivate(ctx)
if err != nil {
return Result{}, err
}
defer unlock()
if err := ctx.Err(); err != nil {
return Result{}, err
}
slot.mu.RLock()
module, ok := slot.modules[version]
if !ok {
slot.mu.RUnlock()
return Result{}, ErrVersionNotFound
}
if slot.active == version {
result := Result{Route: route, PreviousVersion: version, ActiveVersion: version, ActivatedAt: slot.updatedAt}
slot.mu.RUnlock()
return result, nil
}
if active := slot.active; active != "" {
previous, hasPrevious = slot.modules[active]
}
slot.mu.RUnlock()
if module.Preflight != nil {
if err := module.Preflight(ctx, module); err != nil {
return Result{}, err
}
}
if err := ctx.Err(); err != nil {
return Result{}, err
}
if module.Activate != nil {
if err := module.Activate(ctx, module); err != nil {
return Result{}, err
}
}
if err := ctx.Err(); err != nil {
return Result{}, err
}
now := r.now()
slot.mu.Lock()
result := Result{
Route: route,
PreviousVersion: slot.active,
ActiveVersion: version,
ActivatedAt: now,
}
slot.active = version
slot.updatedAt = now
slot.swaps++
slot.mu.Unlock()
if hasPrevious && previous.Version != version && previous.Deactivate != nil {
if err := previous.Deactivate(ctx, previous); err != nil {
result.DeactivateError = err.Error()
}
}
return result, nil
}
// Rollback 通过重新激活旧版本完成 route 回滚。
func (r *Registry) Rollback(ctx context.Context, route, version string) (Result, error) {
return r.Activate(ctx, route, version)
}
// Dispatch 将请求分发到 route 当前激活版本的 handler。
func (r *Registry) Dispatch(ctx context.Context, route string, payload any, metadata map[string]any) (Response, error) {
route = normalizeName(route)
if route == "" {
return Response{}, ErrRouteRequired
}
if err := r.ensureReady(); err != nil {
return Response{}, err
}
slot := r.routeSlot(route)
if slot == nil {
return Response{}, ErrRouteNotFound
}
slot.mu.RLock()
module, ok := slot.modules[slot.active]
slot.mu.RUnlock()
if !ok || module.Handler == nil {
return Response{}, ErrHandlerUnavailable
}
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return Response{}, err
}
return module.Handler(ctx, Request{
Route: route,
Version: module.Version,
Payload: payload,
Metadata: copyMetadata(metadata),
})
}
// ActiveVersion 查询 route 当前激活版本。
func (r *Registry) ActiveVersion(route string) (string, bool) {
route = normalizeName(route)
if route == "" {
return "", false
}
if err := r.ensureReady(); err != nil {
return "", false
}
slot := r.routeSlot(route)
if slot == nil {
return "", false
}
slot.mu.RLock()
version := slot.active
slot.mu.RUnlock()
return version, version != ""
}
// Snapshot 返回所有 route 的稳定排序快照。
func (r *Registry) Snapshot() Snapshot {
if err := r.ensureReady(); err != nil {
return Snapshot{}
}
r.mu.RLock()
slots := make([]*routeSlot, 0, len(r.routes))
for _, slot := range r.routes {
slots = append(slots, slot)
}
r.mu.RUnlock()
sort.Slice(slots, func(i, j int) bool { return slots[i].route < slots[j].route })
snapshot := Snapshot{Routes: make([]RouteSnapshot, 0, len(slots))}
for _, slot := range slots {
slot.mu.RLock()
versions := make([]string, 0, len(slot.modules))
for version := range slot.modules {
versions = append(versions, version)
}
sort.Strings(versions)
snapshot.Routes = append(snapshot.Routes, RouteSnapshot{
Route: slot.route,
ActiveVersion: slot.active,
Versions: versions,
Swaps: slot.swaps,
UpdatedAt: slot.updatedAt,
})
slot.mu.RUnlock()
}
return snapshot
}
func (r *Registry) ensureReady() error {
if r == nil {
return ErrRouteNotFound
}
r.mu.Lock()
if r.routes == nil {
r.routes = make(map[string]*routeSlot)
}
if r.now == nil {
r.now = func() time.Time { return time.Now().UTC() }
}
r.mu.Unlock()
return nil
}
func (r *Registry) routeSlot(route string) *routeSlot {
r.mu.RLock()
slot := r.routes[route]
r.mu.RUnlock()
return slot
}
func (s *routeSlot) lockActivate(ctx context.Context) (func(), error) {
if s.activateMu.TryLock() {
return s.activateMu.Unlock, nil
}
timer := time.NewTimer(time.Millisecond)
defer timer.Stop()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-timer.C:
}
if s.activateMu.TryLock() {
return s.activateMu.Unlock, nil
}
timer.Reset(time.Millisecond)
}
}
// IsNotFound 判断错误是否属于热重载 route 或版本不存在。
func IsNotFound(err error) bool {
return errors.Is(err, ErrRouteNotFound) || errors.Is(err, ErrVersionNotFound)
}
+143
View File
@@ -0,0 +1,143 @@
package hotreload
import (
"context"
"errors"
"strings"
"time"
)
var (
// ErrRouteRequired 表示热重载 route 为空。
ErrRouteRequired = errors.New("hot reload route is required")
// ErrVersionRequired 表示热重载版本为空。
ErrVersionRequired = errors.New("hot reload version is required")
// ErrHandlerRequired 表示模块缺少 handler。
ErrHandlerRequired = errors.New("hot reload handler is required")
// ErrRouteNotFound 表示 route 尚未注册。
ErrRouteNotFound = errors.New("hot reload route is not found")
// ErrVersionNotFound 表示目标版本尚未注册。
ErrVersionNotFound = errors.New("hot reload version is not found")
// ErrVersionExists 表示同一 route 下的版本已经注册,不能被静默覆盖。
ErrVersionExists = errors.New("hot reload version already exists")
// ErrHandlerUnavailable 表示当前激活版本没有可用 handler。
ErrHandlerUnavailable = errors.New("hot reload handler is unavailable")
// ErrBarrierDraining 表示热切换屏障正在排空活跃调用。
ErrBarrierDraining = errors.New("hot reload barrier is draining")
// ErrInvalidMetadataName 表示 metadata 字段名非法。
ErrInvalidMetadataName = errors.New("hot reload metadata name is invalid")
)
// Request 是安全热重载 handler 的最小调用上下文。
type Request struct {
Route string `json:"route"`
Version string `json:"version,omitempty"`
Payload any `json:"payload,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
// Response 是安全热重载 handler 的最小返回结构。
type Response struct {
Payload any `json:"payload,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
// Handler 是热重载 route 的业务处理函数。
type Handler func(context.Context, Request) (Response, error)
// Hook 是热重载模块激活、预检和停用时执行的钩子。
type Hook func(context.Context, Module) error
// Module 表示一个可在线切换的 route 实现版本。
type Module struct {
Route string `json:"route"`
Version string `json:"version"`
Metadata map[string]any `json:"metadata,omitempty"`
Handler Handler `json:"-"`
Preflight Hook `json:"-"`
Activate Hook `json:"-"`
Deactivate Hook `json:"-"`
}
// Options 定义热重载 registry 的可注入依赖。
type Options struct {
Now func() time.Time
}
// Result 描述一次版本切换或回滚的结果。
type Result struct {
Route string `json:"route"`
PreviousVersion string `json:"previous_version,omitempty"`
ActiveVersion string `json:"active_version"`
ActivatedAt time.Time `json:"activated_at"`
DeactivateError string `json:"deactivate_error,omitempty"`
}
// RouteSnapshot 描述单个 route 的当前版本和已注册版本。
type RouteSnapshot struct {
Route string `json:"route"`
ActiveVersion string `json:"active_version,omitempty"`
Versions []string `json:"versions,omitempty"`
Swaps int64 `json:"swaps"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
// Snapshot 描述热重载 registry 的整体快照。
type Snapshot struct {
Routes []RouteSnapshot `json:"routes,omitempty"`
}
func normalizeModule(module Module) (Module, error) {
module.Route = normalizeName(module.Route)
if module.Route == "" {
return Module{}, ErrRouteRequired
}
module.Version = normalizeName(module.Version)
if module.Version == "" {
return Module{}, ErrVersionRequired
}
if module.Handler == nil {
return Module{}, ErrHandlerRequired
}
module.Metadata = copyMetadata(module.Metadata)
return module, nil
}
func normRouteVersion(route, version string) (string, string, error) {
route = normalizeName(route)
if route == "" {
return "", "", ErrRouteRequired
}
version = normalizeName(version)
if version == "" {
return "", "", ErrVersionRequired
}
return route, version, nil
}
func normalizeName(value string) string {
return strings.TrimSpace(value)
}
func copyMetadata(in map[string]any) map[string]any {
if len(in) == 0 {
return nil
}
out := make(map[string]any, len(in))
for key, value := range in {
key = strings.TrimSpace(key)
if key == "" {
continue
}
out[key] = value
}
return out
}