Files
yunzerwebsiteallinone/go/routers/verifyfiles.go
T
2026-10-31 19:17:37 +08:00

118 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routers
import (
"log"
"net/http"
"os"
"path/filepath"
"strings"
"server/pkg/verifyfiles"
beego "github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/server/web/context"
)
// appRoot 定位服务根目录(conf/app.conf 所在目录的上一级)。
//
// beego 找配置的顺序是:当前工作目录 → 可执行文件所在目录。
// 这里沿用同样的顺序,保证不管是用 `go run main.go`(systemd 的 WorkingDirectory)
// 还是编译后的二进制、不管从哪个目录启动,验证文件目录都稳定落在服务根目录下,
// 不会出现「换个目录启动就找不到验证文件」的情况。
//
// 线上即 /www/wwwroot/api.yunzer.cn(conf/app.conf 在 /www/wwwroot/api.yunzer.cn/conf/)。
func appRoot() string {
candidates := make([]string, 0, 2)
if wd, err := os.Getwd(); err == nil {
candidates = append(candidates, wd)
}
if beego.AppPath != "" {
candidates = append(candidates, beego.AppPath)
}
return appRootFrom(candidates)
}
// appRootFrom 在候选目录里找第一个含 conf/app.conf 的目录(即服务根目录)。
func appRootFrom(candidates []string) string {
for _, base := range candidates {
if base == "" {
continue
}
if _, err := os.Stat(filepath.Join(base, "conf", "app.conf")); err == nil {
return base
}
}
return ""
}
// RegisterVerifyFiles 让 Go 直接吐「根路径站点验证文件」。
//
// 背景:nginx 对所有域名都是全量反代给 Go(location / → proxy_pass),
// Go 不认识的路径一律回 beego 404 页,于是这些文件根本访问不到:
// - 备案 / 域名所有权验证:1XXl8fPHr9.txt(阿里云、腾讯云)
// - 搜索引擎站点验证:googleXXXX.html、msXXXXXXXX.txt、Bing 的 .xml
// - 第三方平台域名校验:各平台给的 .txt / .html
//
// 这些文件名每次都不同(随机串),逐个注册路由不现实;改 nginx 又会因为
// 宝塔重生成 vhost 被冲掉(同 routers/spa.go 里遇到的问题)。
// 所以由 Go 统一托管:只要把验证文件丢进 verify 目录,
// https://api.yunzer.cn/1XXl8fPHr9.txt 就能直接取到。
//
// 规则:
// - 只处理 GET / HEAD;
// - 只认「根路径下的单段文件名」,如 /1XXl8fPHr9.txt;
// - 文件名过白名单正则,扩展名在允许列表内(默认 .txt/.html/.htm);
// - 文件必须真实存在于验证目录,否则放行给正常路由(最终仍是 beego 404)。
//
// 验证文件目录由 app.conf 的 verify_dir 指定(默认相对进程工作目录的 verify,
// 即线上服务目录 /www/wwwroot/api.yunzer.cn/verify);verify_hosts 可限定生效域名,
// 留空表示所有走本服务的域名都生效(备案常常同时要过 api.yunzer.cn 与官网主域名)。
func RegisterVerifyFiles(dir string, exts string, hosts ...string) {
dir = strings.TrimSpace(dir)
if dir == "-" {
// 显式关闭
return
}
if dir == "" {
// 默认:服务根目录下的 verify/(app.conf 所在目录的上一级)
dir = filepath.Join(appRoot(), verifyfiles.DefaultDir)
}
resolver, err := verifyfiles.New(verifyfiles.Config{
Dir: dir,
Exts: exts,
Hosts: strings.Join(hosts, ","),
})
if err != nil {
log.Printf("[verify] 根路径验证文件功能未启用: %v", err)
return
}
if resolver == nil {
// dir = "-":显式关闭
return
}
// 目录不存在就建一个,方便直接往里丢验证文件
if err := os.MkdirAll(resolver.Dir(), 0o755); err != nil {
log.Printf("[verify] 创建验证文件目录失败: %v", err)
}
beego.InsertFilter("*", beego.BeforeStatic, func(ctx *context.Context) {
if ctx.Request.Method != http.MethodGet && ctx.Request.Method != http.MethodHead {
return
}
if !resolver.AllowHost(ctx.Request.Host) {
return
}
full, ok := resolver.Resolve(ctx.Request.URL.Path)
if !ok {
return
}
// 文件内容不变,但可能被替换(换 token、换文件名),短缓存即可
ctx.Output.Header("Cache-Control", "public, max-age=300")
http.ServeFile(ctx.ResponseWriter, ctx.Request, full)
})
log.Printf("[verify] 根路径验证文件目录: %s,允许扩展名: %v", resolver.Dir(), resolver.Exts())
}