@@ -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 \
|
||||
|
||||
29
backend/gateway/docker-compose.yml
Normal file
29
backend/gateway/docker-compose.yml
Normal file
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,18 +28,12 @@ struct LoginRequest {
|
||||
password: String,
|
||||
}
|
||||
|
||||
// 统一响应包装
|
||||
// 登录/认证类接口扁平响应(与前端约定对齐)
|
||||
#[derive(Serialize)]
|
||||
struct ApiResponse<T> {
|
||||
struct LoginResponse {
|
||||
success: bool,
|
||||
token: Option<String>,
|
||||
message: String,
|
||||
data: Option<T>,
|
||||
}
|
||||
|
||||
// 登录业务数据
|
||||
#[derive(Serialize)]
|
||||
struct LoginData {
|
||||
token: String,
|
||||
}
|
||||
|
||||
// JWT Claims
|
||||
@@ -98,7 +92,7 @@ async fn main() {
|
||||
async fn login_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<LoginRequest>,
|
||||
) -> (StatusCode, Json<ApiResponse<LoginData>>) {
|
||||
) -> (StatusCode, Json<LoginResponse>) {
|
||||
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,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,18 +28,12 @@ struct LoginRequest {
|
||||
password: String,
|
||||
}
|
||||
|
||||
// 统一响应包装
|
||||
// 登录/认证类接口扁平响应(与前端约定对齐)
|
||||
#[derive(Serialize)]
|
||||
struct ApiResponse<T> {
|
||||
struct LoginResponse {
|
||||
success: bool,
|
||||
token: Option<String>,
|
||||
message: String,
|
||||
data: Option<T>,
|
||||
}
|
||||
|
||||
// 登录业务数据
|
||||
#[derive(Serialize)]
|
||||
struct LoginData {
|
||||
token: String,
|
||||
}
|
||||
|
||||
// JWT Claims
|
||||
@@ -98,7 +92,7 @@ async fn main() {
|
||||
async fn login_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<LoginRequest>,
|
||||
) -> (StatusCode, Json<ApiResponse<LoginData>>) {
|
||||
) -> (StatusCode, Json<LoginResponse>) {
|
||||
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,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user