package routers import ( "os" "strings" "server/controllers" "server/middleware" "server/routers/api" authrouter "server/routers/auth" "server/routers/backend" "server/routers/index" "server/routers/platform" beego "github.com/beego/beego/v2/server/web" "github.com/beego/beego/v2/server/web/context" ) // 初始化路由(精简版) func init() { // 扫描器探针降噪:.env / config.js / credentials.json / Go 源码路径等探测请求不再逐条打印, // 全部 404 时只定期汇总「未发现泄露」,一旦返回 2xx 立即告警具体路径。 // dev 模式的 `[D] router.go` 访问日志由 middleware 的 filterconsole 适配器过滤, // 这里保留 DefaultAccessLogFilter 仅用于 AccessLogs 开启的场景。 beego.DefaultAccessLogFilter = middleware.NewSecurityProbeLogFilter(beego.DefaultAccessLogFilter) // 全局 CORS 处理 + 预检请求 // 注意:Allow-Origin 为 * 时不能同时设置 Allow-Credentials: true,否则浏览器会拒绝带 Authorization 的预检(上传/接口跨域常见现象)。 // 当前 JWT 走 Header、前端 axios withCredentials=false,无需携带 Cookie,故不返回 Allow-Credentials。 beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) { ctx.Output.Header("Access-Control-Allow-Origin", "*") ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") ctx.Output.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") ctx.Output.Header("Access-Control-Max-Age", "86400") if ctx.Input.Method() == "OPTIONS" { ctx.Output.Status = 200 ctx.Output.Body([]byte("")) return } }) // 全局 JWT 鉴权:解析 token 并写入上下文(userId/tenantId/userType/...)。 // 模式由 app.conf 的 auth_enforce 控制: // warn(默认,观察模式,只记录不拦截)→ on(真正拦截)→ off(紧急关闭) // 放在操作日志之前注册,保证操作日志能取到 userId。 beego.InsertFilter("*", beego.BeforeRouter, middleware.JWTAuthMiddleware()) // 模块套餐鉴权:租户当前套餐(含单品订单)未开通的模块,其接口直接拒绝。 // 映射与开关见 app.conf 的 plan_module_paths / plan_enforce(默认 warn 只记日志)。 beego.InsertFilter("*", beego.BeforeRouter, middleware.PlanAuthMiddleware()) // 全局操作日志:请求开始采集并异步写入 beego.InsertFilter("*", beego.BeforeRouter, middleware.BeginOperationLog) // Let's Encrypt HTTP-01 验证回访。 // 无条件注册:证书颁发机构何时来验证与 APP_MODE 无关,任何启动模式都必须能应答。 beego.Router("/.well-known/acme-challenge/:token", &controllers.AcmeChallengeController{}, "get:Serve") // 统一认证中心(UAC):所有运行模式都必须注册,否则对应模式下无法登录 authrouter.Register() // 根据运行模式选择要注册的路由组 // 优先读取环境变量 APP_MODE,其次读取配置 app_mode,默认 all mode := os.Getenv("APP_MODE") if mode == "" { mode, _ = beego.AppConfig.String("app_mode") } if mode == "" { mode = "all" } switch mode { case "platform": platform.Register() api.Register() // 在 platform 模式下,仍保留 backend 登录相关路由,避免后台登录 404 backend.RegisterAuthRoutes() case "backend": backend.Register() api.Register() case "index": index.Register() api.Register() case "api": api.Register() case "all": platform.Register() backend.Register() index.Register() api.Register() default: // 未知模式时,退回到只启用平台端,避免启动失败 platform.Register() api.Register() } // 根路径站点验证文件(备案 / 搜索引擎 / 第三方平台的域名所有权验证)。 // 详见 routers/verifyfiles.go 注释。 // 由 app.conf 的 verify_dir / verify_exts / verify_hosts 控制: // verify_dir 验证文件目录,留空 = 默认 verify(相对服务目录);填 - = 关闭 // verify_exts 允许的扩展名,留空 = 默认 .txt,.html,.htm // verify_hosts 限定生效域名,留空 = 所有走本服务的域名都生效 // 必须注册在 RegisterPlatformSPA 之前:两者都是 BeforeStatic 过滤器, // 先注册者先执行,避免验证文件被 SPA 的 index.html 兜底吃掉。 if verifyDir, _ := beego.AppConfig.String("verify_dir"); strings.TrimSpace(verifyDir) != "-" { verifyExts, _ := beego.AppConfig.String("verify_exts") var verifyHosts []string if raw, _ := beego.AppConfig.String("verify_hosts"); raw != "" { for _, h := range strings.Split(raw, ",") { if t := strings.TrimSpace(h); t != "" { verifyHosts = append(verifyHosts, t) } } } RegisterVerifyFiles(verifyDir, verifyExts, verifyHosts...) } // 平台后台 SPA 静态托管(详见 routers/spa.go 注释)。 // 由 app.conf 的 platform_spa_dir / platform_spa_hosts 控制;任一为空则不启用。 if spaDir, _ := beego.AppConfig.String("platform_spa_dir"); strings.TrimSpace(spaDir) != "" { var hosts []string if raw, _ := beego.AppConfig.String("platform_spa_hosts"); raw != "" { for _, h := range strings.Split(raw, ",") { if t := strings.TrimSpace(h); t != "" { hosts = append(hosts, t) } } } RegisterPlatformSPA(spaDir, hosts...) } }