spark-server/internal/pkg/reqctx/reqctx.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

95 lines
2.1 KiB
Go

package reqctx
import (
"context"
"crypto/rand"
"encoding/hex"
"net"
"strings"
"time"
"spark-server/internal/pkg/i18n"
)
type ctxKey struct{}
// RequestContext is the per-request identity. Use RealIP for rate limit and audit, never 127.0.0.1 from gnet.RemoteAddr.
type RequestContext struct {
TraceID string
RequestID string
UserID int64
DeviceID uint64
Language string
AppVersion string
ConnectionID string
RemoteAddr string
RealIP string
JTI string
TokenExpires time.Time
}
func With(ctx context.Context, rc *RequestContext) context.Context {
return context.WithValue(ctx, ctxKey{}, rc)
}
func From(ctx context.Context) *RequestContext {
rc, _ := ctx.Value(ctxKey{}).(*RequestContext)
return rc
}
// RealIP returns the client IP from context (PROXY / X-Real-IP), not gnet RemoteAddr.
func RealIP(ctx context.Context) string {
if rc := From(ctx); rc != nil {
return rc.RealIP
}
return ""
}
// Lang is the canonical UI language (zh-CN / en / zh-TW) for errors and notifications.
func Lang(ctx context.Context) i18n.Lang {
if rc := From(ctx); rc != nil {
return i18n.Parse(rc.Language)
}
return i18n.Default
}
// Language returns the canonical language tag stored on context.
func Language(ctx context.Context) string {
return Lang(ctx).String()
}
// ParseHTTPRealIP prefers X-Real-IP, then X-Forwarded-For, then RemoteAddr host.
func ParseHTTPRealIP(realIPHeader, forwardedFor, remoteAddr string) string {
if ip := strings.TrimSpace(realIPHeader); ip != "" {
return stripHost(ip)
}
if forwardedFor != "" {
first, _, _ := strings.Cut(forwardedFor, ",")
if ip := stripHost(strings.TrimSpace(first)); ip != "" {
return ip
}
}
return stripHost(remoteAddr)
}
func stripHost(v string) string {
v = strings.TrimSpace(v)
if v == "" {
return ""
}
host, _, err := net.SplitHostPort(v)
if err == nil {
return host
}
return v
}
// NewID returns a 16-byte hex id for trace_id / request_id.
func NewID() string {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return "00000000000000000000000000000000"
}
return hex.EncodeToString(b[:])
}