Documentation
¶
Overview ¶
* @Author: kamalyes 501893067@qq.com * @Date: 2025-12-20 00:00:00 * @LastEditors: kamalyes 501893067@qq.com * @LastEditTime: 2025-12-20 15:55:00 * @FilePath: \go-toolbox\pkg\metadata\accessor.go * @Description: 元数据访问器 * * Copyright (c) 2025 by kamalyes, All Rights Reserved.
* @Author: kamalyes 501893067@qq.com * @Date: 2025-12-20 00:00:00 * @LastEditors: kamalyes 501893067@qq.com * @LastEditTime: 2025-12-20 15:55:00 * @FilePath: \go-toolbox\pkg\metadata\converter.go * @Description: 元数据转换工具 * * Copyright (c) 2025 by kamalyes, All Rights Reserved.
* @Author: kamalyes 501893067@qq.com * @Date: 2025-12-20 00:00:00 * @LastEditors: kamalyes 501893067@qq.com * @LastEditTime: 2025-12-20 16:50:00 * @FilePath: \go-toolbox\pkg\metadata\extractor.go * @Description: HTTP 请求元数据提取器 * * Copyright (c) 2025 by kamalyes, All Rights Reserved.
* @Author: kamalyes 501893067@qq.com * @Date: 2026-01-09 00:00:00 * @LastEditors: kamalyes 501893067@qq.com * @LastEditTime: 2026-01-16 12:01:15 * @FilePath: \go-toolbox\pkg\metadata\language.go * @Description: 语言信息提取和处理 * * Copyright (c) 2026 by kamalyes, All Rights Reserved.
* @Author: kamalyes 501893067@qq.com * @Date: 2025-12-20 00:00:00 * @LastEditors: kamalyes 501893067@qq.com * @LastEditTime: 2025-12-20 15:55:00 * @FilePath: \go-toolbox\pkg\metadata\types.go * @Description: HTTP 请求元数据结构定义 * * Copyright (c) 2025 by kamalyes, All Rights Reserved.
* @Author: kamalyes 501893067@qq.com * @Date: 2025-12-20 00:00:00 * @LastEditors: kamalyes 501893067@qq.com * @LastEditTime: 2025-12-20 16:05:00 * @FilePath: \go-toolbox\pkg\metadata\utils.go * @Description: 元数据工具函数 * * Copyright (c) 2025 by kamalyes, All Rights Reserved.
Index ¶
- func ExtractLanguage(r *http.Request) string
- func ExtractLanguageWithDefault(r *http.Request, defaultLang string) string
- func FromAcceptLanguageSource(ctx context.Context, r *http.Request, key string) string
- func FromContextSource(ctx context.Context, r *http.Request, key string) string
- func FromCookieSource(ctx context.Context, r *http.Request, key string) string
- func FromHeaderSource(ctx context.Context, r *http.Request, key string) string
- func FromQuerySource(ctx context.Context, r *http.Request, key string) string
- func GetRemoteIP(remoteAddr string) string
- func GetRemotePort(remoteAddr string) string
- func GetTLSVersionString(version uint16) string
- func NormalizeLanguage(lang string) string
- func ParseAcceptLanguage(acceptLang string) (language, region, fullTag string)
- type ContextKey
- type LanguageExtractor
- type MetadataExtractor
- func (me *MetadataExtractor) Default(val string) *MetadataExtractor
- func (me *MetadataExtractor) FromContext(key ContextKey) *MetadataExtractor
- func (me *MetadataExtractor) FromCookie(key string) *MetadataExtractor
- func (me *MetadataExtractor) FromHeader(key string) *MetadataExtractor
- func (me *MetadataExtractor) FromQuery(key string) *MetadataExtractor
- func (me *MetadataExtractor) Get() string
- func (me *MetadataExtractor) WithProcess(process ProcessFunc) *MetadataExtractor
- type ProcessFunc
- type RequestMetadata
- type SourceFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ExtractLanguage ¶
ExtractLanguage 快捷函数:使用默认配置提取语言 优先级:Query(lang/language) → Header(X-Language) → Cookie(language) → Accept-Language → "en"
func ExtractLanguageWithDefault ¶
ExtractLanguageWithDefault 快捷函数:使用指定默认语言提取
func FromAcceptLanguageSource ¶
FromAcceptLanguageSource 从 Accept-Language header 中提取语言 例如: "zh-CN,zh;q=0.9,en;q=0.8" -> "zh-CN"
func FromContextSource ¶
FromContextSource 从 context 中提取字符串值
func FromCookieSource ¶
FromCookieSource 从 HTTP cookie 中提取值
func FromHeaderSource ¶
FromHeaderSource 从 HTTP header 中提取值
func FromQuerySource ¶
FromQuerySource 从 URL query 参数中提取值
直接扫原始查询串,不走 r.URL.Query()
r.URL.Query() 会把整个查询串解析成一张 url.Values(map[string][]string), 每个键一次调用就建一张 map而取语言要查 lang 与 language 两个键,于是同一个 查询串每次请求要解析两遍、建两张 map 这里按 url.ParseQuery 的规则线性扫一遍,首个匹配即返回,不分配 map
语义与 url.Values.Get 对齐:按 & 切分、按首个 = 切分、键与值都要经过 QueryUnescape、解码失败的那一对跳过(ParseQuery 会记录错误但不收录该对)、 多个同名键取第一个这几条由 TestQueryGetMatchesStdlib 差分验证
[EN] Scans the raw query string instead of going through r.URL.Query(), which parses the whole string into a url.Values map — one map per call, and looking up a language checks both lang and language, so the same query string was parsed twice per request. This walks it once by url.ParseQuery's rules and returns the first match without allocating a map. The semantics match url.Values.Get — split on &, split on the first =, unescape both key and value, skip a pair that fails to unescape as ParseQuery does, and take the first of several same-named keys — all verified differentially by TestQueryGetMatchesStdlib
func GetRemoteIP ¶
GetRemoteIP 从 RemoteAddr 中提取 IP 地址(去除端口)
func GetTLSVersionString ¶
GetTLSVersionString 将 TLS 版本号转换为可读字符串
func NormalizeLanguage ¶
NormalizeLanguage 标准化语言代码 例如: "zh-cn" -> "zh-CN", "zh_CN" -> "zh-CN", "EN" -> "en", "zh-hans-cn" -> "zh-Hans-CN"
func ParseAcceptLanguage ¶
ParseAcceptLanguage 解析 Accept-Language 头获取主要语言和地区代码 返回: 语言代码(如 "zh"), 地区代码(如 "CN"), 完整标签(如 "zh-CN")
Types ¶
type ContextKey ¶
type ContextKey string
func (ContextKey) String ¶
func (ck ContextKey) String() string
type LanguageExtractor ¶
type LanguageExtractor struct {
DefaultLanguage string
QueryKeys []string // Query 参数的键名,如 ["lang", "language"]
HeaderKeys []string // Header 的键名,如 ["X-Language"]
CookieKey string // Cookie 的键名
UseAcceptLang bool // 是否使用 Accept-Language header
Normalize bool // 是否标准化语言代码
}
LanguageExtractor 语言提取器配置
func NewLanguageExtractor ¶
func NewLanguageExtractor(defaultLang string) *LanguageExtractor
NewLanguageExtractor 创建默认的语言提取器
func (*LanguageExtractor) Extract ¶
func (le *LanguageExtractor) Extract(r *http.Request) string
Extract 从请求中提取语言信息 优先级:Query → Header → Cookie → Accept-Language → 默认值
func (*LanguageExtractor) ExtractWithContext ¶
func (le *LanguageExtractor) ExtractWithContext(r *http.Request, contextKey ContextKey) (*http.Request, string)
ExtractWithContext 从请求中提取语言信息并存入 context
type MetadataExtractor ¶
type MetadataExtractor struct {
// contains filtered or unexported fields
}
func NewMetadataExtractor ¶
func NewMetadataExtractor(ctx context.Context, r *http.Request) *MetadataExtractor
NewMetadataExtractor 创建一个元数据提取器,从 context 和 request 中提取值
func NewMetadataExtractorFromRequest ¶
func NewMetadataExtractorFromRequest(r *http.Request) *MetadataExtractor
NewMetadataExtractorFromRequest 从 http.Request 创建元数据提取器,自动使用 request 的 context
func (*MetadataExtractor) Default ¶
func (me *MetadataExtractor) Default(val string) *MetadataExtractor
Default 设置默认值,当所有来源均未提取到值时返回该默认值
func (*MetadataExtractor) FromContext ¶
func (me *MetadataExtractor) FromContext(key ContextKey) *MetadataExtractor
FromContext 从 context 中提取值
func (*MetadataExtractor) FromCookie ¶
func (me *MetadataExtractor) FromCookie(key string) *MetadataExtractor
FromCookie 从 HTTP cookie 中提取值
func (*MetadataExtractor) FromHeader ¶
func (me *MetadataExtractor) FromHeader(key string) *MetadataExtractor
FromHeader 从 HTTP header 中提取值
func (*MetadataExtractor) FromQuery ¶
func (me *MetadataExtractor) FromQuery(key string) *MetadataExtractor
FromQuery 从 URL query 参数中提取值
func (*MetadataExtractor) WithProcess ¶
func (me *MetadataExtractor) WithProcess(process ProcessFunc) *MetadataExtractor
WithProcess 为最后添加的来源设置处理函数
type ProcessFunc ¶
type RequestMetadata ¶
type RequestMetadata struct {
// 基础请求信息
UserAgent string `json:"user_agent"`
ClientIP string `json:"client_ip"`
RemoteAddr string `json:"remote_addr"`
RequestURI string `json:"request_uri"`
QueryString string `json:"query_string"`
RequestMethod string `json:"request_method"`
RequestHost string `json:"request_host"`
// 来源信息
Origin string `json:"origin"`
Referer string `json:"referer"`
// 代理和转发信息
XForwardedFor string `json:"x_forwarded_for"`
XRealIP string `json:"x_real_ip"`
XForwardedProto string `json:"x_forwarded_proto"`
XForwardedHost string `json:"x_forwarded_host"`
XForwardedPort string `json:"x_forwarded_port"`
// 客户端偏好信息
AcceptLanguage string `json:"accept_language"`
AcceptEncoding string `json:"accept_encoding"`
Accept string `json:"accept"`
// WebSocket 协议信息
SecWebSocketKey string `json:"sec_websocket_key"`
SecWebSocketVersion string `json:"sec_websocket_version"`
SecWebSocketProtocol string `json:"sec_websocket_protocol"`
SecWebSocketExtensions string `json:"sec_websocket_extensions"`
// 连接信息
Connection string `json:"connection"`
Upgrade string `json:"upgrade"`
// CDN 和安全信息
CFRay string `json:"cf_ray"`
CFConnectingIP string `json:"cf_connecting_ip"`
CFIPCountry string `json:"cf_ipcountry"`
XRequestID string `json:"x_request_id"`
XCorrelationID string `json:"x_correlation_id"`
XDeviceID string `json:"x_device_id"` // 设备唯一标识
// 缓存和条件请求
CacheControl string `json:"cache_control"`
IfNoneMatch string `json:"if_none_match"`
IfModifiedSince string `json:"if_modified_since"`
// 客户端提示信息(Client Hints)
SecCHUA string `json:"sec_ch_ua"`
SecCHUAMobile string `json:"sec_ch_ua_mobile"`
SecCHUAPlatform string `json:"sec_ch_ua_platform"`
DNT string `json:"dnt"`
// TLS 信息
Protocol string `json:"protocol"`
TLSVersion uint16 `json:"tls_version,omitempty"`
TLSCipherSuite uint16 `json:"tls_cipher_suite,omitempty"`
TLSServerName string `json:"tls_server_name,omitempty"`
// User-Agent 解析结果(来自 useragent.Parse)
Browser string `json:"browser,omitempty"` // 浏览器名称: Chrome, Firefox, Safari
BrowserVersion string `json:"browser_version,omitempty"` // 浏览器版本号
OS string `json:"os,omitempty"` // 操作系统: Windows, macOS, Android, iOS
OSVersion string `json:"os_version,omitempty"` // 操作系统版本号
Device string `json:"device,omitempty"` // 设备名称
DeviceType string `json:"device_type,omitempty"` // 设备类型: mobile/tablet/desktop/bot
DeviceVendor string `json:"device_vendor,omitempty"` // 设备厂商: Apple, Samsung, Huawei
IsBot bool `json:"is_bot"` // 是否为爬虫/机器人
BotName string `json:"bot_name,omitempty"` // 爬虫名称
IsMobile bool `json:"is_mobile"` // 是否为移动设备
IsTablet bool `json:"is_tablet"` // 是否为平板设备
}
RequestMetadata HTTP 请求元数据
func ExtractRequestMetadata ¶
func ExtractRequestMetadata(r *http.Request) *RequestMetadata
ExtractRequestMetadata 从 HTTP 请求中提取所有元数据 头部键名一律写成 Go 的规范形式
http.Header.Get 会先把键名过一遍 textproto.CanonicalMIMEHeaderKey,而该函数的 规范形式是「按连字符分段、每段首字母大写其余小写」所以 X-Request-ID 并不规范, 规范形式是 X-Request-Id;CF-Ray 要写成 Cf-Ray,Sec-WebSocket-Key 要写成 Sec-Websocket-Key非规范的键名会让 Get 每次都分配一个规范化后的字符串副本
两种写法取到的值完全相同(Get 两边都会规范化),差的只是开销实测:
Get("X-Request-ID") 45.0ns / 16B / 1 次分配
Get("X-Request-Id") 15.7ns / 0B / 0 次
本函数要读三十多个头部,原先有十五个键名不规范,于是每次请求白付十五次分配
Cf-Ipcountry 这类写法看着像拼错,其实是规范形式,不要「修正」回去 ¶
[EN] Header keys are written in Go's canonical form throughout. http.Header.Get passes the key through textproto.CanonicalMIMEHeaderKey, whose canonical form upper-cases the first letter of each dash-separated token and lower-cases the rest, so X-Request-ID is not canonical — X-Request-Id is — and CF-Ray must be written Cf-Ray. A non-canonical key makes Get allocate a canonicalised copy on every call. Both spellings read the same value since Get canonicalises either way; only the cost differs, measured at 45.0ns with one allocation against 15.7ns with none. This function reads over thirty headers and fifteen of its keys were non-canonical, so every request paid fifteen needless allocations. Spellings like Cf-Ipcountry look like typos but are the canonical form and should not be "corrected" back
func FromMap ¶
func FromMap(data map[string]interface{}) *RequestMetadata
FromMap 从 map[string]interface{} 创建 RequestMetadata(使用反射自动填充)
func (*RequestMetadata) GetHeader ¶
func (m *RequestMetadata) GetHeader(key string) string
GetHeader 从 RequestMetadata 中获取指定的头信息
func (*RequestMetadata) SetHeader ¶
func (m *RequestMetadata) SetHeader(key, value string)
SetHeader 设置 RequestMetadata 中指定的头信息
func (*RequestMetadata) ToMap ¶
func (m *RequestMetadata) ToMap() map[string]interface{}
ToMap 将 RequestMetadata 转换为 map[string]interface{}