Files
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

316 lines
7.4 KiB
Go

package db
import (
"context"
"errors"
"sort"
"strings"
"sync"
"time"
)
// ErrHashBackendRequired 表示 hash 后端缺失或没有完成初始化。
var ErrHashBackendRequired = errors.New("hash backend is required")
// HashBackend 是按记录主键和字段名读写二进制字段的后端接口。
type HashBackend interface {
LoadHash(context.Context, string, []string) (map[string][]byte, bool, error)
SaveHash(context.Context, string, map[string][]byte, time.Duration) error
}
// HashSaveBatch 表示一次 hash 字段批量保存项。
type HashSaveBatch struct {
Key string
Fields map[string][]byte
TTL time.Duration
}
// HashBatchBackend 支持批量保存 hash 字段。
type HashBatchBackend interface {
SaveHashBatch(context.Context, []HashSaveBatch) error
}
// HashLoadRequest 描述一次 hash 批量读取请求。
type HashLoadRequest struct {
Key string
Fields []string
}
// HashLoadResult 描述一次 hash 批量读取结果。
type HashLoadResult struct {
Fields map[string][]byte
Found bool
}
// HashBatchLoaderBackend 支持批量读取 hash 字段。
type HashBatchLoaderBackend interface {
LoadHashBatch(context.Context, []HashLoadRequest) (map[string]HashLoadResult, error)
}
// MemoryHashBackend 是线程安全的内存 hash 后端。
type MemoryHashBackend struct {
mu sync.RWMutex
records map[string]map[string][]byte
ttl map[string]time.Duration
}
// NewMemoryHashBackend 创建内存 hash 后端。
func NewMemoryHashBackend() *MemoryHashBackend {
return &MemoryHashBackend{
records: make(map[string]map[string][]byte),
ttl: make(map[string]time.Duration),
}
}
// LoadHash 从内存读取指定 hash 字段。
func (b *MemoryHashBackend) LoadHash(ctx context.Context, key string, fields []string) (map[string][]byte, bool, error) {
if b == nil {
return nil, false, ErrHashBackendRequired
}
ctx = contextOrBackground(ctx)
if err := ctx.Err(); err != nil {
return nil, false, err
}
key = strings.TrimSpace(key)
if key == "" {
return nil, false, ErrRecordKeyRequired
}
fields = normHashFields(fields)
b.mu.RLock()
record, ok := b.records[key]
if !ok {
b.mu.RUnlock()
return nil, false, nil
}
out := make(map[string][]byte, len(record))
if len(fields) == 0 {
for name, data := range record {
out[name] = append([]byte(nil), data...)
}
} else {
for _, name := range fields {
if data, exists := record[name]; exists {
out[name] = append([]byte(nil), data...)
}
}
}
b.mu.RUnlock()
return out, true, nil
}
// LoadHashBatch 批量读取 hash 字段。
func (b *MemoryHashBackend) LoadHashBatch(ctx context.Context, requests []HashLoadRequest) (map[string]HashLoadResult, error) {
if b == nil {
return nil, ErrHashBackendRequired
}
ctx = contextOrBackground(ctx)
if err := ctx.Err(); err != nil {
return nil, err
}
out := make(map[string]HashLoadResult, len(requests))
for _, request := range requests {
fields, found, err := b.LoadHash(ctx, request.Key, request.Fields)
if err != nil {
return nil, err
}
out[strings.TrimSpace(request.Key)] = HashLoadResult{Fields: fields, Found: found}
}
return out, nil
}
// SaveHash 保存指定 hash 字段。
func (b *MemoryHashBackend) SaveHash(ctx context.Context, key string, fields map[string][]byte, ttl time.Duration) error {
if b == nil {
return ErrHashBackendRequired
}
ctx = contextOrBackground(ctx)
if err := ctx.Err(); err != nil {
return err
}
key = strings.TrimSpace(key)
if key == "" {
return ErrRecordKeyRequired
}
fields, err := cloneHashFields(fields)
if err != nil {
return err
}
if len(fields) == 0 {
return nil
}
b.mu.Lock()
b.ensureReadyLocked()
record := b.records[key]
if record == nil {
record = make(map[string][]byte, len(fields))
b.records[key] = record
}
for name, data := range fields {
record[name] = append([]byte(nil), data...)
}
if ttl > 0 {
b.ttl[key] = ttl
}
b.mu.Unlock()
return nil
}
// SaveHashBatch 批量保存 hash 字段。
func (b *MemoryHashBackend) SaveHashBatch(ctx context.Context, batches []HashSaveBatch) error {
if b == nil {
return ErrHashBackendRequired
}
ctx = contextOrBackground(ctx)
merged, err := mergeHashSaveBatches(batches)
if err != nil {
return err
}
for _, batch := range merged {
if err := ctx.Err(); err != nil {
return err
}
if err := b.SaveHash(ctx, batch.Key, batch.Fields, batch.TTL); err != nil {
return err
}
}
return nil
}
func mergeHashSaveBatches(batches []HashSaveBatch) ([]HashSaveBatch, error) {
if len(batches) == 0 {
return nil, nil
}
merged := make(map[string]HashSaveBatch, len(batches))
for _, batch := range batches {
if len(batch.Fields) == 0 {
continue
}
key := strings.TrimSpace(batch.Key)
if key == "" {
return nil, ErrRecordKeyRequired
}
fields, err := cloneHashFields(batch.Fields)
if err != nil {
return nil, err
}
if len(fields) == 0 {
continue
}
current := merged[key]
if current.Fields == nil {
current = HashSaveBatch{
Key: key,
Fields: make(map[string][]byte, len(fields)),
}
}
for name, data := range fields {
current.Fields[name] = append([]byte(nil), data...)
}
current.TTL = batch.TTL
merged[key] = current
}
keys := make([]string, 0, len(merged))
for key := range merged {
keys = append(keys, key)
}
sort.Strings(keys)
out := make([]HashSaveBatch, 0, len(keys))
for _, key := range keys {
batch := merged[key]
batch.Fields = cloneBytesMap(batch.Fields)
out = append(out, batch)
}
return out, nil
}
// Check 检查上下文是否已取消。
func (b *MemoryHashBackend) Check(ctx context.Context) error {
if b == nil {
return ErrHashBackendRequired
}
ctx = contextOrBackground(ctx)
return ctx.Err()
}
// Snapshot 返回全部 hash 记录副本。
func (b *MemoryHashBackend) Snapshot() map[string]map[string][]byte {
if b == nil {
return map[string]map[string][]byte{}
}
b.mu.RLock()
out := make(map[string]map[string][]byte, len(b.records))
for key, fields := range b.records {
out[key] = cloneBytesMap(fields)
}
b.mu.RUnlock()
return out
}
func (b *MemoryHashBackend) ensureReadyLocked() {
if b.records == nil {
b.records = make(map[string]map[string][]byte)
}
if b.ttl == nil {
b.ttl = make(map[string]time.Duration)
}
}
func cloneHashFields(fields map[string][]byte) (map[string][]byte, error) {
if len(fields) == 0 {
return nil, nil
}
out := make(map[string][]byte, len(fields))
names := make([]string, 0, len(fields))
for rawName := range fields {
names = append(names, rawName)
}
sort.Strings(names)
for _, rawName := range names {
name := strings.TrimSpace(rawName)
if name == "" {
return nil, ErrRecordKeyRequired
}
if _, exists := out[name]; exists {
continue
}
data := fields[rawName]
if data == nil {
return nil, errors.New("hash field value is nil")
}
out[name] = append([]byte(nil), data...)
}
return out, nil
}
func cloneBytesMap(in map[string][]byte) map[string][]byte {
if len(in) == 0 {
return nil
}
out := make(map[string][]byte, len(in))
for key, data := range in {
out[key] = append([]byte(nil), data...)
}
return out
}
func normHashFields(fields []string) []string {
if len(fields) == 0 {
return nil
}
seen := make(map[string]struct{}, len(fields))
for _, field := range fields {
field = strings.TrimSpace(field)
if field != "" {
seen[field] = struct{}{}
}
}
out := make([]string, 0, len(seen))
for field := range seen {
out = append(out, field)
}
sort.Strings(out)
return out
}