Files
90CNServer/server/internal/services/dnfbridge/slow_request_diagnostic_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

66 lines
3.2 KiB
Go

package dnfbridge
import (
"testing"
"time"
"longheng.io/server/internal/modules/dnf/channelcatalog"
dnfproto "longheng.io/server/internal/modules/dnf/protocol"
)
// 这两个慢请求诊断事件是现网延迟排查的唯一手段:玩家侧无法复现,只能靠服务器
// 自己把超过门限的请求记下来。因此必须锁死「事件不会被 suppressPacketLogEvent
// 静默丢弃」这一条。
//
// 关键点:本测试调用的是**生产代码同一个字段构造函数**(slowUpperRequestFields /
// slowLegacyRequestFields),而不是照抄一份字段列表。照抄的版本改坏线上代码也不会
// 失败——这一点已用 mutation test 实测验证过,所以字段定义必须共用。
//
// 曾经踩过的坑:字段名用过 "msg_id",而 suppressPacketLogEvent 会按 msg_id 命中
// 1276/1516/1518 直接返回 true;那三个恰恰是最可能变慢的高频 opcode,等于诊断在
// 最需要的时候哑火。
func TestSlowRequestDiagnosticEventsAreNotSuppressed(t *testing.T) {
session := &gameSession{connID: "game-000001", channel: channelcatalog.Channel{ID: 93}}
// game-slow-upper-request:含最高风险的 1276/1516/1518 与 msg_id 13。
for _, riskyMsgID := range []uint16{1276, 1516, 1518, 13, 4, 476, 1, 593} {
packet := dnfproto.ChannelPacket{
Header: dnfproto.ChannelHeader{MsgID: riskyMsgID, Classification: 1, Seq: 7},
Body: []byte{1, 2, 3},
}
fields := slowUpperRequestFields(session, packet, "ENUM_CMDPACKET_TEST", true, 3, 250*time.Millisecond)
if suppressPacketLogEvent("game-slow-upper-request", fields...) {
t.Fatalf("game-slow-upper-request 被抑制(request_msg_id=%d);诊断会哑火", riskyMsgID)
}
}
// game-slow-legacy-request:含 type 35(被抑制的 legacy-meta 值)与进游戏主力的 8/197。
for _, riskyType := range []uint16{35, 8, 197, 1425, 1345, 4} {
fields := slowLegacyRequestFields(session, byte(1), riskyType, 16, 250*time.Millisecond)
if suppressPacketLogEvent("game-slow-legacy-request", fields...) {
t.Fatalf("game-slow-legacy-request 被抑制(request_type=%d);诊断会哑火", riskyType)
}
}
}
// 反向确认:抑制规则本身仍然生效,本测试不是靠「suppressPacketLogEvent 永远
// 返回 false」蒙过去的。
func TestSuppressionRulesStillApplyToKnownChattyEvents(t *testing.T) {
if !suppressPacketLogEvent("game-upper-send-meta", "msg_id", uint16(1276)) {
t.Fatal("msg_id 1276 的 game-upper-send-meta 应仍被抑制")
}
if !suppressPacketLogEvent("game-upper-send-meta", "msg_id", uint16(13)) {
t.Fatal("msg_id 13 的 game-upper-send-meta 应仍被抑制")
}
if !suppressPacketLogEvent("game-legacy-meta", "type", uint16(35)) {
t.Fatal("type 35 的 game-legacy-meta 应仍被抑制")
}
if !suppressPacketLogEvent("game-town-set-user-position-captured") {
t.Fatal("game-town-set-user-position-captured 应仍被抑制")
}
// 证明抑制是靠 "msg_id"/"type" 这两个字段名触发的:换成别的名字就不该被抑制。
if suppressPacketLogEvent("game-upper-send-meta", "request_msg_id", uint16(1276)) {
t.Fatal("字段名不是 msg_id 时不应触发抑制——这正是诊断事件改用 request_msg_id 的原因")
}
}