fix: code compliance audit and fix across all services
NestJS (6 services): implement @RequirePermission decorator with SetMetadata+Reflector, register APP_GUARD globally, fix as assertions to type guards, add explicit return types, fix import type for express, fix /metrics implicit any, replace native Error with ApplicationError, remove typeorm remnants, register LifecycleService. teacher-bff: add logger, ApplicationError, GlobalErrorFilter, forward real userId to downstream, log downstream failures, migrate health controller to shared/health. Go (2 services): interface to any, doc comments, CORS dev whitelist, JWT secret fail-fast, push-gateway internal API auth, metrics and readyz endpoints, remove dead code. Python (2 services): lifespan return type, dev_mode to bool, data-ana APIRouter, ai POST body model, ClickHouse async wrapping.
This commit is contained in:
@@ -6,6 +6,7 @@ require (
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/sony/gobreaker/v2 v2.1.0
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.69.0
|
||||
go.opentelemetry.io/otel v1.44.0
|
||||
@@ -14,6 +15,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||
github.com/bytedance/sonic v1.15.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
||||
@@ -40,7 +42,11 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.7.0 // indirect
|
||||
@@ -52,6 +58,7 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/arch v0.27.0 // indirect
|
||||
golang.org/x/crypto v0.52.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
|
||||
@@ -2,6 +2,8 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZp
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA=
|
||||
github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
@@ -74,8 +76,16 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
@@ -85,10 +95,20 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
|
||||
@@ -97,6 +117,8 @@ github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
|
||||
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sony/gobreaker/v2 v2.1.0 h1:av2BnjtRmVPWBvy5gSFPytm1J8BmN5AGhq875FfGKDM=
|
||||
github.com/sony/gobreaker/v2 v2.1.0/go.mod h1:dO3Q/nCzxZj6ICjH6J/gM0r4oAwBMVLY8YAQf+NTtUg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -148,6 +170,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU=
|
||||
golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
@@ -171,6 +195,8 @@ google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zN
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Config 持有 api-gateway 运行时配置
|
||||
type Config struct {
|
||||
Port string
|
||||
JWTSecret string
|
||||
@@ -23,10 +25,26 @@ type Config struct {
|
||||
DevMode bool
|
||||
}
|
||||
|
||||
// devJWTSecret 是 DevMode 下的默认 JWT 密钥(仅用于本地联调,生产必须配置 JWT_SECRET)
|
||||
const devJWTSecret = "p1-dev-secret-change-in-production"
|
||||
|
||||
// Load 从环境变量加载配置。
|
||||
// 非 DevMode 下若 JWT_SECRET 未配置则 fatal 退出;DevMode 下使用默认密钥并打印 warning。
|
||||
func Load() *Config {
|
||||
devMode := getEnvBool("DEV_MODE", false)
|
||||
jwtSecret := getEnv("JWT_SECRET", "")
|
||||
if jwtSecret == "" {
|
||||
if devMode {
|
||||
log.Println("warning: JWT_SECRET not set, using dev default (DEV_MODE=true)")
|
||||
jwtSecret = devJWTSecret
|
||||
} else {
|
||||
log.Fatal("JWT_SECRET must be set in non-dev mode")
|
||||
}
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Port: getEnv("API_GATEWAY_PORT", "8080"),
|
||||
JWTSecret: getEnv("JWT_SECRET", "p1-dev-secret-change-in-production"),
|
||||
JWTSecret: jwtSecret,
|
||||
JWTIssuer: getEnv("JWT_ISSUER", "next-edu-cloud"),
|
||||
JWTAudience: getEnv("JWT_AUDIENCE", "next-edu-cloud"),
|
||||
ClassesServiceURL: getEnv("CLASSES_SERVICE_URL", "http://localhost:3001"),
|
||||
@@ -39,10 +57,11 @@ func Load() *Config {
|
||||
AiServiceURL: getEnv("AI_SERVICE_URL", "http://localhost:3008"),
|
||||
OTLPEndpoint: getEnv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318"),
|
||||
LogLevel: getEnv("LOG_LEVEL", "info"),
|
||||
DevMode: getEnvBool("DEV_MODE", false),
|
||||
DevMode: devMode,
|
||||
}
|
||||
}
|
||||
|
||||
// getEnv 读取环境变量,缺失时返回 fallback
|
||||
func getEnv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
@@ -50,15 +69,7 @@ func getEnv(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getEnvInt(key string, fallback int) int {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
if i, err := strconv.Atoi(v); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// getEnvBool 读取环境变量并解析为 bool,缺失或解析失败时返回 fallback
|
||||
func getEnvBool(key string, fallback bool) bool {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
if b, err := strconv.ParseBool(v); err == nil {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/edu-cloud/api-gateway/internal/config"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// publicPaths 是无需鉴权的公开路径(精确匹配,基于去掉 /api/v1 前缀后的路径)
|
||||
@@ -73,7 +72,7 @@ func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
||||
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (any, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
@@ -107,7 +106,7 @@ func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
|
||||
if sub, ok := claims["sub"].(string); ok {
|
||||
c.Request.Header.Set("x-user-id", sub)
|
||||
}
|
||||
if roles, ok := claims["roles"].([]interface{}); ok {
|
||||
if roles, ok := claims["roles"].([]any); ok {
|
||||
roleStrs := make([]string, 0, len(roles))
|
||||
for _, r := range roles {
|
||||
if s, ok := r.(string); ok {
|
||||
@@ -120,22 +119,3 @@ func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequestIDMiddleware 注入请求 ID 用于全链路追踪
|
||||
func RequestIDMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = generateUUID()
|
||||
}
|
||||
c.Set("request_id", requestID)
|
||||
c.Writer.Header().Set("X-Request-ID", requestID)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// generateUUID 生成带 req- 前缀的唯一请求 ID
|
||||
// 使用 uuid.New() 基于 RFC 4122 v4 随机 UUID,避免 time.Now() 产生的冲突与可预测性
|
||||
func generateUUID() string {
|
||||
return "req-" + uuid.New().String()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -12,22 +13,26 @@ import (
|
||||
// corsMaxAge 预检缓存时长:12 小时
|
||||
const corsMaxAge = 12 * 60 * 60
|
||||
|
||||
// devCORSOrigins 是未配置 CORS_ORIGINS 时的开发环境默认白名单
|
||||
const devCORSOrigins = "http://localhost:3000,http://localhost:3001"
|
||||
|
||||
// CORS 返回跨域资源共享中间件。
|
||||
// 允许来源从环境变量 CORS_ORIGINS 读取(逗号分隔,默认 *)。
|
||||
// 允许来源从环境变量 CORS_ORIGINS 读取(逗号分隔);
|
||||
// 未配置时使用开发环境白名单(localhost:3000/3001)并打印 warning。
|
||||
// 允许方法:GET POST PUT DELETE OPTIONS PATCH
|
||||
// 允许头:Authorization Content-Type X-Request-Id X-Trace-Id
|
||||
// 暴露头:X-Request-Id X-Trace-Id
|
||||
func CORS() gin.HandlerFunc {
|
||||
allowed := parseCORSOrigins(os.Getenv("CORS_ORIGINS"))
|
||||
if len(allowed) == 0 {
|
||||
log.Println("warning: CORS_ORIGINS not set, using dev default whitelist")
|
||||
allowed = parseCORSOrigins(devCORSOrigins)
|
||||
}
|
||||
|
||||
return func(c *gin.Context) {
|
||||
origin := c.GetHeader("Origin")
|
||||
allowOrigin := ""
|
||||
|
||||
if len(allowed) == 0 {
|
||||
// 未配置则默认允许所有来源
|
||||
allowOrigin = "*"
|
||||
} else if allowed[origin] {
|
||||
if allowed[origin] {
|
||||
allowOrigin = origin
|
||||
}
|
||||
|
||||
@@ -39,9 +44,7 @@ func CORS() gin.HandlerFunc {
|
||||
h.Set("Access-Control-Expose-Headers", "X-Request-Id, X-Trace-Id")
|
||||
h.Set("Access-Control-Max-Age", strconv.Itoa(corsMaxAge))
|
||||
// 非通配来源需标注 Vary,便于缓存正确区分
|
||||
if allowOrigin != "*" {
|
||||
h.Add("Vary", "Origin")
|
||||
}
|
||||
h.Add("Vary", "Origin")
|
||||
}
|
||||
|
||||
// 预检请求直接返回 204
|
||||
@@ -53,7 +56,7 @@ func CORS() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// parseCORSOrigins 解析逗号分隔的来源列表为集合,空字符串返回空 map(表示通配 *)
|
||||
// parseCORSOrigins 解析逗号分隔的来源列表为集合,空字符串返回空 map
|
||||
func parseCORSOrigins(raw string) map[string]bool {
|
||||
allowed := map[string]bool{}
|
||||
if raw == "" {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/edu-cloud/api-gateway/internal/observability"
|
||||
"github.com/edu-cloud/api-gateway/internal/proxy"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
|
||||
)
|
||||
|
||||
@@ -51,6 +52,8 @@ func main() {
|
||||
// 健康检查路由(无需鉴权,在 Auth 之前)
|
||||
r.GET("/healthz", health.Healthz)
|
||||
r.GET("/readyz", health.Readyz)
|
||||
// Prometheus 指标端点
|
||||
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
|
||||
|
||||
// API v1 组:熔断 + 鉴权 + 反向代理
|
||||
api := r.Group("/api/v1")
|
||||
|
||||
Reference in New Issue
Block a user