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,91 @@
package presence
import (
"context"
"strings"
"time"
"longheng.io/server/internal/platform/metrics"
)
const (
// MetricPresenceCount 是在线 session 数 gauge 名称。
MetricPresenceCount = "presence_count"
// MetricUserCount 是在线用户数 gauge 名称。
MetricUserCount = "presence_user_count"
// MetricSubscriptionCount 是订阅关系数 gauge 名称。
MetricSubscriptionCount = "presence_subscription_count"
)
// MetricsObserver 表示底层 runtime 暴露了适合 Prometheus scrape 的 gauge。
// Manager 和 RedisManager 都实现该接口,wiring 代码可以通过类型断言挂到
// metrics.Registry,而不需要绑定具体实现。
type MetricsObserver interface {
ObserveMetrics(reg *metrics.Registry, labels map[string]string)
}
const defRedisObsTimeout = 2 * time.Second
// ObserveMetrics 基于内存快照把 presence_count / presence_user_count /
// presence_subscription_count 发布为 gauge。它只持有 RLock、没有 IO,
// 可以安全用于 scrape 路径。
func (m *Manager) ObserveMetrics(reg *metrics.Registry, labels map[string]string) {
if m == nil || reg == nil {
return
}
snapshot := m.Snapshot()
setPresenceGauges(reg, labels, int64(snapshot.PresenceCount), int64(snapshot.UserCount), int64(snapshot.SubscriptionCount))
}
// ObserveMetrics 对共享 Redis presence key 执行两次 SCARD。
// SCARD 是 O(1),适合 scrape 路径;这里仍用短超时包住 Redis 访问:
// Redis 失败时本次 gauge 不更新,而不是阻塞 metrics endpoint。
func (m *RedisManager) ObserveMetrics(reg *metrics.Registry, labels map[string]string) {
if m == nil || reg == nil {
return
}
m.ensureReady()
if m.executor == nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), defRedisObsTimeout)
defer cancel()
presences, err := m.redisInt(ctx, "SCARD", m.sessionsIndexKey())
if err != nil {
return
}
users, err := m.redisInt(ctx, "SCARD", m.usersIndexKey())
if err != nil {
return
}
subscriptions, err := m.redisInt(ctx, "SCARD", m.subIndexKey())
if err != nil {
return
}
setPresenceGauges(reg, labels, presences, users, subscriptions)
}
func setPresenceGauges(reg *metrics.Registry, labels map[string]string, presences, users, subscriptions int64) {
gaugeLabels := normObserverLabels(labels)
reg.SetGauge(MetricPresenceCount, gaugeLabels, presences)
reg.SetGauge(MetricUserCount, gaugeLabels, users)
reg.SetGauge(MetricSubscriptionCount, gaugeLabels, subscriptions)
}
func normObserverLabels(labels map[string]string) map[string]string {
if len(labels) == 0 {
return nil
}
out := make(map[string]string, len(labels))
for key, value := range labels {
key = strings.TrimSpace(key)
if key == "" {
continue
}
out[key] = value
}
if len(out) == 0 {
return nil
}
return out
}