From 07acfad146ca572f9e21c8940b8a6832599abe8c Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 27 Apr 2026 13:16:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20=E6=B7=BB=E5=8A=A0=20alias=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StaticConfig 添加 Alias 字段,与 Root 互斥 - server.go 创建 handler 时设置 alias - validate.go 添加 root/alias 互斥验证和路径安全检查 - converter.go nginx alias 指令正确转换为 Alias 字段 - defaults.go --generate-config 输出包含 alias 文档和示例 Co-Authored-By: Claude Opus 4.7 --- internal/config/config.go | 7 +++++++ internal/config/defaults.go | 11 ++++++++--- internal/config/validate.go | 10 ++++++++++ internal/converter/nginx/converter.go | 8 +------- internal/converter/nginx/converter_test.go | 10 +++++----- internal/server/server.go | 8 ++++++++ 6 files changed, 39 insertions(+), 15 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 93ee89b..994b70f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -261,8 +261,15 @@ type StaticConfig struct { // Root 静态文件根目录 // 所有静态文件请求都将以此目录为基础解析 + // 请求路径追加到 root 后面 + // 示例: root=/var/www, path=/static/ → /static/img.png → /var/www/static/img.png Root string `yaml:"root"` + // Alias 替换路径(与 root 互斥) + // 将 location 路径替换为 alias 路径(nginx alias 语义) + // 示例: alias=/var/www/files/, path=/images/ → /images/logo.png → /var/www/files/logo.png + Alias string `yaml:"alias"` + // Index 索引文件列表 // 访问目录时依次查找这些文件作为默认页面 // 默认为 ["index.html", "index.htm"] diff --git a/internal/config/defaults.go b/internal/config/defaults.go index fc25a5e..0f3a8f0 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -343,6 +343,11 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) { for _, st := range cfg.Servers[0].Static { buf.WriteString(" - path: \"/\" # 匹配路径前缀\n") fmt.Fprintf(&buf, " root: \"%s\" # 静态文件根目录\n", st.Root) + buf.WriteString(" # alias: \"\" # 替换路径(与 root 互斥,nginx alias 语义)\n") + buf.WriteString(" # root: 请求路径追加到 root 后面\n") + buf.WriteString(" # alias: 请求路径替换 location 为 alias\n") + buf.WriteString(" # 示例: path=/images/, alias=/var/www/files/\n") + buf.WriteString(" # /images/logo.png → /var/www/files/logo.png\n") buf.WriteString(" index: # 索引文件\n") for _, idx := range st.Index { fmt.Fprintf(&buf, " - \"%s\"\n", idx) @@ -353,9 +358,9 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) { buf.WriteString(" # location_type: \"\" # 位置匹配类型(有效值: exact, prefix, regex, regex_caseless, prefix_priority, named)\n") buf.WriteString(" # internal: false # 仅允许内部重定向访问\n") } - buf.WriteString(" # 示例:额外的静态目录\n") - buf.WriteString(" # - path: \"/assets/\"\n") - buf.WriteString(" # root: \"/var/www/assets\"\n") + buf.WriteString(" # 示例:使用 alias 替换路径\n") + buf.WriteString(" # - path: \"/images/\"\n") + buf.WriteString(" # alias: \"/var/www/files/\" # /images/logo.png → /var/www/files/logo.png\n") buf.WriteString(" # index: [\"index.html\"]\n") buf.WriteString("\n") diff --git a/internal/config/validate.go b/internal/config/validate.go index 06520d9..be5a695 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -221,11 +221,21 @@ func validateStatics(statics []StaticConfig) error { } paths[path] = i + // root 和 alias 互斥检查 + if s.Root != "" && s.Alias != "" { + return fmt.Errorf("static[%d]: root 和 alias 不能同时设置", i) + } + // 验证根目录路径安全 if s.Root != "" && strings.Contains(s.Root, "..") { return fmt.Errorf("static[%d]: 根目录路径不能包含 '..'", i) } + // 验证 alias 路径安全 + if s.Alias != "" && strings.Contains(s.Alias, "..") { + return fmt.Errorf("static[%d]: alias 路径不能包含 '..'", i) + } + // 验证 try_files 模式 for j, pattern := range s.TryFiles { if err := validateTryFilesPattern(pattern); err != nil { diff --git a/internal/converter/nginx/converter.go b/internal/converter/nginx/converter.go index eaed012..31416c1 100644 --- a/internal/converter/nginx/converter.go +++ b/internal/converter/nginx/converter.go @@ -832,13 +832,7 @@ func convertStaticDirectives(directives []Directive, static *config.StaticConfig } case "alias": if len(d.Args) > 0 { - static.Root = d.Args[0] - result.Warnings = append(result.Warnings, Warning{ - Directive: "alias", - Line: d.Line, - File: d.File, - Message: "alias is converted to root; semantic differences may exist for locations with non-trailing paths", - }) + static.Alias = d.Args[0] } case "index": static.Index = append(static.Index, d.Args...) diff --git a/internal/converter/nginx/converter_test.go b/internal/converter/nginx/converter_test.go index fd663f3..0b2a7ce 100644 --- a/internal/converter/nginx/converter_test.go +++ b/internal/converter/nginx/converter_test.go @@ -645,12 +645,12 @@ http { } st := s.Static[0] - if st.Root != "/data/photos/" { - t.Errorf("Root = %s, want /data/photos/", st.Root) + if st.Alias != "/data/photos/" { + t.Errorf("Alias = %s, want /data/photos/", st.Alias) } - - if !hasWarningContaining(result.Warnings, "alias") { - t.Error("expected warning about alias conversion to root") + // alias 和 root 互斥,root 应为空 + if st.Root != "" { + t.Errorf("Root = %s, want empty (alias is set)", st.Root) } } diff --git a/internal/server/server.go b/internal/server/server.go index da96fb8..3aebcd1 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1077,6 +1077,10 @@ func (s *Server) registerStaticHandlersWithLocationEngine(cfg *config.ServerConf static.Index, true, // useSendfile ) + // 设置 alias(与 root 互斥) + if static.Alias != "" { + staticHandler.SetAlias(static.Alias) + } if s.fileCache != nil { staticHandler.SetFileCache(s.fileCache) // 设置默认缓存 TTL (5s) @@ -1442,6 +1446,10 @@ func (s *Server) registerStaticHandlers(router *handler.Router, cfg *config.Serv static.Index, true, // useSendfile ) + // 设置 alias(与 root 互斥) + if static.Alias != "" { + staticHandler.SetAlias(static.Alias) + } if s.fileCache != nil { staticHandler.SetFileCache(s.fileCache) // 设置默认缓存 TTL (5s)