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,270 @@
|
||||
package nativepatch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrControlNotDue = errors.New("native patch control intent is not due yet")
|
||||
|
||||
type ControlManagerOptions struct {
|
||||
Target string
|
||||
NodeID string
|
||||
Store ControlStore
|
||||
Engine Engine
|
||||
Policy Policy
|
||||
Now func() time.Time
|
||||
// 控制面上报必须有硬超时,否则补丁已 apply 后可能被不可达后端卡在同步验收路径。
|
||||
ReportTimeout time.Duration
|
||||
}
|
||||
|
||||
type ControlManager struct {
|
||||
target string
|
||||
nodeID string
|
||||
store ControlStore
|
||||
engine Engine
|
||||
policy Policy
|
||||
now func() time.Time
|
||||
reportTimeout time.Duration
|
||||
|
||||
opMu sync.Mutex
|
||||
mu sync.Mutex
|
||||
restoreTimer *time.Timer
|
||||
restoreGeneration int64
|
||||
}
|
||||
|
||||
func NewControlManager(options ControlManagerOptions) (*ControlManager, error) {
|
||||
if options.Target == "" {
|
||||
return nil, fmt.Errorf("native patch control target is required")
|
||||
}
|
||||
if options.NodeID == "" {
|
||||
return nil, fmt.Errorf("native patch control node id is required")
|
||||
}
|
||||
if options.Store == nil {
|
||||
return nil, fmt.Errorf("native patch control store is required")
|
||||
}
|
||||
if options.Engine == nil {
|
||||
return nil, fmt.Errorf("native patch engine is required")
|
||||
}
|
||||
if options.Now == nil {
|
||||
options.Now = time.Now
|
||||
}
|
||||
reportTimeout := options.ReportTimeout
|
||||
if reportTimeout <= 0 {
|
||||
reportTimeout = 30 * time.Second
|
||||
}
|
||||
return &ControlManager{
|
||||
target: options.Target,
|
||||
nodeID: options.NodeID,
|
||||
store: options.Store,
|
||||
engine: options.Engine,
|
||||
policy: options.Policy,
|
||||
now: options.Now,
|
||||
reportTimeout: reportTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *ControlManager) Process(ctx context.Context, intent ControlIntent) (Result, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
if !intent.AvailableAt.IsZero() && intent.AvailableAt.After(m.now().UTC()) {
|
||||
_ = m.report(ctx, intent, "pending", "waiting")
|
||||
return Result{}, ErrControlNotDue
|
||||
}
|
||||
_ = m.report(ctx, intent, "applying", "")
|
||||
var result Result
|
||||
var err error
|
||||
armedDuration := time.Duration(0)
|
||||
armedRestore := false
|
||||
switch intent.Action {
|
||||
case "", ControlApply:
|
||||
// apply 只消费控制面下发的包路径;包内容仍要经过 LoadPackage 和 Policy 双重校验。
|
||||
pkg, loadErr := LoadPackage(ctx, intent.PackageDir)
|
||||
if loadErr != nil {
|
||||
err = loadErr
|
||||
break
|
||||
}
|
||||
if validateErr := m.policy.Validate(pkg.Plan); validateErr != nil {
|
||||
err = validateErr
|
||||
break
|
||||
}
|
||||
m.opMu.Lock()
|
||||
result, err = m.engine.Apply(ctx, pkg)
|
||||
if err == nil {
|
||||
armedDuration, armedRestore = m.armAutoRestore(intent)
|
||||
}
|
||||
m.opMu.Unlock()
|
||||
case ControlRestore:
|
||||
m.opMu.Lock()
|
||||
result, err = m.engine.Restore(ctx)
|
||||
if err == nil {
|
||||
m.cancelAutoRestore()
|
||||
}
|
||||
m.opMu.Unlock()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported native patch control action %s", intent.Action)
|
||||
}
|
||||
if err != nil {
|
||||
_ = m.report(ctx, intent, "failed", err.Error())
|
||||
return result, err
|
||||
}
|
||||
_ = m.report(ctx, intent, "applied", "")
|
||||
if armedRestore {
|
||||
m.reportArmed(intent, armedDuration)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *ControlManager) Watch(ctx context.Context) error {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
ch, err := m.store.Watch(ctx, m.target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case intent, ok := <-ch:
|
||||
if !ok {
|
||||
return ctx.Err()
|
||||
}
|
||||
// 未到生效时间不是失败;Watcher 继续等待下一条控制意图。
|
||||
if _, err := m.Process(ctx, intent); err != nil && !errors.Is(err, ErrControlNotDue) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ControlManager) report(ctx context.Context, intent ControlIntent, status string, detail string) error {
|
||||
reportCtx, cancel := m.reportContext(ctx)
|
||||
defer cancel()
|
||||
return m.store.Report(reportCtx, ControlProgress{
|
||||
Target: m.target,
|
||||
NodeID: m.nodeID,
|
||||
Version: intent.Version,
|
||||
Action: intent.Action,
|
||||
Status: status,
|
||||
Detail: detail,
|
||||
UpdatedAt: m.now().UTC(),
|
||||
})
|
||||
}
|
||||
|
||||
func (m *ControlManager) reportContext(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if _, ok := ctx.Deadline(); ok {
|
||||
return ctx, func() {}
|
||||
}
|
||||
timeout := 30 * time.Second
|
||||
if m != nil && m.reportTimeout > 0 {
|
||||
timeout = m.reportTimeout
|
||||
}
|
||||
return context.WithTimeout(ctx, timeout)
|
||||
}
|
||||
|
||||
func (m *ControlManager) armAutoRestore(intent ControlIntent) (time.Duration, bool) {
|
||||
if m == nil || m.policy.MaxLiveDuration <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
duration := m.policy.MaxLiveDuration
|
||||
m.mu.Lock()
|
||||
if m.restoreTimer != nil {
|
||||
m.restoreTimer.Stop()
|
||||
}
|
||||
m.restoreGeneration++
|
||||
generation := m.restoreGeneration
|
||||
m.restoreTimer = time.AfterFunc(duration, func() {
|
||||
m.runAutoRestore(generation, intent)
|
||||
})
|
||||
m.mu.Unlock()
|
||||
return duration, true
|
||||
}
|
||||
|
||||
func (m *ControlManager) reportArmed(intent ControlIntent, duration time.Duration) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.reportTimeout)
|
||||
defer cancel()
|
||||
_ = m.report(ctx, intent, "restore_scheduled", duration.String())
|
||||
}
|
||||
|
||||
func (m *ControlManager) runAutoRestore(generation int64, intent ControlIntent) {
|
||||
if !m.autoRestoreCurrent(generation) {
|
||||
return
|
||||
}
|
||||
// 自动恢复走同一套进度上报,方便验收报告确认补丁没有超期滞留。
|
||||
restoreIntent := ControlIntent{
|
||||
Action: ControlRestore,
|
||||
Target: intent.Target,
|
||||
Version: intent.Version,
|
||||
RequestedBy: "auto_restore",
|
||||
Reason: "native patch max live duration reached",
|
||||
Sequence: intent.Sequence,
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.reportTimeout)
|
||||
defer cancel()
|
||||
_ = m.report(ctx, restoreIntent, "restoring", "max_live_duration")
|
||||
if !m.autoRestoreCurrent(generation) {
|
||||
return
|
||||
}
|
||||
m.opMu.Lock()
|
||||
defer m.opMu.Unlock()
|
||||
if !m.autoRestoreCurrent(generation) {
|
||||
return
|
||||
}
|
||||
if _, err := m.engine.Restore(ctx); err != nil {
|
||||
if m.autoRestoreCurrent(generation) {
|
||||
_ = m.report(ctx, restoreIntent, "restore_failed", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
if !m.autoRestoreCurrent(generation) {
|
||||
return
|
||||
}
|
||||
m.finishAutoRestore(generation)
|
||||
_ = m.report(ctx, restoreIntent, "restored", "max_live_duration")
|
||||
}
|
||||
|
||||
func (m *ControlManager) autoRestoreCurrent(generation int64) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.restoreTimer != nil && m.restoreGeneration == generation
|
||||
}
|
||||
|
||||
func (m *ControlManager) finishAutoRestore(generation int64) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
if m.restoreGeneration == generation {
|
||||
m.restoreTimer = nil
|
||||
m.restoreGeneration++
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *ControlManager) cancelAutoRestore() {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
if m.restoreTimer != nil {
|
||||
m.restoreTimer.Stop()
|
||||
m.restoreTimer = nil
|
||||
}
|
||||
m.restoreGeneration++
|
||||
m.mu.Unlock()
|
||||
}
|
||||
Reference in new issue
Block a user