服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
205 lines
6.6 KiB
Go
205 lines
6.6 KiB
Go
package rpc
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
"sync/atomic"
|
||
|
||
"longheng.io/server/pkg/contracts"
|
||
)
|
||
|
||
// RequireMode 描述对端没有签名时是否允许通过;签名"错误"始终拒绝。
|
||
//
|
||
// RequireAll 缺签名或签名错误都拒绝。生产推荐。
|
||
// RequirePresent 缺签名放行(灰度),签名错误仍拒绝。仅用于"双方还没全部接入鉴权"
|
||
// 的滚动升级窗口。完成滚动后应立刻切到 RequireAll。
|
||
type RequireMode string
|
||
|
||
const (
|
||
// RequireAll 表示缺签名和签名错误都拒绝,是生产推荐模式。
|
||
RequireAll RequireMode = "all"
|
||
// RequirePresent 表示缺签名暂时放行,但签名错误仍拒绝。
|
||
RequirePresent RequireMode = "present"
|
||
)
|
||
|
||
// AuthTransportMetrics 是 AuthenticatedTransport 的指标钩子。所有字段都是
|
||
// 64-bit counter,调用方可以原子读取作为 Prometheus / scope 输出。
|
||
// 调用方可以 nil,AuthenticatedTransport 自己持有一个匿名实例做内部统计。
|
||
type AuthTransportMetrics struct {
|
||
SignedRequests atomic.Uint64
|
||
SignedResponses atomic.Uint64
|
||
AcceptedRequests atomic.Uint64
|
||
AcceptedResponses atomic.Uint64
|
||
RejectedMissing atomic.Uint64
|
||
RejectedInvalid atomic.Uint64
|
||
RejectedSkew atomic.Uint64
|
||
RejectedAlgorithm atomic.Uint64
|
||
RejectedReplay atomic.Uint64
|
||
RejectedReplayCheck atomic.Uint64
|
||
GraceLetThrough atomic.Uint64
|
||
SignFailures atomic.Uint64
|
||
}
|
||
|
||
// AuthenticatedTransport 包装任意 Transport,发送侧自动签名,接收侧自动验签。
|
||
// 不引入新的拓扑或网络层,对调用方完全透明。
|
||
type AuthenticatedTransport struct {
|
||
inner Transport
|
||
auth *Authenticator
|
||
require RequireMode
|
||
metrics *AuthTransportMetrics
|
||
}
|
||
|
||
// AuthTransportOptions 是装配参数。Auth 必填;Inner 通常是 BusTransport
|
||
// 或 GRPCTransport。Require 缺省 RequireAll。
|
||
type AuthTransportOptions struct {
|
||
Inner Transport
|
||
Auth *Authenticator
|
||
Require RequireMode
|
||
Metrics *AuthTransportMetrics
|
||
}
|
||
|
||
// NewAuthenticatedTransport 构造一个签名 / 验签包装。inner 或 auth 为 nil 返回错误,
|
||
// 拒绝静默退化为"不鉴权"。
|
||
func NewAuthenticatedTransport(opts AuthTransportOptions) (*AuthenticatedTransport, error) {
|
||
if opts.Inner == nil {
|
||
return nil, errors.New("rpcauth: inner transport is required")
|
||
}
|
||
if opts.Auth == nil {
|
||
return nil, errors.New("rpcauth: authenticator is required")
|
||
}
|
||
require := opts.Require
|
||
switch require {
|
||
case "":
|
||
require = RequireAll
|
||
case RequireAll, RequirePresent:
|
||
default:
|
||
return nil, fmt.Errorf("rpcauth: unsupported require mode %q", require)
|
||
}
|
||
metrics := opts.Metrics
|
||
if metrics == nil {
|
||
metrics = &AuthTransportMetrics{}
|
||
}
|
||
return &AuthenticatedTransport{
|
||
inner: opts.Inner,
|
||
auth: opts.Auth,
|
||
require: require,
|
||
metrics: metrics,
|
||
}, nil
|
||
}
|
||
|
||
// Metrics 暴露内部计数器,便于 wiring 层挂到 metrics.Scope 上。
|
||
func (t *AuthenticatedTransport) Metrics() *AuthTransportMetrics {
|
||
if t == nil {
|
||
return nil
|
||
}
|
||
return t.metrics
|
||
}
|
||
|
||
// Require 暴露当前模式,主要用于自检 / 日志。
|
||
func (t *AuthenticatedTransport) Require() RequireMode {
|
||
if t == nil {
|
||
return ""
|
||
}
|
||
return t.require
|
||
}
|
||
|
||
// SubscribeRequests 订阅请求并在交给业务 handler 前完成验签。
|
||
func (t *AuthenticatedTransport) SubscribeRequests(ctx context.Context, nodeID string, handler RequestHandler) (TransportSubscription, error) {
|
||
if t == nil {
|
||
return nil, errors.New("rpcauth: transport is nil")
|
||
}
|
||
return t.inner.SubscribeRequests(ctx, nodeID, func(ctx context.Context, req contracts.RPCRequest) error {
|
||
if err := t.auth.VerifyRequestContext(ctx, req); err != nil {
|
||
t.recordVerifyFailure(err)
|
||
if t.shouldAcceptDespite(err) {
|
||
return handler(ctx, req)
|
||
}
|
||
return err
|
||
}
|
||
t.metrics.AcceptedRequests.Add(1)
|
||
return handler(ctx, req)
|
||
})
|
||
}
|
||
|
||
// SubscribeResponses 订阅响应并在交给等待方前完成验签。
|
||
func (t *AuthenticatedTransport) SubscribeResponses(ctx context.Context, nodeID string, handler ResponseHandler) (TransportSubscription, error) {
|
||
if t == nil {
|
||
return nil, errors.New("rpcauth: transport is nil")
|
||
}
|
||
return t.inner.SubscribeResponses(ctx, nodeID, func(ctx context.Context, resp contracts.RPCResponse) error {
|
||
if err := t.auth.VerifyResponseContext(ctx, resp); err != nil {
|
||
t.recordVerifyFailure(err)
|
||
if t.shouldAcceptDespite(err) {
|
||
return handler(ctx, resp)
|
||
}
|
||
return err
|
||
}
|
||
t.metrics.AcceptedResponses.Add(1)
|
||
return handler(ctx, resp)
|
||
})
|
||
}
|
||
|
||
// PublishRequest 对请求签名后交给底层 transport 发布。
|
||
func (t *AuthenticatedTransport) PublishRequest(ctx context.Context, targetNodeID string, request contracts.RPCRequest) error {
|
||
if t == nil {
|
||
return errors.New("rpcauth: transport is nil")
|
||
}
|
||
if err := t.auth.SignRequest(&request); err != nil {
|
||
t.metrics.SignFailures.Add(1)
|
||
return err
|
||
}
|
||
t.metrics.SignedRequests.Add(1)
|
||
return t.inner.PublishRequest(ctx, targetNodeID, request)
|
||
}
|
||
|
||
// PublishResponse 对响应签名后交给底层 transport 发布。
|
||
func (t *AuthenticatedTransport) PublishResponse(ctx context.Context, targetNodeID string, response contracts.RPCResponse) error {
|
||
if t == nil {
|
||
return errors.New("rpcauth: transport is nil")
|
||
}
|
||
if err := t.auth.SignResponse(&response); err != nil {
|
||
t.metrics.SignFailures.Add(1)
|
||
return err
|
||
}
|
||
t.metrics.SignedResponses.Add(1)
|
||
return t.inner.PublishResponse(ctx, targetNodeID, response)
|
||
}
|
||
|
||
// shouldAcceptDespite 区分"可灰度通过"和"必须拒绝"的失败。
|
||
// 只有 ErrAuthSignatureMissing 在 RequirePresent 模式下允许放行;
|
||
// invalid / skew / algorithm 一律拒绝,无论什么模式。
|
||
func (t *AuthenticatedTransport) shouldAcceptDespite(err error) bool {
|
||
if t.require != RequirePresent {
|
||
return false
|
||
}
|
||
if !errors.Is(err, ErrAuthSignatureMissing) {
|
||
return false
|
||
}
|
||
t.metrics.GraceLetThrough.Add(1)
|
||
return true
|
||
}
|
||
|
||
func (t *AuthenticatedTransport) recordVerifyFailure(err error) {
|
||
switch {
|
||
case errors.Is(err, ErrAuthSignatureMissing):
|
||
t.metrics.RejectedMissing.Add(1)
|
||
case errors.Is(err, ErrAuthSignatureInvalid):
|
||
t.metrics.RejectedInvalid.Add(1)
|
||
case errors.Is(err, ErrAuthTimestampSkew):
|
||
t.metrics.RejectedSkew.Add(1)
|
||
case errors.Is(err, ErrAuthAlgUnsup):
|
||
t.metrics.RejectedAlgorithm.Add(1)
|
||
case errors.Is(err, ErrAuthReplayDetected):
|
||
t.metrics.RejectedReplay.Add(1)
|
||
case errors.Is(err, ErrAuthReplayCheckFailed):
|
||
t.metrics.RejectedReplayCheck.Add(1)
|
||
default:
|
||
// 未知错误不分类,但仍然计入 invalid,避免悄悄漏统计。
|
||
t.metrics.RejectedInvalid.Add(1)
|
||
// 兜底,让调用方在日志里能看到具体类型。
|
||
_ = strings.TrimSpace(err.Error())
|
||
}
|
||
}
|