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,70 @@
// 本文件把「持久化能力接口」的名字放在仓储层,供服务层使用。
//
// 为什么需要它:投影读缓存(internal/services/dnfbridge/projection_read_cache.go)要在一次扇出里
// 包住仓储实现,并在转发时保留这些可选能力(字段级保存、批量字段保存、连通性自检、TTL)。
// 这些接口定义在 internal/platform/db,而 Source Boundaries 的规则 2
// (internal/architecture/dependency_test.go:99-104)规定服务层不得 import platform/db,
// 只能经 internal/modules/dnf/repository 访问持久化。仓储层本来就在用这些类型
// (各仓储接口内嵌 db.Store[Record]),所以由它把名字交给服务层正是这条规则的既定路径。
//
// 这里刻意用**类型别名**而不是新定义的接口:别名与原类型完全等价,服务层里的类型断言、
// 泛型实例化与转发行为一字不变(改前改后语义相同,只是名字换了归属)。
package repository
import (
"context"
"longheng.io/server/internal/platform/db"
)
// Store 是按 key 读写记录的持久化存储。
type Store[T any] = db.Store[T]
// FieldStore 能只保存一条记录的指定字段。
type FieldStore[T any, F comparable] = db.FieldStore[T, F]
// BatchFieldStore 能批量保存多条记录的指定字段。
type BatchFieldStore[T any, F comparable] = db.BatchFieldStore[T, F]
// FieldSave 是一次「记录 + 要保存的字段」。
type FieldSave[T any, F comparable] = db.FieldSave[T, F]
// Checker 是存储的连通性自检能力(仓储健康检查)。
type Checker = db.Checker
// Expirer 是给 key 设置 TTL 的能力。
type Expirer = db.Expirer
// SaveFields 只保存给定字段;normalize 为 nil 时不做字段规整。
func SaveFields[T any, F comparable](
ctx context.Context,
store Store[T],
record T,
normalize func([]F) []F,
fields ...F,
) error {
return db.SaveFields(ctx, store, record, normalize, fields...)
}
// 下面的编译期断言钉住「这些名字是**别名**,不是新定义的类型」。
//
// 为什么要钉:服务层(projection_read_cache.go)用 `x.(dnfrepo.FieldStore[T, F])` 这样的类型断言
// 来判断某个仓储实现是否支持字段级保存。别名两边可互相赋值,断言照旧命中;一旦有人把它们改成
// `type FieldStore[T any, F comparable] interface{ ... }`(新类型),断言就不再命中**仓储实现**,
// 投影读缓存会静默失效——不报错、不崩溃,只是每次扇出都重新读库(性能回退,且很难发现)。
// 有了这两组双向赋值断言,那种改动会**编译不过**。
var (
_ Store[struct{}] = (db.Store[struct{}])(nil)
_ db.Store[struct{}] = (Store[struct{}])(nil)
_ FieldStore[struct{}, int] = (db.FieldStore[struct{}, int])(nil)
_ db.FieldStore[struct{}, int] = (FieldStore[struct{}, int])(nil)
_ BatchFieldStore[struct{}, int] = (db.BatchFieldStore[struct{}, int])(nil)
_ db.BatchFieldStore[struct{}, int] = (BatchFieldStore[struct{}, int])(nil)
_ Checker = (db.Checker)(nil)
_ db.Checker = (Checker)(nil)
_ Expirer = (db.Expirer)(nil)
_ db.Expirer = (Expirer)(nil)
_ FieldSave[struct{}, int] = db.FieldSave[struct{}, int]{}
_ func(context.Context, Store[struct{}], struct{}, func([]int) []int, ...int) error = SaveFields[struct{}, int]
)