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,117 @@
package runtimeguard
import "strings"
// 生产类环境集合:与 SECURITY.md / config validation 中的"生产基线"对齐。
// 任何一项命中即视为多节点 Profile。命中条件做成显式列表(而不是"非 dev 即生产"),
// 避免出现"prod-canary" 这种新词被悄悄当成 dev 的情况——这种情形应该明确加进来。
var prodEnvTokens = []string{
"prod",
"production",
"staging",
"stage",
"preprod",
"pre-production",
"k8s",
"kubernetes",
"helm",
"cluster",
"live",
}
// IsProductionEnvironment 判断给定的环境名是否落在"生产类"集合内。匹配规则:
// 字符串包含 prodEnvTokens 中任意一个 token(不区分大小写)。
// 例如 "prod-canary"、"k8s-staging"、"helm-cluster-2" 都视为生产类。
func IsProductionEnvironment(env string) bool {
env = strings.ToLower(strings.TrimSpace(env))
if env == "" {
return false
}
for _, token := range prodEnvTokens {
if strings.Contains(env, token) {
return true
}
}
return false
}
// ProfileFromEnvironment 从环境名 + topology 开关推导出一个默认 Profile。
// 规则:
// - 命中生产类环境名 -> Distributed=true(即使 topology 关着,因为多 logic 实例
// 是生产部署的常态,要求项目显式声明"这不是多节点"才允许 memory 后端)
// - topology 显式开启 -> Distributed=true
// - 否则 Distributed=false(local / dev / smoke 默认放行内存后端)
//
// 项目侧可以拿到这个 Profile 之后再调 WithAllowMemory 把已知"暂时不能上分布式后端"
// 的模块加入白名单。
func ProfileFromEnvironment(env string, topologyEnabled bool) Profile {
distributed := topologyEnabled || IsProductionEnvironment(env)
return Profile{Distributed: distributed}
}
// ProfileFromEnvironmentAndBackends 从环境名、topology 开关和实际存储后端共同推导 Profile。
//
// 这个入口用于项目侧 runtime wiring。不要只因为 service.environment 写成 local
// 就把共享 runtime 当成单节点安全:如果 Profile / guild / chat 等 owner 已经接入
// MySQL、Redis 或 hybrid 后端,说明这条链路正在使用跨进程权威/缓存,启动期也应该
// 按分布式 Profile 检查内存 runtime。
func ProfileFromEnvironmentAndBackends(env string, topologyEnabled bool, backends ...Backend) Profile {
distributed := topologyEnabled || IsProductionEnvironment(env)
for _, backend := range backends {
if backend.IsDistributed() {
distributed = true
break
}
}
return Profile{Distributed: distributed}
}
// BackendFromStoreKind 把通用配置里的 store_kind / kind 字段映射为 runtimeguard 后端。
// 项目侧在接入自定义 owner 时可以复用它,避免散落的字符串判断只看 environment。
func BackendFromStoreKind(kind string) Backend {
switch strings.ToLower(strings.TrimSpace(kind)) {
case "redis", "pika", "profiledb", "profile_db":
return BackendRedis
case "mysql":
return BackendMySQL
case "mysql_redis", "redis_mysql", "hybrid":
return BackendHybrid
case "memory", "file", "json", "":
return BackendMemory
default:
return BackendUnknown
}
}
// WithAllowMemory 是 Profile 链式构造的便捷方法:返回一份拷贝,附加 module 到
// AllowMemoryModules 白名单。
func (p Profile) WithAllowMemory(modules ...string) Profile {
if len(modules) == 0 {
return p
}
out := Profile{
Distributed: p.Distributed,
AllowMemoryModules: make([]string, 0, len(p.AllowMemoryModules)+len(modules)),
}
out.AllowMemoryModules = append(out.AllowMemoryModules, p.AllowMemoryModules...)
for _, m := range modules {
m = strings.TrimSpace(m)
if m == "" {
continue
}
if containsString(out.AllowMemoryModules, m) {
continue
}
out.AllowMemoryModules = append(out.AllowMemoryModules, m)
}
return out
}
func containsString(list []string, target string) bool {
for _, item := range list {
if item == target {
return true
}
}
return false
}