feat(platform): proxy amap security requests

This commit is contained in:
lingniu
2026-07-04 14:39:57 +08:00
parent 6e5ceafcb7
commit b7dd65e435
3 changed files with 186 additions and 4 deletions

View File

@@ -6,8 +6,11 @@ import (
"database/sql"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"sync"
"time"
@@ -51,7 +54,10 @@ func NewServer(cfg config.Config) http.Handler {
api := platform.NewHandler(platform.NewServiceWithRuntime(store, platform.RuntimeInfo{
RequestTimeoutMs: int(cfg.RequestTimeout / time.Millisecond),
}))
return withRequestTimeout(withAppConfig(static.Handler(cfg.StaticDir, api), cfg), cfg.RequestTimeout)
handler := static.Handler(cfg.StaticDir, api)
handler = withAppConfig(handler, cfg)
handler = withAMapSecurityProxy(handler, cfg, defaultAMapProxyUpstreams(), http.DefaultClient)
return withRequestTimeout(handler, cfg.RequestTimeout)
}
func withAppConfig(next http.Handler, cfg config.Config) http.Handler {
@@ -69,7 +75,7 @@ func withAppConfig(next http.Handler, cfg config.Config) http.Handler {
w.Header().Set("Cache-Control", "no-store")
body, err := json.Marshal(appConfig{
AMapWebJSKey: cfg.AMapWebJSKey,
AMapSecurityCode: cfg.AMapSecurityCode,
AMapSecurityCode: exposedAMapSecurityCode(cfg),
AMapServiceHost: cfg.AMapServiceHost,
})
if err != nil {
@@ -80,6 +86,109 @@ func withAppConfig(next http.Handler, cfg config.Config) http.Handler {
})
}
func exposedAMapSecurityCode(cfg config.Config) string {
if cfg.AMapServiceHost != "" {
return ""
}
return cfg.AMapSecurityCode
}
type amapProxyUpstreams struct {
RestAPI string
WebAPI string
FMap string
}
func defaultAMapProxyUpstreams() amapProxyUpstreams {
return amapProxyUpstreams{
RestAPI: "https://restapi.amap.com",
WebAPI: "https://webapi.amap.com",
FMap: "https://fmap01.amap.com",
}
}
func (upstreams amapProxyUpstreams) normalized() amapProxyUpstreams {
defaults := defaultAMapProxyUpstreams()
if upstreams.RestAPI == "" {
upstreams.RestAPI = defaults.RestAPI
}
if upstreams.WebAPI == "" {
upstreams.WebAPI = defaults.WebAPI
}
if upstreams.FMap == "" {
upstreams.FMap = defaults.FMap
}
upstreams.RestAPI = strings.TrimRight(upstreams.RestAPI, "/")
upstreams.WebAPI = strings.TrimRight(upstreams.WebAPI, "/")
upstreams.FMap = strings.TrimRight(upstreams.FMap, "/")
return upstreams
}
func withAMapSecurityProxy(next http.Handler, cfg config.Config, upstreams amapProxyUpstreams, client *http.Client) http.Handler {
serviceHost := strings.TrimRight(cfg.AMapServiceHost, "/")
securityCode := strings.TrimSpace(cfg.AMapSecurityCode)
if serviceHost == "" || securityCode == "" {
return next
}
if client == nil {
client = http.DefaultClient
}
upstreams = upstreams.normalized()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != serviceHost && !strings.HasPrefix(r.URL.Path, serviceHost+"/") {
next.ServeHTTP(w, r)
return
}
target, err := amapProxyURL(upstreams, serviceHost, securityCode, r)
if err != nil {
httpx.WriteError(w, http.StatusBadGateway, "AMAP_PROXY_URL_INVALID", "高德地图代理地址无效", err.Error(), requestTraceID(r))
return
}
req, err := http.NewRequestWithContext(r.Context(), r.Method, target, r.Body)
if err != nil {
httpx.WriteError(w, http.StatusBadGateway, "AMAP_PROXY_REQUEST_INVALID", "高德地图代理请求无效", err.Error(), requestTraceID(r))
return
}
req.Header = r.Header.Clone()
req.Host = ""
resp, err := client.Do(req)
if err != nil {
httpx.WriteError(w, http.StatusBadGateway, "AMAP_PROXY_FAILED", "高德地图代理请求失败", err.Error(), requestTraceID(r))
return
}
defer resp.Body.Close()
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
w.WriteHeader(resp.StatusCode)
_, _ = io.Copy(w, resp.Body)
})
}
func amapProxyURL(upstreams amapProxyUpstreams, serviceHost string, securityCode string, r *http.Request) (string, error) {
targetPath := strings.TrimPrefix(r.URL.Path, serviceHost)
if targetPath == "" {
targetPath = "/"
}
upstreamBase := upstreams.RestAPI
if strings.HasPrefix(targetPath, "/v4/map/styles") {
upstreamBase = upstreams.WebAPI
} else if strings.HasPrefix(targetPath, "/v3/vectormap") {
upstreamBase = upstreams.FMap
}
base, err := url.Parse(upstreamBase)
if err != nil {
return "", err
}
base.Path = strings.TrimRight(base.Path, "/") + targetPath
query := r.URL.Query()
query.Set("jscode", securityCode)
base.RawQuery = query.Encode()
return base.String(), nil
}
func withRequestTimeout(next http.Handler, timeout time.Duration) http.Handler {
if timeout <= 0 {
return next