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,121 @@
// Package architecture_test 用 AST 静态断言 AGENTS.md 的 Source Boundaries。
// 测试只解析本模块源码 import,离线可运行、结果确定:
// 1. internal/modules/dnf/** 不得 import internal/services/**(领域层不依赖传输/装配层);
// 2. internal/services/dnfbridge 不得 import internal/platform 的数据库/仓储实现层
// (只允许经 internal/modules/dnf/repository 的 dnfrepo 接口路径);
// 3. internal/app/dnf90 之外不得 import internal/services/logic/dnf(组合根唯一),
// internal/services/logic/dnf/** 自身子包不受限,既有例外必须登记在 allowedExceptions。
package architecture_test
import (
"go/parser"
"go/token"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
)
const modulePath = "longheng.io/server/"
// allowedExceptions 登记现状盘点发现的既有违规,格式为 文件相对路径 -> import 前缀 -> 保留原因。
// 断言规则:清单之外不得新增违规;清单内条目一旦不再被引用(stale)同样失败,
// 保证允许清单始终最小且与代码同步。新增例外必须在注释中说明原因。
var allowedExceptions = map[string]map[string]string{
"cmd/server/doctor/main.go": {
// doctor 是部署预检工具,需复用 logic/dnf 的配置解析与仓储装配做启动前校验(2026-07 盘点时的既有例外)。
modulePath + "internal/services/logic/dnf": "doctor preflight reuses logic/dnf config loading and repository assembly",
},
}
func TestSourceBoundaries(t *testing.T) {
root := moduleRoot(t)
matched := map[string]map[string]bool{}
err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() {
if entry.Name() == ".git" {
return filepath.SkipDir
}
return nil
}
if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") {
return nil
}
relative, err := filepath.Rel(root, path)
if err != nil {
return err
}
relative = filepath.ToSlash(relative)
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly)
if err != nil {
t.Errorf("parse %s: %v", relative, err)
return nil
}
for _, imported := range file.Imports {
pathValue, err := strconv.Unquote(imported.Path.Value)
if err != nil {
t.Errorf("decode import in %s: %v", relative, err)
continue
}
reason := forbiddenImport(relative, pathValue)
if reason == "" {
continue
}
if _, ok := allowedExceptions[relative][pathValue]; ok {
if matched[relative] == nil {
matched[relative] = map[string]bool{}
}
matched[relative][pathValue] = true
continue
}
t.Errorf("%s imports %q: %s", relative, pathValue, reason)
}
return nil
})
if err != nil {
t.Fatal(err)
}
for file, imports := range allowedExceptions {
for prefix, why := range imports {
if !matched[file][prefix] {
t.Errorf("stale allowlist entry %s -> %q (%s): remove it or restore the import", file, prefix, why)
}
}
}
}
func forbiddenImport(relative, imported string) string {
// 规则 1:DNF 领域层不依赖传输/装配层。
if strings.HasPrefix(relative, "internal/modules/dnf/") {
if strings.HasPrefix(imported, modulePath+"internal/services/") {
return "internal/modules/dnf owns domain rules and must not import internal/services"
}
}
// 规则 2:bridge 只允许经 dnfrepo 接口访问持久化,不得直连平台数据库/仓储实现层。
if strings.HasPrefix(relative, "internal/services/dnfbridge/") {
if imported == modulePath+"internal/platform/db" || strings.HasPrefix(imported, modulePath+"internal/platform/db/") {
return "internal/services/dnfbridge must reach persistence through internal/modules/dnf/repository interfaces, not platform db implementations"
}
}
// 规则 3:组合根唯一。logic/dnf 树内部(含 game 子包)不受限,其余例外须登记 allowedExceptions。
if !strings.HasPrefix(relative, "internal/app/dnf90/") && !strings.HasPrefix(relative, "internal/services/logic/dnf/") {
if imported == modulePath+"internal/services/logic/dnf" || strings.HasPrefix(imported, modulePath+"internal/services/logic/dnf/") {
return "only the internal/app/dnf90 composition root may import internal/services/logic/dnf"
}
}
return ""
}
func moduleRoot(t *testing.T) string {
t.Helper()
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("resolve architecture test path")
}
return filepath.Clean(filepath.Join(filepath.Dir(file), "..", ".."))
}