# Juwan 后端项目完整使用指南

## 项目概述

```
Juwan 是一个基于 Go-Zero 微服务框架的分布式后端系统，采用以下架构：

┌─────────────────────────────────────────────────────────────────────┐
│                      Envoy Gateway (负载均衡、认证)                      │
│                        端口: 80 (HTTP)                               │
└──────────────┬──────────────────────────────────────────────────────┘
               │
       ┌───────┴──────────┐
       │                  │
   ┌───▼────────┐    ┌───▼────────┐
   │  User API  │    │ Order API  │
   │ (8888)     │    │ (8889)     │
   └───┬────────┘    └────────────┘
       │
   ┌───▼────────────────────┐
   │  User RPC (内部使用)    │
   │  gRPC (不暴露)          │
   └────────────────────────┘
       │
   ┌───▼────────────────────┐
   │  PostgreSQL Database    │
   └────────────────────────┘
```

**关键特性：**
- ✅ API 层通过 Envoy Gateway 暴露给外部
- ✅ RPC 层仅限集群内部通信（通过 K8s Service Discovery）
- ✅ JWT 认证（可选路由）
- ✅ 密码加密存储
- ✅ 用户会话管理

---

## 1️⃣ 添加新服务（完整步骤）

### 示例：添加一个 Product API 服务

#### Step 1: 定义 API 接口

创建 `desc/api/product.api`：

```goctl
syntax = "v1"

type (
    Product {
        ProductId   int64   `json:"productId"`
        Name        string  `json:"name"`
        Description string  `json:"description"`
        Price       float64 `json:"price"`
        Stock       int32   `json:"stock"`
        CreateAt    int64   `json:"createAt"`
    }
    
    CreateProductReq {
        Name        string  `json:"name" binding:"required,min=2"`
        Description string  `json:"description"`
        Price       float64 `json:"price" binding:"required,gt=0"`
        Stock       int32   `json:"stock" binding:"required,gte=0"`
    }
    
    CreateProductResp {
        ProductId int64  `json:"productId"`
        Message   string `json:"message"`
    }
    
    GetProductReq {
        ProductId int64 `path:"productId" binding:"required,gt=0"`
    }
    
    ListProductsReq {
        Page  int64 `form:"page" binding:"required,gt=0"`
        Limit int64 `form:"limit" binding:"required,gt=0,lte=100"`
    }
    
    ListProductsResp {
        Total    int64      `json:"total"`
        Products []Product  `json:"products"`
    }
)

@server (
    group:      product
    prefix:     /api/products
    middleware: Logger
)
service product-api {
    @doc (summary: "创建商品")
    @handler CreateProduct
    post / (CreateProductReq) returns (CreateProductResp)
    
    @doc (summary: "获取商品详情")
    @handler GetProduct
    get /:productId (GetProductReq) returns (Product)
    
    @doc (summary: "列出所有商品")
    @handler ListProducts
    get / (ListProductsReq) returns (ListProductsResp)
}
```

#### Step 2: 创建 RPC 定义（内部服务）

创建 `desc/rpc/product.proto`：

```proto
syntax = "proto3";

option go_package = "./pb";

package pb;

message Product {
    int64 productId = 1;
    string name = 2;
    string description = 3;
    double price = 4;
    int32 stock = 5;
    int64 createAt = 6;
}

message GetProductReq {
    int64 productId = 1;
}

message GetProductResp {
    Product product = 1;
}

message UpdateStockReq {
    int64 productId = 1;
    int32 delta = 2;  // 正数增加库存，负数减少
}

message UpdateStockResp {
    int32 newStock = 1;
}

service ProductCenter {
    rpc GetProduct(GetProductReq) returns (GetProductResp);
    rpc UpdateStock(UpdateStockReq) returns (UpdateStockResp);
}
```

#### Step 3: 生成代码

```bash
# 生成 API 服务代码
goctl api go -api desc/api/product.api -dir app/product/api --style=goZero

# 生成 RPC 服务代码
goctl rpc protoc desc/rpc/product.proto \
  --go_out=./app/product/rpc \
  --go-grpc_out=./app/product/rpc \
  --zrpc_out=./app/product/rpc \
  --style=goZero
```

#### Step 4: 实现业务逻辑

编辑 `app/product/api/internal/logic/product/createproduct.go`：

```go
package product

import (
    "context"
    "product-api/app/product/api/internal/svc"
    "product-api/app/product/api/internal/types"
)

type CreateProductLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateProductLogic {
    return &CreateProductLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *CreateProductLogic) CreateProduct(req *types.CreateProductReq) (*types.CreateProductResp, error) {
    // TODO: 调用 RPC 或数据库创建商品
    return &types.CreateProductResp{
        ProductId: 1,
        Message:   "创建成功",
    }, nil
}
```

#### Step 5: 配置 K8s 部署

创建 `deploy/k8s/service/product/product-api.yaml`：

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: product-api-config
  namespace: juwan
data:
  product-api.yaml: |
    Name: product-api
    Host: 0.0.0.0
    Port: 8890
    Database:
      DataSource: postgres://user:pass@pg-dx:5432/juwan

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-api
  namespace: juwan
spec:
  replicas: 2
  selector:
    matchLabels:
      app: product-api
  template:
    metadata:
      labels:
        app: product-api
    spec:
      containers:
      - name: product-api
        image: your-registry/product-api:latest
        ports:
        - containerPort: 8890
        volumeMounts:
        - name: config
          mountPath: /etc/product-api
        env:
        - name: TZ
          value: "Asia/Shanghai"
      volumes:
      - name: config
        configMap:
          name: product-api-config

---
apiVersion: v1
kind: Service
metadata:
  name: product-api-svc
  namespace: juwan
spec:
  selector:
    app: product-api
  ports:
  - port: 8890
    targetPort: 8890
  type: ClusterIP
```

#### Step 6: 更新 Envoy 网关配置

在 `deploy/k8s/envoy-gateway.yaml` 中添加：

```yaml
# 在 http_filters->route_config->virtual_hosts->routes 添加：
- match:
    prefix: /api/products
  route:
    cluster: product_api_cluster
    timeout: 30s

# 在 clusters 添加：
- name: product_api_cluster
  connect_timeout: 5s
  type: STRICT_DNS
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: product_api_cluster
    endpoints:
      - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: product-api-svc.juwan.svc.cluster.local
                  port_value: 8890
  health_checks:
    - timeout: 3s
      interval: 10s
      unhealthy_threshold: 2
      healthy_threshold: 2
      http_health_check:
        path: /health
```

#### Step 7: 部署到集群

```bash
# 应用 K8s 配置
kubectl apply -f deploy/k8s/service/product/product-api.yaml

# 更新 Envoy Gateway 配置
kubectl apply -f deploy/k8s/envoy-gateway.yaml

# 重启 Envoy Pod 使配置生效
kubectl delete pods -n juwan -l app=envoy-gateway

# 验证
kubectl get pods -n juwan
curl http://localhost/api/products
```

---

## 2️⃣ RPC 服务内部隔离（不暴露给外部）

### 当前架构（推荐）

```
┌────────────────────────────────────────────┐
│         外部客户端 (互联网)                 │
└────────────┬─────────────────────────────┘
             │
        ┌────▼──────────┐
        │  Envoy Gateway│
        │  (仅 HTTP)    │
        └────┬──────────┘
             │
    ┌────────┴─────────────┐
    │                      │
┌───▼───────────┐  ┌──────▼─────────┐
│  User API    │  │  Product API   │
│  (8888)      │  │  (8890)        │
└───┬───────────┘  └────────────────┘
    │
┌───▼──────────────────────────────────┐
│  User RPC (完全隐藏)                  │
│  - 不暴露端口                        │
│  - gRPC 通信                        │
│  - K8s Service DNS 发现              │
│  - NetworkPolicy 限制通信            │
└──────────────────────────────────────┘
```

### 实现步骤

#### 1. 创建仅内部的 Service（无 port 暴露）

在 `deploy/k8s/service/user/user-rpc.yaml`：

```yaml
apiVersion: v1
kind: Service
metadata:
  name: user-rpc-svc
  namespace: juwan
spec:
  selector:
    app: user-rpc
  ports:
  - name: grpc
    port: 50051
    targetPort: 50051
  type: ClusterIP  # 📌 仅限集群内部访问，不暴露 NodePort 或 LoadBalancer
  sessionAffinity: None
```

#### 2. 配置 NetworkPolicy（进一步限制）

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: user-rpc-access
  namespace: juwan
spec:
  podSelector:
    matchLabels:
      app: user-rpc
  policyTypes:
  - Ingress
  ingress:
  # 仅允许 API 和其他服务(如 Order API)访问
  - from:
    - podSelector:
        matchLabels:
          app: user-api
    - podSelector:
        matchLabels:
          app: order-api
    - podSelector:
        matchLabels:
          app: product-api
    ports:
    - protocol: TCP
      port: 50051
```

#### 3. API 服务中调用 RPC

在 `app/users/api/internal/svc/servicecontext.go`：

```go
package svc

import (
    "github.com/zeromicro/go-zero/zrpc"
    "app/users/api/internal/config"
    "app/users/rpc/pb"
)

type ServiceContext struct {
    Config   config.Config
    UserRpc  pb.UsercenterClient  // RPC 客户端
}

func NewServiceContext(c config.Config) *ServiceContext {
    userRpcClient := zrpc.MustNewClient(c.UserRpc)
    return &ServiceContext{
        Config:   c,
        UserRpc:  pb.NewUsercenterClient(userRpcClient.Conn()),
    }
}
```

配置文件 `app/users/api/etc/user-api.yaml`：

```yaml
Name: user-api
Host: 0.0.0.0
Port: 8888

# RPC 配置（使用 K8s DNS）
UserRpc:
  Endpoints:
    - user-rpc-svc.juwan.svc.cluster.local:50051
  
Database:
  DataSource: postgres://user:pass@pg-dx:5432/juwan
```

#### 4. RPC 不在 Envoy 中配置路由

❌ **不要**在 `envoy-gateway.yaml` 中添加 RPC 集群：

```yaml
# ❌❌❌ 不要这样做 ❌❌❌
# routes:
#   - match:
#       prefix: /juwan.pb.Usercenter/
#     route:
#       cluster: user_rpc_cluster  # 这样会暴露 RPC
```

---

## 3️⃣ JWT 认证与分级访问控制

### 实现逻辑

```
请求到达 Envoy → JWT 验证

┌──────────────────────────────────────┐
│  无效或缺省 Token                     │
└──────────┬───────────────────────────┘
           │
    公开路由 (允许) ──→ /api/users/login
                      /api/users/register
                      /api/products (列表、详情)
    
    受保护路由 (拒绝) ──→ 返回 401 Unauthorized
           │
    ┌──────▼───────────────────────────┐
    │  有效 Token                       │
    │  (已登录)                        │
    └──────┬───────────────────────────┘
           │
    ┌──────▼──────────────────────────────────────────────┐
    │ 完整数据访问 (取决于后端 RPC)                         │
    │ - 用户信息 (包括隐私信息)                           │
    │ - 订单历史                                         │
    │ - 收藏列表                                         │
    └──────────────────────────────────────────────────────┘
```

### 配置步骤

#### 1. 生成 JWT 密钥并存储为 K8s Secret

```bash
# 生成 HMAC 密钥（用于签名）
openssl rand -hex 32

# 输出示例: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

# 创建 Secret
kubectl create secret generic jwt-secret \
  --from-literal=key=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 \
  -n juwan

# 验证
kubectl get secret jwt-secret -n juwan -o yaml
```

#### 2. 更新 Envoy 配置（添加 JWT 验证）

编辑 `deploy/k8s/envoy-gateway.yaml`：

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: envoy-config
  namespace: juwan
data:
  envoy.yaml: |
    static_resources:
      listeners:
        - name: listener_http
          address:
            socket_address:
              address: 0.0.0.0
              port_value: 8080
          filter_chains:
            - filters:
                - name: envoy.filters.network.http_connection_manager
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                    stat_prefix: ingress_http
                    http_filters:
                      # JWT 认证过滤器（在 router 之前）
                      - name: envoy.filters.http.jwt_authn
                        typed_config:
                          "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
                          
                          # 定义 JWT 提供者
                          providers:
                            my-provider:
                              issuer: "juwan"
                              audiences: "api"
                              # 使用文件系统上的密钥
                              local_jwks:
                                filename: /etc/envoy/jwks.json
                          
                          # 定义受保护的路由
                          rules:
                            # 规则1: 登录和注册不需要认证
                            - match:
                                prefix: /api/users/login
                              allow_missing_or_failed: true  # 允许缺省/失败的 token
                            - match:
                                prefix: /api/users/register
                              allow_missing_or_failed: true
                            # 规则2: 重定向认证失败请求
                            - match:
                                prefix: "/"
                              requires:
                                provider_name: "my-provider"
                                # 如果认证失败，Envoy 直接拒绝，返回 401
                      
                      # 路由过滤器
                      - name: envoy.filters.http.router
                        typed_config:
                          "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                    
                    route_config:
                      name: local_route
                      virtual_hosts:
                        - name: backend
                          domains: ["*"]
                          routes:
                            - match:
                                prefix: /api/users
                              route:
                                cluster: user_api_cluster
                            - match:
                                prefix: /api/products
                              route:
                                cluster: product_api_cluster
                            # ... 其他路由 ...
      
      # ... clusters 定义保持不变 ...
```

#### 3. 在 API 服务中添加认证中间件

创建 `app/users/api/internal/middleware/authmiddleware.go`：

```go
package middleware

import (
    "fmt"
    "net/http"
    "strings"
    
    "github.com/golang-jwt/jwt/v4"
)

type AuthMiddleware struct {
    JwtSecret string
}

func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // 获取 Authorization header
        authHeader := r.Header.Get("Authorization")
        if authHeader == "" {
            // 如果认证失败，Envoy 会返回 401，
            // 但我们可以在 API 层添加自定义逻辑
            next(w, r)
            return
        }
        
        parts := strings.Split(authHeader, " ")
        if len(parts) != 2 || parts[0] != "Bearer" {
            http.Error(w, "Invalid authorization header", http.StatusUnauthorized)
            return
        }
        
        token := parts[1]
        
        // 验证 token
        _, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
            }
            return []byte(m.JwtSecret), nil
        })
        
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        
        // Token 有效，继续处理
        next(w, r)
    }
}
```

#### 4. 登录端点返回 JWT Token

编辑 `app/users/api/internal/logic/user/loginlogic.go`：

```go
package user

import (
    "context"
    "time"
    "github.com/golang-jwt/jwt/v4"
    "app/users/api/internal/svc"
    "app/users/api/internal/types"
)

type LoginLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func (l *LoginLogic) Login(req *types.LoginReq) (*types.LoginResp, error) {
    // TODO: 验证用户名和密码
    
    // 生成 JWT Token
    claims := jwt.MapClaims{
        "userId":   1,  // 实际应从数据库获取
        "username": req.Username,
        "exp":      time.Now().Add(24 * time.Hour).Unix(),
        "iat":      time.Now().Unix(),
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString([]byte(l.svcCtx.Config.JwtSecret))
    if err != nil {
        return nil, err
    }
    
    return &types.LoginResp{
        Token:   tokenString,
        Expires: time.Now().Add(24 * time.Hour).Unix(),
    }, nil
}
```

### JWT 认证时的分级访问

**后端 RPC 可处理分级访问：**

```go
// 在 User RPC 中实现
func (s *UsercenterServer) GetUserInfo(ctx context.Context, req *pb.GetUsersByIdReq) (*pb.GetUsersByIdResp, error) {
    // 获取请求者的 userId（从 context 中取，由 API 层传递）
    requesterID := ctx.Value("userId").(int64)
    targetID := req.Id
    
    // 查询用户信息
    user := s.getUserFromDB(targetID)
    
    if requesterID == targetID {
        // 自己查看自己 → 返回完整信息（包含隐私信息）
        return &pb.GetUsersByIdResp{
            Users: &pb.Users{
                UserId:    user.UserId,
                Username:  user.Username,
                Email:     user.Email,  // ✅ 包含
                Phone:     user.Phone,  // ✅ 包含
                // ... 所有字段
            },
        }, nil
    } else {
        // 查看别人 → 返回部分信息
        return &pb.GetUsersByIdResp{
            Users: &pb.Users{
                UserId:    user.UserId,
                Username:  user.Username,
                // ❌ 不返回 Email、Phone 等隐私信息
            },
        }, nil
    }
}
```

**或在 API 层处理：**

```go
func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserInfoReq) (*types.UserInfo, error) {
    // 从 context 获取当前认证用户
    currentUser := l.ctx.Value("userId").(int64)
    
    // 调用 RPC
    rpcResp, err := l.svcCtx.UserRpc.GetUsersById(l.ctx, &pb.GetUsersByIdReq{
        Id: req.UserId,
    })
    
    if currentUser == req.UserId {
        // 自己查看自己 → 返回所有信息
        return &types.UserInfo{
            UserId:   rpcResp.Users.UserId,
            Username: rpcResp.Users.Username,
            Email:    rpcResp.Users.Email,
            Phone:    rpcResp.Users.Phone,
        }, nil
    } else {
        // 查看别人 → 仅返回公开信息
        return &types.UserInfo{
            UserId:   rpcResp.Users.UserId,
            Username: rpcResp.Users.Username,
        }, nil
    }
}
```

---

## 4️⃣ 认证失败处理策略

### 当前配置

| 路由 | 认证要求 | 认证失败 | 示例 |
|-----|---------|--------|------|
| `/api/users/login` | ❌ 不需要 | 放行 | `POST /api/users/login` (允许) |
| `/api/users/register` | ❌ 不需要 | 放行 | `POST /api/users/register` (允许) |
| `/api/users/:userId` | ✅ 需要 | 拒绝 401 | `GET /api/users/123` (无 token → 401) |
| `/api/users/:userId/password` | ✅ 需要 | 拒绝 401 | `PUT /api/users/123/password` (无 token → 401) |
| `/api/products` | ❌ 不需要 | 放行 | `GET /api/products` (允许) |
| `/api/products/:id` | ❌ 不需要 | 放行 | `GET /api/products/1` (允许) |

### 自定义错误响应

创建 `deploy/k8s/envoy-gateway.yaml` 的自定义拒绝响应：

```yaml
# 在 jwt_authn 过滤器中配置
http_filters:
  - name: envoy.filters.http.jwt_authn
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
      providers:
        my-provider:
          issuer: "juwan"
          audiences: "api"
          local_jwks:
            filename: /etc/envoy/jwks.json
      rules:
        - match:
            prefix: /api/users/login
          allow_missing_or_failed: true
        - match:
            prefix: /api/users/register
          allow_missing_or_failed: true
        - match:
            prefix: /
          requires:
            provider_name: "my-provider"
      # 认证失败后的行为
      bypass_cors_preflight: false  # 允许 OPTIONS 跨域请求
```

**认证失败时 Envoy 返回 401：**

```json
HTTP/1.1 401 Unauthorized
content-type: text/html

Jwt verification fails.
```

### 在 API 层添加自定义错误处理

如果希望自定义错误响应，可以在每个受保护的 handler 中添加检查：

```go
func (l *UpdateUserInfoLogic) UpdateUserInfo(req *types.UpdateUserInfoReq) (*types.UpdateUserInfoResp, error) {
    // 获取当前请求者的身份
    ctx := context.WithValue(l.ctx, "currentUserId", req.UserId)
    
    // 如果没有匹配的 token，API 层可以添加自定义错误
    if req.UserId <= 0 {
        return nil, errors.New("invalid user id")
    }
    
    // 继续处理
    return &types.UpdateUserInfoResp{Message: "更新成功"}, nil
}
```

---

## 5️⃣ 完整工作流示例

### 场景：用户注册 → 登录 → 获取用户信息

#### 步骤 1: 注册用户（无需认证）

```bash
curl -X POST http://localhost/api/users/register \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john_doe",
    "password": "securePass123",
    "email": "john@example.com",
    "phone": "13800138001"
  }'

# 响应
{
  "userId": 1,
  "username": "john_doe",
  "email": "john@example.com",
  "message": "用户注册成功"
}
```

#### 步骤 2: 登录获取 Token（无需认证）

```bash
curl -X POST http://localhost/api/users/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john_doe",
    "password": "securePass123"
  }'

# 响应
{
  "userId": 1,
  "username": "john_doe",
  "email": "john@example.com",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires": 1708694400
}
```

#### 步骤 3: 获取用户信息（需要 Token）

```bash
# ❌ 无 Token → 401 Unauthorized
curl http://localhost/api/users/1
# Jwt verification fails.

# ✅ 有 Token → 成功
curl http://localhost/api/users/1 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# 响应
{
  "userId": 1,
  "username": "john_doe",
  "email": "john@example.com",
  "phone": "13800138001",
  "avatar": "https://...",
  "status": 1,
  "createAt": 1708608000,
  "updateAt": 1708608000
}
```

#### 步骤 4: 查看他人信息（部分数据）

```bash
# 用户1 查看用户2 的信息（已登录）
curl http://localhost/api/users/2 \
  -H "Authorization: Bearer eyJhbGc..."

# 响应（仅公开信息）
{
  "userId": 2,
  "username": "jane_doe",
  # ❌ 不包含 email, phone 等隐私信息
}
```

---

## 6️⃣ 部署检查清单

在部署新服务或更新配置前，使用此清单：

- [ ] **API 定义** - `desc/api/*.api` 文件已创建
- [ ] **RPC 定义** - `desc/rpc/*.proto` 文件已创建（如需内部通信）
- [ ] **代码生成** - 运行 `goctl api/rpc` 命令生成代码
- [ ] **业务逻辑** - 编辑 `app/*/api/internal/logic/` 实现功能
- [ ] **K8s 部署清单** - 创建 `deploy/k8s/service/*/` 文件
  - [ ] ConfigMap（配置）
  - [ ] Deployment（部署）
  - [ ] Service（K8s 服务发现）
  - [ ] NetworkPolicy（网络隔离，可选）
- [ ] **Envoy 更新** - 修改 `deploy/k8s/envoy-gateway.yaml`
  - [ ] 添加路由规则
  - [ ] 添加上游集群
  - [ ] 验证健康检查地址
- [ ] **测试验证**
  ```bash
  kubectl apply -f deploy/k8s/service/{name}/
  kubectl apply -f deploy/k8s/envoy-gateway.yaml
  kubectl delete pods -n juwan -l app=envoy-gateway
  curl http://localhost/api/{path}  # 端口转发后测试
  ```

---

## 7️⃣ 故障排查

### 问题 1: 新服务无法访问

```bash
# 检查 Pod 状态
kubectl get pods -n juwan

# 查看 Pod 日志
kubectl logs -n juwan -l app=your-service --tail=100

# 检查 Service
kubectl get svc -n juwan

# 测试 DNS 解析
kubectl exec -it {pod-name} -n juwan -- nslookup your-service-svc.juwan.svc.cluster.local
```

### 问题 2: Envoy 配置错误

```bash
# 查看 Envoy Pod 日志
kubectl logs -n juwan -l app=envoy-gateway --tail=50

# 常见错误
# - "no such field" → YAML 字段名与 Envoy 版本不兼容
# - "Unknown cluster" → Envoy 配置中缺少 cluster 定义
# - "Connection refused" → 后端服务未启动或 DNS 无法解析
```

### 问题 3: JWT 认证失败

```bash
# 检查 JWT 配置是否正确
kubectl get configmap envoy-config -n juwan -o yaml

# 查看 jwks.json 是否存在
kubectl exec -it {envoy-pod} -n juwan -- cat /etc/envoy/jwks.json

# 验证 Token 格式
curl -H "Authorization: Bearer {token}" http://localhost/api/users/1
```

---

## 文件组织总结

```
project-root/
├── desc/                              # 接口定义
│   ├── api/
│   │   ├── users.api                  # User API 定义
│   │   └── product.api                # ← 新增：Product API 定义
│   ├── rpc/
│   │   ├── users.proto                # User RPC 定义
│   │   └── product.proto              # ← 新增：Product RPC 定义
│   └── sql/
│
├── app/                               # 应用代码
│   ├── users/
│   │   ├── api/                       # User API 实现
│   │   └── rpc/                       # User RPC 实现（内部）
│   └── product/                       # ← 新增：Product 服务
│       ├── api/
│       └── rpc/
│
├── deploy/                            # K8s 部署
│   ├── k8s/
│   │   ├── envoy-gateway.yaml         # ← 更新：添加新路由
│   │   ├── base/
│   │   └── service/
│   │       ├── user/
│   │       │   ├── user-api.yaml
│   │       │   └── user-rpc.yaml
│   │       └── product/               # ← 新增：Product 部署文件
│   │           ├── product-api.yaml
│   │           └── product-rpc.yaml
│   └── envoy/                         # 参考配置
│
└── docs/                              # 文档
    └── PROJECT_GUIDE.md               # ← 本文件
```

---

希望这份指南能帮助你快速上手项目！有任何问题欢迎提出 📝
