diff --git a/deploy/132-app/docker-compose.yml b/deploy/132-app/docker-compose.yml index 185e36b..e9a9754 100644 --- a/deploy/132-app/docker-compose.yml +++ b/deploy/132-app/docker-compose.yml @@ -31,12 +31,17 @@ services: ADMIN_USER_IDS: ${ADMIN_USER_IDS:-} CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN:-*} OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.100.128:4318 + # 微信域名校验文件目录(容器内路径);未设则该路由不注册 + WECHAT_VERIFY_DIR: /etc/sundynix/wechat-verify ports: ["3000:8080"] # frp 外网 → 132:3000 → 容器 8080 volumes: # 微信支付证书(商户私钥 + 微信支付公钥):宿主机 132 的目录只读挂进容器。 # ⚠️ admin「系统配置 → 支付」里填的路径必须是**容器内路径**(/etc/sundynix/wechat-cert/...), # 不是宿主机路径——容器看不到宿主机的 /home/workspace/...。私钥不进镜像、不进 git。 - /home/workspace/wechat-pay-cert:/etc/sundynix/wechat-cert:ro + # 微信域名校验文件(MP_verify_xxx.txt):配置 JS安全域名/网页授权域名时微信要求 + # 能从域名根目录访问到。放宿主机、只读挂进来,不进镜像也不进 git。 + - /home/workspace/wechat-verify:/etc/sundynix/wechat-verify:ro dispatcher: build: { context: ../.., dockerfile: sundynix-dispatcher/Dockerfile } diff --git a/sundynix-gateway/internal/router/router.go b/sundynix-gateway/internal/router/router.go index 12fdef6..f858cf4 100644 --- a/sundynix-gateway/internal/router/router.go +++ b/sundynix-gateway/internal/router/router.go @@ -7,6 +7,7 @@ import ( "log" "net/http" "os" + "path/filepath" "strings" "github.com/gin-gonic/gin" @@ -195,6 +196,20 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob. c.JSON(http.StatusNotFound, gin.H{"error": "接口不存在"}) return } + // 微信域名校验文件(MP_verify_xxx.txt):配置 JS安全域名/网页授权域名时微信要求 + // 能从域名根目录直接访问。文件放 WECHAT_VERIFY_DIR(容器里挂宿主机目录), + // 不进镜像也不进 git。 + // 放在 NoRoute 里而不是注册 /:mpfile —— 后者会匹配**所有单段路径**, + // 把官网的 /pricing、/download 全变成 404(实测踩过)。 + if dir := os.Getenv("WECHAT_VERIFY_DIR"); dir != "" { + name := strings.TrimPrefix(p, "/") + // 只放行这一种文件名,且不含路径分隔符,避免变成任意文件下载口子 + if strings.HasPrefix(name, "MP_verify_") && strings.HasSuffix(name, ".txt") && + !strings.ContainsAny(name, `/\`) && !strings.Contains(name, "..") { + c.File(filepath.Join(dir, name)) + return + } + } if rel := strings.TrimPrefix(p, "/"); rel != "" { if _, statErr := fs.Stat(adminDist, rel); statErr == nil { adminServer.ServeHTTP(c.Writer, c.Request) // /assets/* 等真实文件