Files
90CNServer/server/internal/architecture/dependency_test.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

122 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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), "..", ".."))
}