fix(service): 对齐新版服务发现与稳定依赖链(by AI)

This commit is contained in:
Star 2026-07-18 18:45:24 +08:00
parent e8369d4680
commit ba1a909d49
8 changed files with 100 additions and 76 deletions

View File

@ -1,5 +1,11 @@
# CHANGELOG - go/service
## v1.5.21 (2026-07-18)
- **依赖校验修复**: 升级 `discover``v1.5.7``redis``v1.5.11`,并使用稳定版 `redigo v1.9.3`
- **依赖对齐**: 升级 `cast``v1.5.4``crypto``v1.5.4``encoding``v1.5.5``starter``v1.5.6`
- **Discover API 对齐**: 服务注册与代理调用改用新版包级 Discover API调用配置转换为统一的协议、超时和 Token 字符串格式。
- **Header 命名对齐**: 内部引用统一采用 `RequestId``SessionId``DeviceId``UserId``ClientIp` Go 命名。
## v1.5.20 (2026-06-22)
- **Client Key 应答头条件化**:
- `Device-Id` / `Session-Id` 仅当请求头未携带时才写入应答头,避免客户端已持有 ID 时重复返回。

12
TEST.md
View File

@ -1,10 +1,14 @@
# Service Module Test Report
## v1.5.21 验证
- `go list -m all`:模块图使用 `discover v1.5.7``redis v1.5.11` 与稳定版 `redigo v1.9.3`
- `go test -v ./...``go test -bench=. ./...` 全部通过。
## 性能测试 (Benchmark)
- 测试日期: 2026-06-22
- 版本: v1.5.20
- 指标: `BenchmarkRouting`: **5394 ns/op**
- 环境: Darwin / Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
- 测试日期: 2026-07-18
- 版本: v1.5.21
- 指标: `BenchmarkRouting`: **2382 ns/op**
- 环境: Darwin / Apple M3 Max
## 单元测试覆盖 (Unit Test)
- [x] `TestServeHTTP`: 基础请求与响应

14
go.mod
View File

@ -3,17 +3,17 @@ module apigo.cc/go/service
go 1.25.0
require (
apigo.cc/go/cast v1.5.3
apigo.cc/go/cast v1.5.4
apigo.cc/go/config v1.5.3
apigo.cc/go/discover v1.5.3
apigo.cc/go/discover v1.5.7
apigo.cc/go/file v1.5.5
apigo.cc/go/http v1.5.3
apigo.cc/go/id v1.5.6
apigo.cc/go/jsmod v1.5.3
apigo.cc/go/log v1.5.8
apigo.cc/go/redis v1.5.10
apigo.cc/go/redis v1.5.11
apigo.cc/go/safe v1.5.2
apigo.cc/go/starter v1.5.5
apigo.cc/go/starter v1.5.6
apigo.cc/go/timer v1.5.0
apigo.cc/go/watch v1.5.2
github.com/gorilla/websocket v1.5.3
@ -21,13 +21,13 @@ require (
)
require (
apigo.cc/go/crypto v1.5.3 // indirect
apigo.cc/go/encoding v1.5.4 // indirect
apigo.cc/go/crypto v1.5.4 // indirect
apigo.cc/go/encoding v1.5.5 // indirect
apigo.cc/go/rand v1.5.3 // indirect
apigo.cc/go/shell v1.5.4 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/gomodule/redigo v1.9.3 // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect

38
go.sum
View File

@ -1,51 +1,57 @@
apigo.cc/go/cast v1.5.3 h1:jk6VX0rGFhjKtfPhsaV6IKYpiGmORRk9qPTtuNS53tw=
apigo.cc/go/cast v1.5.3/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4=
apigo.cc/go/cast v1.5.4 h1:OH5Vkrc1K19wuazf9VecjwwMwKDFgLQkI3UvwB4umcs=
apigo.cc/go/cast v1.5.4/go.mod h1:GMjjrYn93tWat1U409G7h1jR3ejfLLI7r0efBo9Sbd4=
apigo.cc/go/config v1.5.3 h1:peq1FM2xO+vzPHJf8Dwg3DXm8PtFQMfTFKQj6fpoG7A=
apigo.cc/go/config v1.5.3/go.mod h1:ZiOAjWa1mQIzszaJZN+kO6YU4GXreng+NxkcK/TAkqQ=
apigo.cc/go/crypto v1.5.3 h1:2JUHC2cgR2zrnn36EzwkUAdxmmTXAA/8yTNo+2X1mPE=
apigo.cc/go/crypto v1.5.3/go.mod h1:PheYKHEXmoEFI1AK5PpY1borQWcRlkkSaWncT3cWbhE=
apigo.cc/go/discover v1.5.3 h1:WW1A7qReYytebETDb5MhVRPmT10KReGM4QbPTfqS/iw=
apigo.cc/go/discover v1.5.3/go.mod h1:rylFiGvfNro090rGtlc9K31KATn6WZbnn1Fk2AbQZEU=
apigo.cc/go/encoding v1.5.4 h1:Fk8TrveZATyy8SHukC4ZiqdTSp+QIfsRHtt55xmMK7w=
apigo.cc/go/encoding v1.5.4/go.mod h1:dShEsZ3gKqBINz7TSOYf4e7/fBCqCY9VzlenoGUQUFM=
apigo.cc/go/crypto v1.5.4 h1:7rFcF9Mns+7BIB13lystvLVsAeYapKsrD26KOrQq2Zk=
apigo.cc/go/crypto v1.5.4/go.mod h1:PheYKHEXmoEFI1AK5PpY1borQWcRlkkSaWncT3cWbhE=
apigo.cc/go/discover v1.5.7 h1:wb2hXr+6mGxZ+yjOuTNzeLSZ8tNaPvn+dl1AuedPRm0=
apigo.cc/go/discover v1.5.7/go.mod h1:Vg41wYA5+M7t883imlQEOwdeUPZPoNrzDWVshum7KYo=
apigo.cc/go/encoding v1.5.5 h1:kduNLWQgtcQqHYobOuu1djbgg8LedkGOe8f18ZMfqzs=
apigo.cc/go/encoding v1.5.5/go.mod h1:Big9q1Zwy4071dXtnrQ3SJDzfa/G7/A60KE/5+M//P8=
apigo.cc/go/file v1.5.5 h1:/+HmDumLu6Qk2KuQL63M9lpgzHTDL+QJ8dStOl7e9gs=
apigo.cc/go/file v1.5.5/go.mod h1:xRVNhctvqOKeBemmcRW/BQfgkc3B+vT/UZVdSc7duUo=
apigo.cc/go/http v1.5.3 h1:nvJh9bqPPcPRv6p8WEw7bJAd0UC+r2zvQA8/QioVLTQ=
apigo.cc/go/http v1.5.3/go.mod h1:cFrPK61y9f1PrsNSJscZT/QVOgkT15o9OP7O8cuMb8Q=
apigo.cc/go/id v1.5.5 h1:fQXfb2WZ4hEtzXkpb9w9o8AOcTZ44fYQfTV6iZ49l8o=
apigo.cc/go/id v1.5.5/go.mod h1:hCTQq+KC1ALWe1FpPERf+W4B6FSulg9FAgOUJDDySiY=
apigo.cc/go/id v1.5.6 h1:Z3PPp8H8FgNHMwHerxtT2GgstueDv4aCDnUUCoQZoFQ=
apigo.cc/go/id v1.5.6/go.mod h1:HcJK691qfBPzvQ/lt8bo/incKUFtG5vENX/rDEgzJh0=
apigo.cc/go/jsmod v1.5.3 h1:S3W317bH0QV2NMeRO1E0v6ySIBOfMWYv/NuQJbvqKWU=
apigo.cc/go/jsmod v1.5.3/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
apigo.cc/go/log v1.5.8 h1:/IYtGPWhRjT3OayylDIphkWZIQbpLjqVeSnFEiD3Dy0=
apigo.cc/go/log v1.5.8/go.mod h1:HfFPANMYxJx197SSTXB21Pgxcz/gGqPP8nlSErgd5WE=
apigo.cc/go/rand v1.5.3 h1:O4bPIwyaOWEBCr0nL9A4G4qG48AqiGTCzfPeckm3Ius=
apigo.cc/go/rand v1.5.3/go.mod h1:q1BTFkY/cXE229dDD5Q22lF7T0DoKPV6xAu+6bCrDH4=
apigo.cc/go/redis v1.5.9 h1:h77XPjQWcIDgxrtwjtS/gwT6PS5nSk6/x785t0OMlbY=
apigo.cc/go/redis v1.5.9/go.mod h1:YZRDLA3gWw9LAc4Z/4GrDEy8eMDMdDPDA0iqSEAu1fE=
apigo.cc/go/redis v1.5.11 h1:r/kiY3EUD+3YwTd+zMZaRD7eOQZEIvSJwAVnzG96zoQ=
apigo.cc/go/redis v1.5.11/go.mod h1:sCzxIWN+nNIRvR5ePaHsP19hjR52NPfXsq7L1c6A5ec=
apigo.cc/go/safe v1.5.2 h1:EnuEOW/SGwf/5A0nw9LnqfKJE071+TIc6ez8HI9R9Lg=
apigo.cc/go/safe v1.5.2/go.mod h1:2GqCCLLGex4OAhdET3iBWm1R+LIYtmTrvHP8W0iESSw=
apigo.cc/go/shell v1.5.4 h1:Kn6lP6I6d9U0hbyUjpKKFdFZ8RPo4vi4V6AYW8YFzrc=
apigo.cc/go/shell v1.5.4/go.mod h1:FdZWUrcXHGJXo725oSyHqAeFoX0E9yY3PDhrz9hujgY=
apigo.cc/go/starter v1.5.5 h1:4ST02o4qP8IIekxtd9Jhx5RHTrSGXtVQUguSIXV0iWc=
apigo.cc/go/starter v1.5.5/go.mod h1:WAGhdtmZdpP1Jn/z0pCqHwpTbqqaFhm5OqH7QVtcanY=
apigo.cc/go/starter v1.5.6 h1:hcZbR/70q0XW8xZII8BnGDmIomus//+8sWxGqluyj0U=
apigo.cc/go/starter v1.5.6/go.mod h1:abqfSu+lUz/FySn1VxjT8QeAa3mfKo6WrP5xLddw7QE=
apigo.cc/go/timer v1.5.0 h1:iPo/IQn+iuhBRI1/MR1txwZnamef/RBBfOiIlBiqkgk=
apigo.cc/go/timer v1.5.0/go.mod h1:kOnqTTX+zA4AH7SfC+LpUm4ZvS+DVyWWMqul/V5QWJs=
apigo.cc/go/watch v1.5.2 h1:zG56PD8Vml5gVJeo2yNuX9s6stOaIJRYqqKGqXK+xTU=
apigo.cc/go/watch v1.5.2/go.mod h1:MW1XnI0MVyUZpRy590no1vrDT+U4y20L0UZW9/LgU+k=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=

View File

@ -30,10 +30,10 @@ func (rh *RouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer atomic.AddInt64(&rh.webRequestingNum, -1)
tracker := timer.Start()
requestId := r.Header.Get(discover.HeaderRequestID)
requestId := r.Header.Get(discover.HeaderRequestId)
if requestId == "" {
requestId = IDMaker.Get10Bytes14MPerSecond()
r.Header.Set(discover.HeaderRequestID, requestId)
r.Header.Set(discover.HeaderRequestId, requestId)
}
request := NewRequest(r)
@ -458,7 +458,7 @@ func (ws *WebServer) handleClientKeys(request *Request, response *Response) {
})
}
}
request.Request.Header.Set(discover.HeaderSessionID, sessionId)
request.Request.Header.Set(discover.HeaderSessionId, sessionId)
if !hasRequestSessionId {
response.Header().Set(ws.usedSessionIdKey, sessionId)
}
@ -487,7 +487,7 @@ func (ws *WebServer) handleClientKeys(request *Request, response *Response) {
})
}
}
request.Request.Header.Set(discover.HeaderDeviceID, deviceId)
request.Request.Header.Set(discover.HeaderDeviceId, deviceId)
if !hasRequestDeviceId {
response.Header().Set(ws.usedDeviceIdKey, deviceId)
}

View File

@ -1,6 +1,7 @@
package service
import (
"apigo.cc/go/discover"
gohttp "apigo.cc/go/http"
"apigo.cc/go/log"
"fmt"
@ -172,12 +173,12 @@ func (ws *WebServer) processProxy(request *Request, response *Response, logger *
copyResponse(res, response, logger)
} else {
// Discover 代理
if ws.discoverer == nil {
if !ws.discoverStarted {
logger.Error("proxy failed: Discoverer is not initialized")
response.WriteHeader(http.StatusBadGateway)
return true
}
caller := ws.discoverer.NewCaller(request.Request, logger)
caller := discover.NewCaller(request.Request, logger)
caller.NoBody = true
res, _ := caller.ManualDoWithNode(request.Method, app, "", path, request.Body)
copyResponse(res, response, logger)

View File

@ -188,22 +188,22 @@ func (r *Request) MakeUrl(path string) string {
// DeviceId 获取设备 ID
func (r *Request) DeviceId() string {
return r.Header().Get(discover.HeaderDeviceID)
return r.Header().Get(discover.HeaderDeviceId)
}
// SessionId 获取会话 ID
func (r *Request) SessionId() string {
return r.Header().Get(discover.HeaderSessionID)
return r.Header().Get(discover.HeaderSessionId)
}
// SetUserId 设置用户 ID传递给下游
func (r *Request) SetUserId(userId string) {
r.Header().Set(discover.HeaderUserID, userId)
r.Header().Set(discover.HeaderUserId, userId)
}
// ClientIp 获取真实 IP
func (r *Request) ClientIp() string {
ip := r.Header().Get(discover.HeaderClientIP)
ip := r.Header().Get(discover.HeaderClientIp)
if ip == "" {
ip = r.Header().Get(discover.HeaderForwardedFor)
}

View File

@ -5,7 +5,6 @@ import (
"apigo.cc/go/discover"
"apigo.cc/go/log"
"apigo.cc/go/redis"
"apigo.cc/go/safe"
"apigo.cc/go/starter"
"apigo.cc/go/watch"
"context"
@ -29,13 +28,13 @@ type staticType struct {
}
type WebServer struct {
Config ServiceConfig
server *http.Server
listener net.Listener
Addr string
useDiscover bool
discoverer *discover.Discoverer
logger *log.Logger
Config ServiceConfig
server *http.Server
listener net.Listener
Addr string
useDiscover bool
discoverStarted bool
logger *log.Logger
// 运行时状态
serverId string
@ -146,16 +145,32 @@ func NewWebServer() *WebServer {
injectObjects: make(map[reflect.Type]any),
injectFunctions: make(map[reflect.Type]func() any),
usedSessionIdKey: "Session-ID",
usedDeviceIdKey: "Device-ID",
usedClientAppKey: "App",
usedSessionIdKey: "Session-ID",
usedDeviceIdKey: "Device-ID",
usedClientAppKey: "App",
}
return ws
}
// SetDiscovererForTest 提供给测试用例使用的后门方法,用于模拟断开或重置服务发现
func SetDiscovererForTest(d *discover.Discoverer) {
DefaultServer.discoverer = d
func makeDiscoverCallConfig(call CallConfig, fallbackTimeout int) string {
protocol := "http"
if call.SSL {
protocol = "https"
} else if call.Http2 {
protocol = "h2c"
}
parts := []string{protocol}
timeout := call.Timeout
if timeout <= 0 {
timeout = fallbackTimeout
}
if timeout > 0 {
parts = append(parts, fmt.Sprintf("%dms", timeout))
}
if call.Token != "" {
parts = append(parts, call.Token)
}
return strings.Join(parts, ":")
}
// ApplyConfig 将 ServiceConfig 中的路由策略应用到内部的文件级策略中
@ -395,30 +410,14 @@ func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error {
ip := GetServerIp()
discoverAddr := fmt.Sprintf("%s:%s", ip, port)
discConf := discover.Config{
Weight: ws.Config.Weight,
CallRetryTimes: 10,
Calls: make(map[string]discover.CallConfig),
}
if discConf.Weight <= 0 {
discConf.Weight = 100
weight := ws.Config.Weight
if weight <= 0 {
weight = 100
}
calls := make(map[string]string, len(ws.Config.Calls))
for name, call := range ws.Config.Calls {
dc := discover.CallConfig{
Http2: call.Http2,
SSL: call.SSL,
}
if call.Timeout > 0 {
dc.Timeout = time.Duration(call.Timeout) * time.Millisecond
} else if ws.Config.RedirectTimeout > 0 {
dc.Timeout = time.Duration(ws.Config.RedirectTimeout) * time.Millisecond
}
if call.Token != "" {
dc.Token = safe.NewSafeBuf([]byte(call.Token))
}
discConf.Calls[name] = dc
calls[name] = makeDiscoverCallConfig(call, ws.Config.RedirectTimeout)
}
registry := ws.Config.Register
@ -427,8 +426,15 @@ func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error {
}
if registry != "" {
ws.discoverer = discover.Start(registry, appName, discoverAddr, logger, discConf)
if ws.discoverer != nil {
discover.Init()
discover.Config.Registry = registry
discover.Config.App = appName
discover.Config.Weight = weight
discover.Config.CallRetryTimes = 10
discover.Config.Calls = calls
discover.SetLogger(logger)
ws.discoverStarted = discover.Start(discoverAddr)
if ws.discoverStarted {
logger.Info("discover registered", "app", appName, "addr", discoverAddr)
}
}
@ -468,8 +474,9 @@ func (ws *WebServer) Stop(ctx context.Context) error {
ws.shutdownHooks = nil
ws.shutdownHooksLock.Unlock()
if ws.discoverer != nil {
ws.discoverer.Stop()
if ws.discoverStarted {
discover.Stop()
ws.discoverStarted = false
}
if ws.server != nil {
if err := ws.server.Shutdown(ctx); err != nil {