feat: 初次启动
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
Name: gateway
|
||||
Host: 0.0.0.0
|
||||
Port: 8888
|
||||
|
||||
Log:
|
||||
Encoding: plain
|
||||
Mode: console
|
||||
|
||||
# 跨域配置
|
||||
Cors:
|
||||
Enable: true
|
||||
AllowOrigins:
|
||||
- "*"
|
||||
AllowMethods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- OPTIONS
|
||||
AllowHeaders:
|
||||
- Content-Type
|
||||
- Authorization
|
||||
- X-Requested-With
|
||||
|
||||
# 上游服务路由表
|
||||
Upstreams:
|
||||
- Prefix: /api/user
|
||||
Target: http://127.0.0.1:9001
|
||||
- Prefix: /api/file
|
||||
Target: http://127.0.0.1:9002
|
||||
- Prefix: /api/sys
|
||||
Target: http://127.0.0.1:9003
|
||||
- Prefix: /api/plant
|
||||
Target: http://127.0.0.1:9004
|
||||
- Prefix: /api/radio
|
||||
Target: http://127.0.0.1:9005
|
||||
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"sundynix-micro-go/app/gateway/internal/config"
|
||||
"sundynix-micro-go/app/gateway/internal/handler"
|
||||
"sundynix-micro-go/app/gateway/internal/middleware"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/conf"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
var configFile = flag.String("f", "etc/gateway.yaml", "the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
|
||||
// 构建反向代理路由器
|
||||
proxyRouter := handler.NewProxyRouter(c.Upstreams)
|
||||
|
||||
// 构建请求处理链
|
||||
var finalHandler http.Handler = proxyRouter
|
||||
|
||||
// 注入 CORS 中间件
|
||||
if c.Cors.Enable {
|
||||
corsMiddleware := middleware.NewCorsMiddleware(c.Cors.AllowOrigins, c.Cors.AllowMethods, c.Cors.AllowHeaders)
|
||||
finalHandler = http.HandlerFunc(corsMiddleware.Handle(func(w http.ResponseWriter, r *http.Request) {
|
||||
proxyRouter.ServeHTTP(w, r)
|
||||
}))
|
||||
}
|
||||
|
||||
// 健康检查
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
fmt.Fprintf(w, `{"code":200,"msg":"gateway is healthy","upstreams":%d}`, len(c.Upstreams))
|
||||
})
|
||||
mux.Handle("/", finalHandler)
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", c.Host, c.Port)
|
||||
logx.Infof("===== Sundynix Gateway 启动 =====")
|
||||
logx.Infof("监听地址: %s", addr)
|
||||
logx.Infof("路由数量: %d", len(c.Upstreams))
|
||||
for _, u := range c.Upstreams {
|
||||
logx.Infof(" %s -> %s", u.Prefix, u.Target)
|
||||
}
|
||||
logx.Infof("================================")
|
||||
|
||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||
logx.Errorf("网关启动失败: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package config
|
||||
|
||||
import "github.com/zeromicro/go-zero/rest"
|
||||
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
|
||||
Cors struct {
|
||||
Enable bool
|
||||
AllowOrigins []string
|
||||
AllowMethods []string
|
||||
AllowHeaders []string
|
||||
}
|
||||
|
||||
Upstreams []Upstream
|
||||
}
|
||||
|
||||
type Upstream struct {
|
||||
Prefix string // URL 前缀,如 /api/user
|
||||
Target string // 后端地址,如 http://127.0.0.1:9001
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"sundynix-micro-go/app/gateway/internal/config"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// ProxyRouter 反向代理路由器
|
||||
type ProxyRouter struct {
|
||||
routes []*route
|
||||
}
|
||||
|
||||
type route struct {
|
||||
prefix string
|
||||
proxy *httputil.ReverseProxy
|
||||
target string
|
||||
}
|
||||
|
||||
// NewProxyRouter 根据配置构建路由表
|
||||
func NewProxyRouter(upstreams []config.Upstream) *ProxyRouter {
|
||||
router := &ProxyRouter{}
|
||||
|
||||
for _, u := range upstreams {
|
||||
targetURL, err := url.Parse(u.Target)
|
||||
if err != nil {
|
||||
logx.Errorf("解析上游地址失败 [%s]: %v", u.Target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
target := targetURL // 显式捕获循环变量
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Rewrite: func(pr *httputil.ProxyRequest) {
|
||||
pr.SetXForwarded()
|
||||
pr.Out.URL.Scheme = target.Scheme
|
||||
pr.Out.URL.Host = target.Host
|
||||
pr.Out.Host = target.Host
|
||||
// 路径透传:前端请求 /api/user/info -> 转发到 user-api 的 /api/user/info
|
||||
// 不剥前缀,后端API的路由本身就包含 /api/user 前缀
|
||||
},
|
||||
}
|
||||
|
||||
// 自定义 Transport:超时控制
|
||||
proxy.Transport = &http.Transport{
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 20,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
prefix := u.Prefix
|
||||
targetAddr := u.Target
|
||||
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
logx.Errorf("代理请求失败 [%s%s -> %s]: %v", prefix, r.URL.Path, targetAddr, err)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
fmt.Fprintf(w, `{"code":502,"msg":"上游服务不可用: %s"}`, prefix)
|
||||
}
|
||||
|
||||
router.routes = append(router.routes, &route{
|
||||
prefix: u.Prefix,
|
||||
proxy: proxy,
|
||||
target: u.Target,
|
||||
})
|
||||
|
||||
logx.Infof("路由注册: %s -> %s", u.Prefix, u.Target)
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// ServeHTTP 根据 URL 前缀匹配路由并转发
|
||||
func (pr *ProxyRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
|
||||
for _, rt := range pr.routes {
|
||||
if strings.HasPrefix(path, rt.prefix) {
|
||||
logx.Infof("[Gateway] %s %s -> %s", r.Method, path, rt.target)
|
||||
rt.proxy.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 没有匹配的路由
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, `{"code":404,"msg":"路由未找到: %s"}`, path)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CorsMiddleware 跨域中间件
|
||||
type CorsMiddleware struct {
|
||||
allowOrigins []string
|
||||
allowMethods []string
|
||||
allowHeaders []string
|
||||
}
|
||||
|
||||
func NewCorsMiddleware(origins, methods, headers []string) *CorsMiddleware {
|
||||
return &CorsMiddleware{
|
||||
allowOrigins: origins,
|
||||
allowMethods: methods,
|
||||
allowHeaders: headers,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CorsMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
allowed := false
|
||||
for _, o := range m.allowOrigins {
|
||||
if o == "*" || o == origin {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allowed {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(m.allowMethods, ", "))
|
||||
w.Header().Set("Access-Control-Allow-Headers", strings.Join(m.allowHeaders, ", "))
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Access-Control-Max-Age", "3600")
|
||||
}
|
||||
|
||||
// 预检请求直接返回
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user