feat(config): 添加 alias 配置支持

- 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 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-27 13:16:12 +08:00
parent 5bb44fdbcb
commit 07acfad146
6 changed files with 39 additions and 15 deletions

View File

@ -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"]

View File

@ -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")

View File

@ -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 {

View File

@ -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...)

View File

@ -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)
}
}

View File

@ -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)