From 83d9a08b9793fcec795edbebd6f84e1528385748 Mon Sep 17 00:00:00 2001 From: fish Date: Sun, 26 Apr 2026 15:15:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=93=E9=80=9A=E5=89=8D=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E8=81=94=E8=B0=83=E9=93=BE=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- backend/gateway/Dockerfile | 5 +- backend/gateway/docker-compose.yml | 29 ++++++++ backend/gateway/nginx/conf.d/default.conf | 31 +++++++-- .../nginx/conf.d/services/user-service.conf | 66 +++++++++++++------ backend/gateway/nginx/nginx.conf | 30 +++++++-- .../user-login-account/src/main.rs | 30 ++++----- .../user-service/user-login-email/src/main.rs | 30 ++++----- frontend/vite.config.ts | 3 +- 8 files changed, 156 insertions(+), 68 deletions(-) create mode 100644 backend/gateway/docker-compose.yml diff --git a/backend/gateway/Dockerfile b/backend/gateway/Dockerfile index e248ba9..cd21eae 100644 --- a/backend/gateway/Dockerfile +++ b/backend/gateway/Dockerfile @@ -10,8 +10,9 @@ RUN mkdir -p /var/log/nginx /var/www/certbot COPY nginx/nginx.conf /etc/nginx/nginx.conf COPY nginx/conf.d/ /etc/nginx/conf.d/ -# 创建自签名证书(仅用于开发,生产环境应挂载真实证书) -RUN apk add --no-cache openssl && \ +# 创建 SSL 目录并生成自签名证书(仅用于开发,生产环境应挂载真实证书) +RUN mkdir -p /etc/nginx/ssl && \ + apk add --no-cache openssl && \ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/key.pem \ -out /etc/nginx/ssl/cert.pem \ diff --git a/backend/gateway/docker-compose.yml b/backend/gateway/docker-compose.yml new file mode 100644 index 0000000..48b04fc --- /dev/null +++ b/backend/gateway/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.8" + +services: + gateway: + build: + context: . + dockerfile: Dockerfile + container_name: api-gateway + ports: + - "80:80" + - "443:443" + volumes: + # 开发环境:挂载配置便于热更新,生产环境应内嵌在镜像中 + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/conf.d:/etc/nginx/conf.d:ro + networks: + - default + - frontend_asset-helper-network + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 3s + start_period: 5s + retries: 3 + +networks: + frontend_asset-helper-network: + external: true diff --git a/backend/gateway/nginx/conf.d/default.conf b/backend/gateway/nginx/conf.d/default.conf index 29b9ec8..20d8480 100644 --- a/backend/gateway/nginx/conf.d/default.conf +++ b/backend/gateway/nginx/conf.d/default.conf @@ -3,25 +3,48 @@ server { listen 80 default_server; listen [::]:80 default_server; server_name _; - + return 444; } -# HTTP 重定向到 HTTPS +# HTTP 重定向到 HTTPS(生产域名) server { listen 80; listen [::]:80; server_name api.example.com; - + location /.well-known/acme-challenge/ { root /var/www/certbot; } - + location / { return 301 https://$server_name$request_uri; } } +# 开发环境 - 直接代理,不重定向到 HTTPS +server { + listen 80; + listen [::]:80; + server_name localhost api-gateway host.docker.internal; + + # 开发环境直接代理,不强制 HTTPS + include /etc/nginx/conf.d/services/*.conf; + + # 健康检查 + location /health { + access_log off; + return 200 '{"status":"healthy","timestamp":"$time_iso8601"}\n'; + add_header Content-Type application/json; + } + + # 根路径 + location / { + return 200 '{"status":"ok","service":"api-gateway","timestamp":"$time_iso8601"}\n'; + add_header Content-Type application/json; + } +} + # API 网关主配置 server { listen 443 ssl http2; diff --git a/backend/gateway/nginx/conf.d/services/user-service.conf b/backend/gateway/nginx/conf.d/services/user-service.conf index 6b7e18b..857ffa3 100644 --- a/backend/gateway/nginx/conf.d/services/user-service.conf +++ b/backend/gateway/nginx/conf.d/services/user-service.conf @@ -1,36 +1,62 @@ # 用户服务路由 -location /api/v1/users { - # 限流 + +# 账号登录(严格限流) +location /api/v1/auth/login/account { + limit_req zone=api_strict burst=5 nodelay; + limit_conn addr 3; + + rewrite ^/api/v1/auth/login/account$ /login break; + proxy_pass http://user_login_account; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Request-ID $request_id; +} + +# 邮箱登录(严格限流) +location /api/v1/auth/login/email { + limit_req zone=api_strict burst=5 nodelay; + limit_conn addr 3; + + rewrite ^/api/v1/auth/login/email$ /login break; + proxy_pass http://user_login_email; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Request-ID $request_id; +} + +# 账号注册(通用限流) +location /api/v1/users/register/account { limit_req zone=general burst=20 nodelay; limit_conn addr 10; - # 代理设置 - proxy_pass http://user_service; + rewrite ^/api/v1/users/register/account$ /register break; + proxy_pass http://user_register_account; proxy_http_version 1.1; - + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Request-ID $request_id; - - # WebSocket 支持(如果需要) - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - # 缓存控制 - proxy_cache_bypass $http_upgrade; - proxy_no_cache 1; } -# 认证相关接口(严格限流) -location /api/v1/auth { - limit_req zone=api_strict burst=5 nodelay; - limit_conn addr 3; - - proxy_pass http://user_service; +# 邮箱注册(通用限流) +location /api/v1/users/register/email { + limit_req zone=general burst=20 nodelay; + limit_conn addr 10; + + rewrite ^/api/v1/users/register/email$ /register break; + proxy_pass http://user_register_email; proxy_http_version 1.1; - + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/backend/gateway/nginx/nginx.conf b/backend/gateway/nginx/nginx.conf index ab0c8a5..9fa8436 100644 --- a/backend/gateway/nginx/nginx.conf +++ b/backend/gateway/nginx/nginx.conf @@ -44,22 +44,42 @@ http { # 连接限制 limit_conn_zone $binary_remote_addr zone=addr:10m; - # 上游服务 - upstream user_service { + # 上游服务 —— 通过宿主机端口访问各微服务(开发环境) + # 生产环境应改为容器名:端口,并确保同网络 + upstream user_login_account { least_conn; - server user-service:8080 max_fails=3 fail_timeout=30s; + server host.docker.internal:20111 max_fails=3 fail_timeout=30s; keepalive 32; } + upstream user_register_account { + least_conn; + server host.docker.internal:20112 max_fails=3 fail_timeout=30s; + keepalive 32; + } + + upstream user_login_email { + least_conn; + server host.docker.internal:20113 max_fails=3 fail_timeout=30s; + keepalive 32; + } + + upstream user_register_email { + least_conn; + server host.docker.internal:20114 max_fails=3 fail_timeout=30s; + keepalive 32; + } + + # 以下服务尚未实现,临时标记为 down,避免启动时 DNS 解析失败 upstream order_service { least_conn; - server order-service:8080 max_fails=3 fail_timeout=30s; + server 127.0.0.1:9999 down; keepalive 32; } upstream payment_service { least_conn; - server payment-service:8080 max_fails=3 fail_timeout=30s; + server 127.0.0.1:9999 down; keepalive 32; } diff --git a/backend/services/user-service/user-login-account/src/main.rs b/backend/services/user-service/user-login-account/src/main.rs index b8203f5..d16f9f3 100644 --- a/backend/services/user-service/user-login-account/src/main.rs +++ b/backend/services/user-service/user-login-account/src/main.rs @@ -28,18 +28,12 @@ struct LoginRequest { password: String, } -// 统一响应包装 +// 登录/认证类接口扁平响应(与前端约定对齐) #[derive(Serialize)] -struct ApiResponse { +struct LoginResponse { success: bool, + token: Option, message: String, - data: Option, -} - -// 登录业务数据 -#[derive(Serialize)] -struct LoginData { - token: String, } // JWT Claims @@ -98,7 +92,7 @@ async fn main() { async fn login_handler( State(state): State>, Json(payload): Json, -) -> (StatusCode, Json>) { +) -> (StatusCode, Json) { info!("Login attempt for user: {}", payload.username); // 查询用户账号与密码 @@ -120,16 +114,16 @@ async fn login_handler( match verify(&payload.password, &password_hash) { Ok(true) => { info!("User {} logged in successfully", payload.username); - + // 生成 JWT let token = generate_token(&user_id.to_string(), &state.jwt_secret); - + ( StatusCode::OK, - Json(ApiResponse { + Json(LoginResponse { success: true, + token: Some(token), message: "Login successful".to_string(), - data: Some(LoginData { token }), }), ) } @@ -137,10 +131,10 @@ async fn login_handler( warn!("Invalid password for user {}", payload.username); ( StatusCode::UNAUTHORIZED, - Json(ApiResponse { + Json(LoginResponse { success: false, + token: None, message: "Invalid credentials".to_string(), - data: None, }), ) } @@ -148,10 +142,10 @@ async fn login_handler( warn!("Password verification error: {:?}", e); ( StatusCode::INTERNAL_SERVER_ERROR, - Json(ApiResponse { + Json(LoginResponse { success: false, + token: None, message: "Internal error".to_string(), - data: None, }), ) } diff --git a/backend/services/user-service/user-login-email/src/main.rs b/backend/services/user-service/user-login-email/src/main.rs index 9006ba2..871f9f2 100644 --- a/backend/services/user-service/user-login-email/src/main.rs +++ b/backend/services/user-service/user-login-email/src/main.rs @@ -28,18 +28,12 @@ struct LoginRequest { password: String, } -// 统一响应包装 +// 登录/认证类接口扁平响应(与前端约定对齐) #[derive(Serialize)] -struct ApiResponse { +struct LoginResponse { success: bool, + token: Option, message: String, - data: Option, -} - -// 登录业务数据 -#[derive(Serialize)] -struct LoginData { - token: String, } // JWT Claims @@ -98,7 +92,7 @@ async fn main() { async fn login_handler( State(state): State>, Json(payload): Json, -) -> (StatusCode, Json>) { +) -> (StatusCode, Json) { info!("Login attempt for email: {}", payload.email); // 查询用户邮箱与密码 @@ -120,16 +114,16 @@ async fn login_handler( match verify(&payload.password, &password_hash) { Ok(true) => { info!("Email {} logged in successfully", payload.email); - + // 生成 JWT let token = generate_token(&user_id.to_string(), &state.jwt_secret); - + ( StatusCode::OK, - Json(ApiResponse { + Json(LoginResponse { success: true, + token: Some(token), message: "Login successful".to_string(), - data: Some(LoginData { token }), }), ) } @@ -137,10 +131,10 @@ async fn login_handler( warn!("Invalid password for email {}", payload.email); ( StatusCode::UNAUTHORIZED, - Json(ApiResponse { + Json(LoginResponse { success: false, + token: None, message: "Invalid credentials".to_string(), - data: None, }), ) } @@ -148,10 +142,10 @@ async fn login_handler( warn!("Password verification error: {:?}", e); ( StatusCode::INTERNAL_SERVER_ERROR, - Json(ApiResponse { + Json(LoginResponse { success: false, + token: None, message: "Internal error".to_string(), - data: None, }), ) } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d02126f..c5c6525 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -18,7 +18,8 @@ export default defineConfig(({ mode }) => ({ }, proxy: { '/api': { - target: process.env.VITE_API_BASE_URL || 'http://host.docker.internal:80', + // 开发环境:通过 Docker 网络直接访问网关容器 + target: process.env.VITE_API_BASE_URL || 'http://api-gateway', changeOrigin: true, secure: false, },