服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
171 lines
4.1 KiB
Go
171 lines
4.1 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// RedisHashBackend 用 Redis hash 实现 HashBackend。
|
|
type RedisHashBackend struct {
|
|
Executor RedisExecutor
|
|
}
|
|
|
|
// NewRedisHashBackend 创建 Redis hash 后端。
|
|
func NewRedisHashBackend(executor RedisExecutor) RedisHashBackend {
|
|
return RedisHashBackend{Executor: executor}
|
|
}
|
|
|
|
// LoadHash 从 Redis hash 读取字段。
|
|
func (b RedisHashBackend) LoadHash(ctx context.Context, key string, fields []string) (map[string][]byte, bool, error) {
|
|
if b.Executor == nil {
|
|
return nil, false, errors.New("redis executor is nil")
|
|
}
|
|
ctx = contextOrBackground(ctx)
|
|
key = strings.TrimSpace(key)
|
|
if key == "" {
|
|
return nil, false, ErrRecordKeyRequired
|
|
}
|
|
fields = normHashFields(fields)
|
|
if len(fields) == 0 {
|
|
reply, err := b.Executor.Do(ctx, "HGETALL", key)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
return redisHGetAllFields(reply)
|
|
}
|
|
args := make([]any, 0, 1+len(fields))
|
|
args = append(args, key)
|
|
for _, field := range fields {
|
|
args = append(args, field)
|
|
}
|
|
reply, err := b.Executor.Do(ctx, "HMGET", args...)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
values, err := redisArray(reply)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(values) != len(fields) {
|
|
return nil, false, fmt.Errorf("redis HMGET returned %d values for %d fields", len(values), len(fields))
|
|
}
|
|
out := make(map[string][]byte, len(fields))
|
|
for i, value := range values {
|
|
data, ok, err := redisBulkBytes(value)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if ok {
|
|
out[fields[i]] = data
|
|
}
|
|
}
|
|
return out, len(out) > 0, nil
|
|
}
|
|
|
|
// SaveHash 保存 Redis hash 字段。
|
|
func (b RedisHashBackend) SaveHash(ctx context.Context, key string, fields map[string][]byte, ttl time.Duration) error {
|
|
return SaveRedisHashFields(ctx, b.Executor, key, fields, ttl)
|
|
}
|
|
|
|
// SaveHashBatch 批量保存 Redis hash 字段。
|
|
func (b RedisHashBackend) SaveHashBatch(ctx context.Context, batches []HashSaveBatch) error {
|
|
merged, err := mergeHashSaveBatches(batches)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
redisBatches := make([]RedisHashBatch, 0, len(merged))
|
|
for _, batch := range merged {
|
|
redisBatches = append(redisBatches, RedisHashBatch(batch))
|
|
}
|
|
return SaveRedisHashFieldBatches(ctx, b.Executor, redisBatches)
|
|
}
|
|
|
|
// Check 通过 PING 检查 Redis 连接。
|
|
func (b RedisHashBackend) Check(ctx context.Context) error {
|
|
if b.Executor == nil {
|
|
return errors.New("redis executor is nil")
|
|
}
|
|
ctx = contextOrBackground(ctx)
|
|
_, err := b.Executor.Do(ctx, "PING")
|
|
return err
|
|
}
|
|
|
|
func redisHGetAllFields(reply any) (map[string][]byte, bool, error) {
|
|
values, err := redisArray(reply)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if len(values)%2 != 0 {
|
|
return nil, false, fmt.Errorf("redis HGETALL returned odd value count %d", len(values))
|
|
}
|
|
out := make(map[string][]byte, len(values)/2)
|
|
for i := 0; i < len(values); i += 2 {
|
|
name, ok, err := redisBulkString(values[i])
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if !ok {
|
|
continue
|
|
}
|
|
data, ok, err := redisBulkBytes(values[i+1])
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if ok {
|
|
out[name] = data
|
|
}
|
|
}
|
|
return out, len(out) > 0, nil
|
|
}
|
|
|
|
func redisArray(reply any) ([]any, error) {
|
|
switch value := reply.(type) {
|
|
case nil:
|
|
return nil, nil
|
|
case []any:
|
|
return value, nil
|
|
case [][]byte:
|
|
out := make([]any, len(value))
|
|
for i, item := range value {
|
|
if item == nil {
|
|
out[i] = nil
|
|
} else {
|
|
out[i] = append([]byte(nil), item...)
|
|
}
|
|
}
|
|
return out, nil
|
|
case []string:
|
|
out := make([]any, len(value))
|
|
for i, item := range value {
|
|
out[i] = item
|
|
}
|
|
return out, nil
|
|
default:
|
|
return nil, fmt.Errorf("unexpected redis array reply %T", reply)
|
|
}
|
|
}
|
|
|
|
func redisBulkString(value any) (string, bool, error) {
|
|
data, ok, err := redisBulkBytes(value)
|
|
if err != nil || !ok {
|
|
return "", ok, err
|
|
}
|
|
return string(data), true, nil
|
|
}
|
|
|
|
func redisBulkBytes(value any) ([]byte, bool, error) {
|
|
switch v := value.(type) {
|
|
case nil:
|
|
return nil, false, nil
|
|
case []byte:
|
|
return append([]byte(nil), v...), true, nil
|
|
case string:
|
|
return []byte(v), true, nil
|
|
default:
|
|
return nil, false, fmt.Errorf("unexpected redis bulk reply %T", value)
|
|
}
|
|
}
|