DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,146 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"longheng.io/server/internal/platform/dispatch"
|
||||
"longheng.io/server/internal/platform/idempotency"
|
||||
"longheng.io/server/pkg/contracts"
|
||||
"longheng.io/server/pkg/protocol"
|
||||
)
|
||||
|
||||
func (s *Service) cachedIdemResp(ctx context.Context, packet contracts.GatewayClientPacket, decision idempotency.Decision) (contracts.LogicPlayerResponse, bool, error) {
|
||||
if s == nil || strings.TrimSpace(decision.Key) == "" {
|
||||
return contracts.LogicPlayerResponse{}, false, nil
|
||||
}
|
||||
var cacheErr error
|
||||
if s.responses != nil {
|
||||
response, ok, err := s.responses.Get(ctx, decision.Key)
|
||||
if ok {
|
||||
return replayIdemResp(packet, response), true, nil
|
||||
}
|
||||
cacheErr = err
|
||||
}
|
||||
// response cache 只是热缓存;miss 或故障时从幂等权威后端读取同事务提交的结果。
|
||||
if s.idempotency == nil {
|
||||
return contracts.LogicPlayerResponse{}, false, cacheErr
|
||||
}
|
||||
payload, ok, resultErr := s.idempotency.LookupResult(ctx, decision)
|
||||
if resultErr != nil || !ok {
|
||||
return contracts.LogicPlayerResponse{}, false, errors.Join(cacheErr, resultErr)
|
||||
}
|
||||
var response contracts.LogicPlayerResponse
|
||||
if err := json.Unmarshal(payload, &response); err != nil {
|
||||
return contracts.LogicPlayerResponse{}, false, fmt.Errorf("decode idempotency result: %w", err)
|
||||
}
|
||||
if s.responses != nil {
|
||||
// 权威结果读取成功后尽力回填热缓存;回填失败不能让已提交结果再次变成不可用。
|
||||
_ = s.responses.Store(ctx, decision.Key, response)
|
||||
}
|
||||
return replayIdemResp(packet, response), true, nil
|
||||
}
|
||||
|
||||
func replayIdemResp(packet contracts.GatewayClientPacket, response contracts.LogicPlayerResponse) contracts.LogicPlayerResponse {
|
||||
// 重放响应只复用业务结果,目标 gateway/session/sequence 必须按本次包重写,避免旧连接收到新响应。
|
||||
response.TargetGatewayNodeID = packet.GatewayNodeID
|
||||
response.SessionID = packet.SessionID
|
||||
response.AccountID = packet.AccountID
|
||||
response.Sequence = packet.Sequence
|
||||
response.WireFormat = packet.WireFormat
|
||||
response.Metadata = replayIdemMetadata(response.Metadata, packet.Metadata)
|
||||
return response
|
||||
}
|
||||
|
||||
func replayIdemMetadata(stored, current map[string]string) map[string]string {
|
||||
// handler 产生的响应 metadata 属于可重放结果;只有链路跟踪字段应跟随本次请求。
|
||||
metadata := protocol.CloneFrameMetadata(stored)
|
||||
current = protocol.CloneFrameMetadata(current)
|
||||
for _, key := range []string{
|
||||
protocol.FrameMetadataTraceParent,
|
||||
protocol.FrameMetadataTraceState,
|
||||
protocol.FrameMetadataBaggage,
|
||||
} {
|
||||
delete(metadata, key)
|
||||
if value := current[key]; value != "" {
|
||||
if metadata == nil {
|
||||
metadata = make(map[string]string, 3)
|
||||
}
|
||||
metadata[key] = value
|
||||
}
|
||||
}
|
||||
if len(metadata) == 0 {
|
||||
return nil
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
func encodeIdemResp(response contracts.LogicPlayerResponse) ([]byte, error) {
|
||||
payload, err := json.Marshal(clonePlayerResp(response))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode idempotency result: %w", err)
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (s *Service) storeIdemResp(ctx context.Context, decision idempotency.Decision, response contracts.LogicPlayerResponse) error {
|
||||
if s == nil || s.responses == nil || strings.TrimSpace(decision.Key) == "" {
|
||||
return nil
|
||||
}
|
||||
return s.responses.Store(ctx, decision.Key, response)
|
||||
}
|
||||
|
||||
func (s *Service) shouldProtectPacket(meta dispatch.HandlerMeta) bool {
|
||||
return meta.Idempotent || meta.PlayerScoped || meta.Stateful || strings.TrimSpace(meta.ProfileOperation) != ""
|
||||
}
|
||||
|
||||
func (s *Service) reservePktIdem(ctx context.Context, packet contracts.GatewayClientPacket) (idempotency.Reservation, idempotency.Decision, error) {
|
||||
if s.idempotency == nil {
|
||||
return idempotency.Reservation{}, idempotency.Decision{Status: idempotency.StatusAccepted}, nil
|
||||
}
|
||||
return s.idempotency.Begin(ctx, packetIdemRequest(packet))
|
||||
}
|
||||
|
||||
func packetIdemRequest(packet contracts.GatewayClientPacket) idempotency.Request {
|
||||
scope := "logic:msg:" + strconv.FormatUint(uint64(packet.MsgID), 10)
|
||||
accountID := strings.ToLower(strings.TrimSpace(packet.AccountID))
|
||||
sessionID := strings.ToLower(strings.TrimSpace(packet.SessionID))
|
||||
rawKey := strings.TrimSpace(packet.IdempotencyKey)
|
||||
clientRequestID := strings.TrimSpace(packet.ClientRequestID)
|
||||
key := ""
|
||||
if clientRequestID != "" {
|
||||
// 稳定的客户端请求 ID 是跨重连业务身份;session 和 sequence 只是当前传输属性。
|
||||
key = idempotency.CanonicalKey("logic-packet-client-request-v1", scope, accountID, clientRequestID)
|
||||
} else if rawKey != "" {
|
||||
// 旧 gateway 的显式 key 未包含 session;logic 必须再做会话隔离,避免重连后 sequence 重置命中旧结果。
|
||||
key = idempotency.CanonicalKey("logic-packet-explicit-v1", scope, accountID, sessionID, rawKey)
|
||||
} else if packet.Sequence > 0 {
|
||||
key = idempotency.CanonicalKey(
|
||||
"logic-packet-fallback-v1",
|
||||
scope,
|
||||
accountID,
|
||||
sessionID,
|
||||
strconv.FormatInt(int64(packet.PacketID), 10),
|
||||
strconv.FormatUint(uint64(packet.MsgID), 10),
|
||||
strconv.FormatUint(packet.Sequence, 10),
|
||||
)
|
||||
}
|
||||
return idempotency.Request{
|
||||
Scope: scope,
|
||||
Subject: accountID,
|
||||
Session: sessionID,
|
||||
Key: key,
|
||||
Fingerprint: idempotency.CanonicalKey(
|
||||
"logic-packet-fingerprint-v1",
|
||||
strconv.FormatInt(int64(packet.PacketID), 10),
|
||||
strconv.FormatUint(uint64(packet.MsgID), 10),
|
||||
strconv.FormatBool(packet.Compressed),
|
||||
string(packet.Body),
|
||||
),
|
||||
Sequence: packet.Sequence,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user