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,193 @@
package metrics
import (
"fmt"
"net/http"
"regexp"
"sort"
"strings"
)
var metricNameRE = regexp.MustCompile(`[^a-zA-Z0-9_:]`)
var labelNameRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
type prometheusSample struct {
name string
typ Type
labels string
value int64
float bool
floatValue float64
}
func Handler(registry *Registry) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
ObserveRuntime(registry)
registry.RunObservers()
WritePrometheus(w, registry.Snapshot())
}
}
func WritePrometheus(w interface{ Write([]byte) (int, error) }, samples []Sample) {
seenTypes := make(map[string]Type)
for _, sample := range normalizeSamples(samples) {
// Histogram 多个子样本共享同一 base 名字 (<base>_bucket / _count / _sum),
// TYPE 标注必须按 family 去重;其他类型按完整 metric 名去重保持原行为。
typeKey := sample.name
if sample.typ == TypeHistogram {
typeKey = histogramFamily(sample.name)
}
if existing, ok := seenTypes[typeKey]; ok {
if existing != sample.typ {
continue
}
} else {
seenTypes[typeKey] = sample.typ
_, _ = fmt.Fprintf(w, "# TYPE %s %s\n", typeKey, prometheusType(sample.typ))
}
if sample.float {
// 用 %g 而非 %f:histogram 的 sum 既要避免 1.0e-06 这种科学计数,
// 又要避免拖一长串无意义的零;%g 在大多数 latency 场景下输出可读小数。
_, _ = fmt.Fprintf(w, "%s%s %g\n", sample.name, sample.labels, sample.floatValue)
} else {
_, _ = fmt.Fprintf(w, "%s%s %d\n", sample.name, sample.labels, sample.value)
}
}
}
// histogramFamily 把 <base>_bucket / <base>_count / <base>_sum
// 还原成 <base>,用于 TYPE 标注去重。未识别后缀时返回原名,等价于"该样本自己就是 family"。
func histogramFamily(name string) string {
for _, suffix := range []string{"_sum", "_count", "_bucket"} {
if strings.HasSuffix(name, suffix) {
return strings.TrimSuffix(name, suffix)
}
}
return name
}
func normalizeSamples(samples []Sample) []prometheusSample {
merged := make(map[string]prometheusSample, len(samples))
for _, sample := range samples {
name := normalizeName(sample.Name)
if name == "" {
continue
}
labels := formatLabels(sample.Labels)
key := string(sample.Type) + "\x00" + name + "\x00" + labels
current := merged[key]
if current.name == "" {
current = prometheusSample{name: name, typ: sample.Type, labels: labels}
}
// Histogram observers 写入的是绝对值(替换语义,跟 gauge 一致),不能累加;
// 否则 bucket / count / sum 会被多次叠加。Gauge 同理。
switch sample.Type {
case TypeGauge, TypeHistogram:
current.value = sample.Value
current.float = sample.Float
current.floatValue = sample.FloatValue
default:
current.value += sample.Value
}
merged[key] = current
}
out := make([]prometheusSample, 0, len(merged))
for _, sample := range merged {
out = append(out, sample)
}
sort.Slice(out, func(i, j int) bool {
if out[i].name != out[j].name {
return out[i].name < out[j].name
}
if out[i].typ != out[j].typ {
return out[i].typ < out[j].typ
}
return out[i].labels < out[j].labels
})
return out
}
func prometheusType(t Type) string {
switch t {
case TypeGauge:
return "gauge"
case TypeHistogram:
return "histogram"
default:
return "counter"
}
}
func normalizeName(name string) string {
return normalizeIdentifier(name, metricNameRE, true)
}
func normalizeLabelName(name string) string {
return normalizeIdentifier(name, labelNameRE, false)
}
func normalizeIdentifier(name string, invalid *regexp.Regexp, allowColonFirst bool) string {
name = strings.TrimSpace(name)
name = invalid.ReplaceAllString(name, "_")
name = strings.Trim(name, "_")
if name == "" {
return ""
}
first := name[0]
if isAlpha(first) || first == '_' || (allowColonFirst && first == ':') {
return name
}
return "_" + name
}
func isAlpha(b byte) bool {
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
}
func formatLabels(labels map[string]string) string {
if len(labels) == 0 {
return ""
}
normalized := make(map[string]string, len(labels))
keys := make([]string, 0, len(labels))
originalKeys := make([]string, 0, len(labels))
for key := range labels {
originalKeys = append(originalKeys, key)
}
sort.Strings(originalKeys)
for _, key := range originalKeys {
name := normalizeLabelName(key)
if name != "" {
if _, exists := normalized[name]; exists {
continue
}
normalized[name] = labels[key]
keys = append(keys, name)
}
}
sort.Strings(keys)
if len(keys) == 0 {
return ""
}
var b strings.Builder
b.WriteByte('{')
for idx, key := range keys {
if idx > 0 {
b.WriteByte(',')
}
b.WriteString(key)
b.WriteString(`="`)
b.WriteString(escapeLabel(normalized[key]))
b.WriteByte('"')
}
b.WriteByte('}')
return b.String()
}
func escapeLabel(value string) string {
value = strings.ReplaceAll(value, `\`, `\\`)
value = strings.ReplaceAll(value, "\n", `\n`)
value = strings.ReplaceAll(value, `"`, `\"`)
return value
}