refactor: isolate config sources (code/file/dynamic), add file-based routing config, fix Start logger trace ID (by AI)

This commit is contained in:
AI Engineer 2026-05-12 23:53:16 +08:00
parent 3925767d2e
commit 2b7e11e7d2
6 changed files with 178 additions and 21 deletions

View File

@ -1,5 +1,11 @@
package service
import (
"path/filepath"
"regexp"
"strings"
)
// CertSet SSL 证书配置
type CertSet struct {
CertFile string
@ -60,6 +66,81 @@ type ServiceConfig struct {
MaxUploadBufferPerConnection int32 // 每个连接的最大上传缓冲区大小
MaxUploadBufferPerStream int32 // 每个流的最大上传缓冲区大小
StopTimeout int // 停止服务的超时时间 (ms)
// 从配置文件中加载的静态路由策略 (按 Host 分组,全局配置用 "" 或 "*")
Proxies map[string][]ProxyRule
Rewrites map[string][]RewriteRule
Statics map[string]map[string]string
}
var Config = ServiceConfig{}
// ApplyConfig 将 ServiceConfig 中的路由策略应用到内部的文件级策略中
func ApplyConfig() {
hostPoliciesLock.Lock()
defer hostPoliciesLock.Unlock()
// 清理旧的 file 策略
fileProxies = make(map[string][]*proxyType)
fileRewrites = make(map[string][]*rewriteType)
for host, rules := range Config.Proxies {
if host == "*" {
host = ""
}
newProxies := make([]*proxyType, 0, len(rules))
for _, r := range rules {
p := &proxyType{authLevel: r.AuthLevel, fromPath: r.Path, toApp: r.ToApp, toPath: r.ToPath}
if strings.ContainsRune(r.Path, '(') {
matcher, err := regexp.Compile("^" + r.Path + "$")
if err == nil {
p.matcher = matcher
}
}
newProxies = append(newProxies, p)
}
fileProxies[host] = newProxies
rebuildProxiesUnderLock(host)
}
for host, rules := range Config.Rewrites {
if host == "*" {
host = ""
}
newRewrites := make([]*rewriteType, 0, len(rules))
for _, r := range rules {
s := &rewriteType{fromPath: r.Path, toPath: r.ToPath}
if strings.ContainsRune(r.Path, '(') {
matcher, err := regexp.Compile("^" + r.Path + "$")
if err == nil {
s.matcher = matcher
}
}
newRewrites = append(newRewrites, s)
}
fileRewrites[host] = newRewrites
rebuildRewritesUnderLock(host)
}
staticsByHostLock.Lock()
defer staticsByHostLock.Unlock()
fileStatics = make(map[string]map[string]*string)
for host, config := range Config.Statics {
if host == "*" {
host = ""
}
newStatics := make(map[string]*string, len(config))
for path, rootPath := range config {
rp := rootPath
if !filepath.IsAbs(rp) {
if absPath, err := filepath.Abs(rp); err == nil {
rp = absPath
}
}
newStatics[path] = &rp
}
fileStatics[host] = newStatics
rebuildStaticsUnderLock(host)
}
}

View File

@ -30,10 +30,19 @@ func (hc *HostContext) Proxy(authLevel int, path string, toApp, toPath string) *
hostPoliciesLock.Lock()
defer hostPoliciesLock.Unlock()
hostProxies[hc.host] = append(hostProxies[hc.host], p)
codeProxies[hc.host] = append(codeProxies[hc.host], p)
rebuildProxiesUnderLock(hc.host)
return hc
}
func rebuildProxiesUnderLock(host string) {
var combined []*proxyType
combined = append(combined, codeProxies[host]...)
combined = append(combined, fileProxies[host]...)
combined = append(combined, dynamicProxies[host]...)
hostProxies[host] = combined
}
var httpClientPool *gohttp.Client
func findProxy(request *Request) (int, *string, *string, string) {
@ -171,8 +180,7 @@ type ProxyRule struct {
ToPath string // 目标路径 (可含 $1 变量替换)
}
// ReplaceProxies 使用全量指针替换的方式 (Copy-on-Write) 无缝更新指定 host 的所有代理规则。
// 该方法非常轻量,仅在赋值瞬间短暂持有写锁,不会阻塞任何并发请求,并且自动淘汰旧规则。
// ReplaceProxies 使用全量指针替换的方式 (Copy-on-Write) 无缝更新指定 host 的动态代理规则,不影响通过代码或文件写入的固定规则。
func ReplaceProxies(host string, rules []ProxyRule) {
newProxies := make([]*proxyType, 0, len(rules))
for _, r := range rules {
@ -188,5 +196,6 @@ func ReplaceProxies(host string, rules []ProxyRule) {
hostPoliciesLock.Lock()
defer hostPoliciesLock.Unlock()
hostProxies[host] = newProxies
dynamicProxies[host] = newProxies
rebuildProxiesUnderLock(host)
}

View File

@ -26,10 +26,19 @@ func (hc *HostContext) Rewrite(path string, toPath string) *HostContext {
hostPoliciesLock.Lock()
defer hostPoliciesLock.Unlock()
hostRewrites[hc.host] = append(hostRewrites[hc.host], s)
codeRewrites[hc.host] = append(codeRewrites[hc.host], s)
rebuildRewritesUnderLock(hc.host)
return hc
}
func rebuildRewritesUnderLock(host string) {
var combined []*rewriteType
combined = append(combined, codeRewrites[host]...)
combined = append(combined, fileRewrites[host]...)
combined = append(combined, dynamicRewrites[host]...)
hostRewrites[host] = combined
}
func processRewrite(request *Request, response *Response, logger *log.Logger) bool {
host := request.Host
hostOnly, port, _ := strings.Cut(host, ":")
@ -108,7 +117,7 @@ type RewriteRule struct {
ToPath string // 重写后的路径,例如 /new/$1
}
// ReplaceRewrites 使用 Copy-on-Write 机制原子地替换指定 host 下的所有重写规则。
// ReplaceRewrites 使用 Copy-on-Write 机制原子地替换指定 host 下的动态重写规则。
func ReplaceRewrites(host string, rules []RewriteRule) {
newRewrites := make([]*rewriteType, 0, len(rules))
for _, r := range rules {
@ -124,5 +133,6 @@ func ReplaceRewrites(host string, rules []RewriteRule) {
hostPoliciesLock.Lock()
defer hostPoliciesLock.Unlock()
hostRewrites[host] = newRewrites
dynamicRewrites[host] = newRewrites
rebuildRewritesUnderLock(host)
}

View File

@ -1,6 +1,7 @@
package service
import (
"apigo.cc/go/config"
"apigo.cc/go/discover"
"apigo.cc/go/log"
"apigo.cc/go/redis"
@ -27,6 +28,7 @@ type WebServer struct {
Addr string
useDiscover bool
discoverer *discover.Discoverer
logger *log.Logger
}
// NewWebServer 创建并返回一个新的 WebServer 实例
@ -36,6 +38,11 @@ func NewWebServer() *WebServer {
// Start 启动服务,实现 starter.Service 接口
func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error {
if logger == nil {
logger = log.DefaultLogger
}
ws.logger = logger
listenStr := Config.Listen
ws.useDiscover = false
@ -182,7 +189,11 @@ func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error {
// Stop 停止服务,实现 starter.Service 接口
func (ws *WebServer) Stop(ctx context.Context) error {
log.DefaultLogger.Info("service stopping")
logger := ws.logger
if logger == nil {
logger = log.DefaultLogger
}
logger.Info("service stopping")
if ws.discoverer != nil {
ws.discoverer.Stop()
}
@ -191,7 +202,7 @@ func (ws *WebServer) Stop(ctx context.Context) error {
return err
}
}
log.DefaultLogger.Info("service stopped")
logger.Info("service stopped")
return nil
}
@ -205,7 +216,23 @@ func (ws *WebServer) Health() error {
// Reload 实现配置重新加载,实现 starter.Reloader 接口
func (ws *WebServer) Reload() error {
log.DefaultLogger.Info("reloading configurations...")
logger := ws.logger
if logger == nil {
logger = log.DefaultLogger
}
logger.Info("reloading configurations...")
// 重新加载配置文件中的策略
appName := Config.App
if appName == "" {
appName = GetDefaultName()
}
if err := config.Load(&Config, appName); err != nil {
logger.Error("failed to load config during reload", "error", err.Error())
}
ApplyConfig()
// 触发业务挂载的 Hook
return triggerReload()
}

View File

@ -72,9 +72,19 @@ var (
websocketServicesLock = sync.RWMutex{}
websocketServicesList = make([]*websocketServiceType, 0)
// Rewrite 与 Proxy 按 Host 隔离
// Rewrite 与 Proxy 按 Host 隔离 (编译后的最终路由)
hostRewrites = make(map[string][]*rewriteType)
hostProxies = make(map[string][]*proxyType)
// 按来源隔离的策略,避免互相覆盖
codeProxies = make(map[string][]*proxyType)
fileProxies = make(map[string][]*proxyType)
dynamicProxies = make(map[string][]*proxyType)
codeRewrites = make(map[string][]*rewriteType)
fileRewrites = make(map[string][]*rewriteType)
dynamicRewrites = make(map[string][]*rewriteType)
hostPoliciesLock = sync.RWMutex{}
// 过滤器与拦截器

View File

@ -14,6 +14,11 @@ import (
var (
statics = make(map[string]*string)
staticsByHost = make(map[string]map[string]*string)
codeStatics = make(map[string]map[string]*string)
fileStatics = make(map[string]map[string]*string)
dynamicStatics = make(map[string]map[string]*string)
staticsByHostLock = sync.RWMutex{}
)
@ -33,17 +38,14 @@ func StaticByHost(path, rootPath, host string) {
staticsByHostLock.Lock()
defer staticsByHostLock.Unlock()
if host == "" {
statics[path] = &rootPath
} else {
if staticsByHost[host] == nil {
staticsByHost[host] = make(map[string]*string)
}
staticsByHost[host][path] = &rootPath
if codeStatics[host] == nil {
codeStatics[host] = make(map[string]*string)
}
codeStatics[host][path] = &rootPath
rebuildStaticsUnderLock(host)
}
// ReplaceStatics 使用 Copy-on-Write 机制原子地替换指定 host 下的所有静态目录规则
// ReplaceStatics 使用 Copy-on-Write 机制原子地替换指定 host 下的动态静态目录规则
func ReplaceStatics(host string, config map[string]string) {
newStatics := make(map[string]*string, len(config))
for path, rootPath := range config {
@ -59,10 +61,28 @@ func ReplaceStatics(host string, config map[string]string) {
staticsByHostLock.Lock()
defer staticsByHostLock.Unlock()
dynamicStatics[host] = newStatics
rebuildStaticsUnderLock(host)
}
func rebuildStaticsUnderLock(host string) {
combined := make(map[string]*string)
// 合并三种来源的静态路由
for k, v := range codeStatics[host] {
combined[k] = v
}
for k, v := range fileStatics[host] {
combined[k] = v
}
for k, v := range dynamicStatics[host] {
combined[k] = v
}
if host == "" {
statics = newStatics
statics = combined
} else {
staticsByHost[host] = newStatics
staticsByHost[host] = combined
}
}