فهرست منبع

Initial commit: Complete Spring Cloud log demo system with batch testing APIs

demo-user 2 ماه پیش
کامیت
a2ea3a3bac
48فایلهای تغییر یافته به همراه5637 افزوده شده و 0 حذف شده
  1. 50 0
      .gitignore
  2. 373 0
      API.md
  3. 364 0
      ARCHITECTURE.md
  4. 378 0
      BATCH_TEST_API.md
  5. 419 0
      COMPLETION_SUMMARY.md
  6. 379 0
      QUICK_START.md
  7. 254 0
      README.md
  8. 48 0
      docker-compose.yml
  9. 82 0
      pom.xml
  10. 22 0
      shop-recycle-common/pom.xml
  11. 20 0
      shop-recycle-common/src/main/java/com/shop/recycle/common/config/WebConfig.java
  12. 51 0
      shop-recycle-common/src/main/java/com/shop/recycle/common/dto/Response.java
  13. 105 0
      shop-recycle-common/src/main/java/com/shop/recycle/common/interceptor/LoggingMdcInterceptor.java
  14. 19 0
      shop-recycle-gateway/Dockerfile
  15. 38 0
      shop-recycle-gateway/pom.xml
  16. 17 0
      shop-recycle-gateway/src/main/java/com/shop/recycle/gateway/GatewayApplication.java
  17. 72 0
      shop-recycle-gateway/src/main/resources/application.yml
  18. 45 0
      shop-recycle-gateway/src/main/resources/logback-spring.xml
  19. 19 0
      shop-recycle-order-service/Dockerfile
  20. 43 0
      shop-recycle-order-service/pom.xml
  21. 19 0
      shop-recycle-order-service/src/main/java/com/shop/recycle/order/OrderServiceApplication.java
  22. 132 0
      shop-recycle-order-service/src/main/java/com/shop/recycle/order/controller/OrderController.java
  23. 225 0
      shop-recycle-order-service/src/main/java/com/shop/recycle/order/controller/OrderTestController.java
  24. 19 0
      shop-recycle-order-service/src/main/java/com/shop/recycle/order/dto/CreateOrderRequest.java
  25. 22 0
      shop-recycle-order-service/src/main/java/com/shop/recycle/order/dto/OrderResponse.java
  26. 149 0
      shop-recycle-order-service/src/main/java/com/shop/recycle/order/service/OrderService.java
  27. 39 0
      shop-recycle-order-service/src/main/resources/application.yml
  28. 52 0
      shop-recycle-order-service/src/main/resources/logback-spring.xml
  29. 19 0
      shop-recycle-payment-service/Dockerfile
  30. 43 0
      shop-recycle-payment-service/pom.xml
  31. 19 0
      shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/PaymentServiceApplication.java
  32. 118 0
      shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/controller/PaymentController.java
  33. 219 0
      shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/controller/PaymentTestController.java
  34. 19 0
      shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/dto/PaymentRequest.java
  35. 22 0
      shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/dto/PaymentResponse.java
  36. 144 0
      shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/service/PaymentService.java
  37. 39 0
      shop-recycle-payment-service/src/main/resources/application.yml
  38. 52 0
      shop-recycle-payment-service/src/main/resources/logback-spring.xml
  39. 13 0
      shop-recycle-web/index.html
  40. 24 0
      shop-recycle-web/package.json
  41. 147 0
      shop-recycle-web/src/App.vue
  42. 48 0
      shop-recycle-web/src/api/index.js
  43. 475 0
      shop-recycle-web/src/components/OrderComponent.vue
  44. 559 0
      shop-recycle-web/src/components/PaymentComponent.vue
  45. 5 0
      shop-recycle-web/src/main.js
  46. 15 0
      shop-recycle-web/vite.config.js
  47. 108 0
      start.ps1
  48. 94 0
      start.sh

+ 50 - 0
.gitignore

@@ -0,0 +1,50 @@
+# Maven
+target/
+*.jar
+*.war
+*.ear
+*.zip
+*.tar
+*.tar.gz
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/
+mvnw
+mvnw.cmd
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+*~
+.DS_Store
+*.iml
+.classpath
+.project
+.settings/
+bin/
+
+# Node
+node_modules/
+npm-debug.log
+yarn-error.log
+dist/
+
+# Logs
+logs/
+*.log
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Runtime
+.env
+.env.local
+.env.*.local

+ 373 - 0
API.md

@@ -0,0 +1,373 @@
+# API 文档
+
+所有API通过API Gateway (`http://localhost:8080`) 访问。
+
+> **💡 快速生成测试数据?** 查看 [BATCH_TEST_API.md](BATCH_TEST_API.md) 获取批量模拟请求API
+> 
+> 一句命令生成50个订单: `curl http://localhost:8080/api/test/order/batch?count=50`
+
+## 订单服务 (Order Service)
+
+### 1. 创建订单
+**请求:**
+```http
+POST /api/order/create
+Content-Type: application/json
+X-User-Id: user123
+
+{
+  "userId": "user123",
+  "price": 99.99,
+  "description": "test order"
+}
+```
+
+**响应 (成功 200):**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "orderId": "ORD-A1B2C3D4",
+    "userId": "user123",
+    "price": 99.99,
+    "status": "CREATED",
+    "description": "test order",
+    "createdAt": 1707200445123
+  }
+}
+```
+
+**响应 (失败):**
+```json
+{
+  "code": -1,
+  "msg": "price must be > 0",
+  "data": null
+}
+```
+
+---
+
+### 2. 获取订单详情
+**请求:**
+```http
+GET /api/order/{orderId}
+```
+
+**示例:**
+```bash
+curl http://localhost:8080/api/order/ORD-A1B2C3D4
+```
+
+**响应 (成功 200):**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "orderId": "ORD-A1B2C3D4",
+    "userId": "user123",
+    "price": 99.99,
+    "status": "CREATED",
+    "description": "test order",
+    "createdAt": 1707200445123
+  }
+}
+```
+
+---
+
+### 3. 支付订单
+**请求:**
+```http
+POST /api/order/{orderId}/pay
+```
+
+**示例:**
+```bash
+curl -X POST http://localhost:8080/api/order/ORD-A1B2C3D4/pay
+```
+
+**响应 (成功 200):**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": null
+}
+```
+
+---
+
+### 4. 删除订单
+**请求:**
+```http
+POST /api/order/delete/{orderId}
+```
+
+**示例:**
+```bash
+curl -X POST http://localhost:8080/api/order/delete/ORD-A1B2C3D4
+```
+
+**响应 (成功 200):**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": null
+}
+```
+
+**响应 (已支付订单不能删除):**
+```json
+{
+  "code": -1,
+  "msg": "Paid order cannot be deleted",
+  "data": null
+}
+```
+
+---
+
+## 支付服务 (Payment Service)
+
+### 1. 支付订单
+**请求:**
+```http
+POST /api/payment/pay
+Content-Type: application/json
+X-User-Id: user123
+
+{
+  "orderId": "ORD-A1B2C3D4",
+  "amount": 99.99,
+  "paymentMethod": "WECHAT"
+}
+```
+
+**支付方式选项:**
+- `CARD` - 银行卡
+- `WECHAT` - 微信
+- `ALIPAY` - 支付宝
+
+**响应 (成功 200):**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "paymentId": "PAY-X1Y2Z3W4",
+    "orderId": "ORD-A1B2C3D4",
+    "amount": 99.99,
+    "status": "SUCCESS",
+    "paymentMethod": "WECHAT",
+    "paidAt": 1707200500234
+  }
+}
+```
+
+**响应 (失败 - 30% 概率):**
+```json
+{
+  "code": -1,
+  "msg": "Third party payment failed",
+  "data": null
+}
+```
+
+---
+
+### 2. 获取支付详情
+**请求:**
+```http
+GET /api/payment/{paymentId}
+```
+
+**示例:**
+```bash
+curl http://localhost:8080/api/payment/PAY-X1Y2Z3W4
+```
+
+**响应 (成功 200):**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "paymentId": "PAY-X1Y2Z3W4",
+    "orderId": "ORD-A1B2C3D4",
+    "amount": 99.99,
+    "status": "SUCCESS",
+    "paymentMethod": "WECHAT",
+    "paidAt": 1707200500234
+  }
+}
+```
+
+---
+
+### 3. 退款
+**请求:**
+```http
+POST /api/payment/refund/{paymentId}
+```
+
+**示例:**
+```bash
+curl -X POST http://localhost:8080/api/payment/refund/PAY-X1Y2Z3W4
+```
+
+**响应 (成功 200):**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": null
+}
+```
+
+**响应 (非成功支付无法退款):**
+```json
+{
+  "code": -1,
+  "msg": "Only success payment can be refunded",
+  "data": null
+}
+```
+
+---
+
+## 日志对应关系
+
+### 订单创建流程产生的日志链路
+
+```
+1. Gateway 收到请求
+   └─ traceId: abc-123-def-456
+   └─ uri: /api/order/create
+   └─ uri_group: /order/*
+
+2. Order Service 处理请求
+   └─ event_class: order
+   └─ duration: 156ms
+   └─ status: success
+
+3. 返回响应
+   └─ 记录完整的MDC上下文
+```
+
+### 支付流程产生的日志链路
+
+```
+1. Gateway 收到请求
+   └─ traceId: xyz-789-uvw-012
+   └─ uri: /api/payment/pay
+   └─ uri_group: /payment/*
+
+2. Payment Service 处理请求
+   └─ event_class: payment
+   └─ duration: 234ms
+   └─ status: success (70%) 或 server_error (30%)
+
+3. 返回响应
+   └─ 记录完整的MDC上下文
+```
+
+---
+
+## cURL 测试脚本
+
+### 完整的订单+支付流程
+
+```bash
+#!/bin/bash
+
+# 1. 创建订单
+echo "Creating order..."
+ORDER_RESPONSE=$(curl -s -X POST http://localhost:8080/api/order/create \
+  -H "Content-Type: application/json" \
+  -H "X-User-Id: user123" \
+  -d '{
+    "userId":"user123",
+    "price":99.99,
+    "description":"test order"
+  }')
+
+ORDER_ID=$(echo $ORDER_RESPONSE | jq -r '.data.orderId')
+echo "Order created: $ORDER_ID"
+
+# 2. 获取订单详情
+echo "Fetching order details..."
+curl -s http://localhost:8080/api/order/$ORDER_ID | jq .
+
+# 3. 支付订单
+echo "Processing payment..."
+PAYMENT_RESPONSE=$(curl -s -X POST http://localhost:8080/api/payment/pay \
+  -H "Content-Type: application/json" \
+  -d "{
+    \"orderId\":\"$ORDER_ID\",
+    \"amount\":99.99,
+    \"paymentMethod\":\"WECHAT\"
+  }")
+
+PAYMENT_ID=$(echo $PAYMENT_RESPONSE | jq -r '.data.paymentId')
+echo "Payment processed: $PAYMENT_ID"
+
+# 4. 标记订单为已支付
+echo "Marking order as paid..."
+curl -s -X POST http://localhost:8080/api/order/$ORDER_ID/pay | jq .
+
+# 5. 尝试删除已支付订单(应该失败)
+echo "Attempting to delete paid order..."
+curl -s -X POST http://localhost:8080/api/order/delete/$ORDER_ID | jq .
+
+# 6. 获取支付详情
+echo "Fetching payment details..."
+curl -s http://localhost:8080/api/payment/$PAYMENT_ID | jq .
+
+# 7. 退款
+echo "Processing refund..."
+curl -s -X POST http://localhost:8080/api/payment/refund/$PAYMENT_ID | jq .
+```
+
+保存为 `test.sh` 并运行:
+```bash
+chmod +x test.sh
+./test.sh
+```
+
+---
+
+## HTTP 状态码
+
+| 状态码 | 含义 |
+|--------|------|
+| 200 | 请求成功(响应体中 code=0 表示业务成功) |
+| 400 | 请求参数错误 |
+| 500 | 服务器错误 |
+
+## 请求头
+
+| 头| 说明 | 示例 |
+|----|------|------|
+| `Content-Type` | 必需 (POST/PUT) | `application/json` |
+| `X-User-Id` | 可选,用于日志追踪 | `user123` |
+| `X-B3-TraceId` | 可选,Jaeger/Zipkin追踪ID | `550e8400-e29b-41d4` |
+
+---
+
+## 错误处理
+
+所有API响应都遵循统一的格式:
+
+```json
+{
+  "code": 0,           // 0 表示成功,非0表示错误
+  "msg": "success",    // 错误或成功信息
+  "data": {...}        // 业务数据(错误时为null)
+}
+```
+
+实际的HTTP状态码始终是200,业务状态通过 `code` 字段判断。

+ 364 - 0
ARCHITECTURE.md

@@ -0,0 +1,364 @@
+# 系统架构设计
+
+## 🏗️ 整体架构
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                          Browser (Vue Web)                    │
+│                      http://localhost:5173                    │
+└────────────────────────┬────────────────────────────────────┘
+                         │
+                    HTTP Requests
+                         │
+                         ▼
+┌─────────────────────────────────────────────────────────────┐
+│                   API Gateway (Spring Cloud)                  │
+│              http://localhost:8080 (Port 8080)               │
+│                                                               │
+│  Functions:                                                   │
+│  • Route requests to backend services                        │
+│  • Record request entry point logs                          │
+│  • Apply global interceptors                                │
+└────────┬──────────────────────┬──────────────────────┬────────┘
+         │                      │                      │
+    /order/*              /payment/*              /health
+         │                      │                      │
+         ▼                      ▼                      ▼
+    ┌─────────────┐      ┌──────────────┐      ┌──────────────┐
+    │Order Service │      │Payment Service│      └──────────────┘
+    │(Port 8081)  │      │(Port 8082)    │
+    │             │      │               │
+    │ /order/     │      │ /payment/     │
+    │  ├─ create  │      │  ├─ pay       │
+    │  ├─ delete  │      │  ├─ refund    │
+    │  ├─ get     │      │  └─ get       │
+    │  └─ pay     │      │               │
+    └─────────────┘      └──────────────┘
+```
+
+## 📦 模块设计
+
+### 1. shop-recycle-common (公共模块)
+
+**职责:** 提供日志、配置、DTO等公共能力
+
+**包含:**
+- `LoggingMdcInterceptor` - 日志MDC拦截器
+- `Response<T>` - 统一响应类
+- `WebConfig` - Web配置(注册拦截器)
+
+**关键特性:**
+```
+自动填充MDC字段:
+  ✓ traceId    (链路追踪ID)
+  ✓ uri        (原始请求路径)
+  ✓ uri_group  (规范化URI分组)
+  ✓ event_class (事件分类)
+  ✓ duration   (请求耗时)
+  ✓ status     (HTTP状态)
+  ✓ error      (错误堆栈)
+```
+
+### 2. shop-recycle-gateway (API网关)
+
+**职责:** 请求路由、负载均衡、协议转换
+
+**配置:**
+```yaml
+routes:
+  - id: order-service
+    uri: http://localhost:8081    # 本地开发
+    条件: Path=/api/order/**
+
+  - id: payment-service
+    uri: http://localhost:8082
+    条件: Path=/api/payment/**
+```
+
+**日志链路:**
+```
+浏览器请求 → Gateway (日志记录进入点)
+         → 路由转发 → Order Service
+         → 生成响应 → 返回浏览器
+```
+
+### 3. shop-recycle-order-service (订单服务)
+
+**职责:** 订单CRUD操作
+
+**数据模型:**
+```java
+OrderResponse {
+  orderId      // ORD-XXXXX (自动生成)
+  userId       // 用户ID (来自请求头)
+  price        // 订单金额
+  status       // CREATED, PAID, CANCELLED
+  description  // 订单描述
+  createdAt    // 创建时间戳
+}
+```
+
+**业务逻辑:**
+```java
+✓ createOrder()     - 创建订单 (校验价格>0)
+✓ deleteOrder()     - 删除订单 (已支付的不能删除)
+✓ getOrder()        - 查询订单
+✓ updateOrderStatus() - 更新状态 (订单支付时调用)
+```
+
+**日志埋点:**
+```
+DEBUG: 订单创建逻辑开始
+INFO:  订单已创建 orderId=ORD-XXXXX
+WARN:  订单不存在 orderId=...
+ERROR: 订单创建失败 (异常堆栈)
+```
+
+### 4. shop-recycle-payment-service (支付服务)
+
+**职责:** 订单支付、退款
+
+**数据模型:**
+```java
+PaymentResponse {
+  paymentId       // PAY-XXXXX (自动生成)
+  orderId         // 关联的订单
+  amount          // 支付金额
+  status          // PENDING, SUCCESS, FAILED, REFUNDED
+  paymentMethod   // CARD, WECHAT, ALIPAY
+  paidAt          // 支付时间戳
+}
+```
+
+**业务逻辑:**
+```java
+✓ payOrder()   - 支付订单 (模拟30%失败率)
+✓ refund()     - 退款 (仅成功支付可退,模拟10%失败率)
+✓ getPayment() - 查询支付
+```
+
+**日志埋点:**
+```
+DEBUG: 订单支付逻辑开始
+INFO:  订单支付成功 paymentId=PAY-XXXXX
+WARN:  支付金额不合法
+ERROR: 第三方支付失败 (异常堆栈)
+```
+
+### 5. shop-recycle-web (Vue前端)
+
+**职责:** 提供Web UI,调用API生成测试流量
+
+**技术栈:**
+- Vue 3 (创意性组件框架)
+- Vite (构建工具)
+- Axios (HTTP客户端)
+
+**页面组件:**
+- **OrderComponent** - 订单管理界面
+- **PaymentComponent** - 支付管理界面
+
+**代理配置 (vite.config.js):**
+```javascript
+proxy: {
+  '/api': {
+    target: 'http://localhost:8080',  // API Gateway
+    changeOrigin: true
+  }
+}
+```
+
+## 🔄 请求流程示例
+
+### 完整的订单创建流程
+
+```
+Step 1: 用户提交表单
+浏览器 → POST /api/order/create
+         ↓
+Step 2: Gateway接收请求
+Gateway → LoggingMdcInterceptor (记录进入点)
+         ├─ traceId = UUID生成
+         ├─ uri = /api/order/create
+         ├─ uri_group = /order/*
+         └─ event_class = order
+         ↓
+Step 3: 路由转发到Order Service
+Gateway → Order Service (http://localhost:8081/api/order/create)
+         ↓
+Step 4: Order Service处理请求
+Order Service → OrderController.createOrder()
+              → OrderService.createOrder()
+              → generate orderId
+              → save to memory
+              → MDC.put("status", "success")
+              └─ log.info("订单已创建")
+         ↓
+Step 5: 响应返回
+Order Service → Response.success({orderId, userId, ...})
+              ↓
+Step 6: 前端接收响应
+浏览器 ← {code: 0, msg: "success", data: {...}}
+       └─ Display success message
+```
+
+### 后端日志输出
+
+```json
+[Gateway在处理请求时]
+{
+  "ts":"2024-02-06T10:30:45.123Z",
+  "app":"shop-recycle-gateway",
+  "uri":"/api/order/create",
+  "uri_group":"/order/*",
+  "traceId":"550e8400-e29b-41d4-a716-446655440000"
+}
+
+[Order Service处理业务逻辑时]
+{
+  "ts":"2024-02-06T10:30:45.200Z",
+  "app":"shop-recycle-order-service",
+  "level":"INFO",
+  "msg":"订单已创建 orderId=ORD-A1B2C3D4",
+  "event_class":"order",
+  "status":"success",
+  "duration":"156",  // 毫秒
+  "traceId":"550e8400-e29b-41d4-a716-446655440000"
+}
+```
+
+## 📊 日志字段映射表
+
+| MDC字段 | 来源 | 设置位置 | 备注 |
+|---------|------|---------|------|
+| `traceId` | 请求头或生成 | Interceptor.preHandle() | X-B3-TraceId或UUID生成 |
+| `uri` | HttpRequest | Interceptor.preHandle() | 原始请求路径 |
+| `uri_group` | 规范化逻辑 | normalizeUri() | /order/* /payment/* |
+| `event_class` | 规则匹配 | deriveEventClass() | order/payment/auth/api |
+| `userId` | 请求头 | Interceptor.preHandle() | X-User-Id头 |
+| `start_time` | System.time | Interceptor.preHandle() | 用于后续计算duration |
+| `duration` | 计算得出 | Interceptor.afterCompletion() | 当前时间 - start_time |
+| `error` | 异常/状态码 | Interceptor.afterCompletion() | Exception.toString()或HTTP状态 |
+| `status` | HTTP状态码 | Interceptor.afterCompletion() | success/client_error/server_error |
+
+## 🔑 关键设计决策
+
+### 1. 拦截器而不是过滤器
+```
+✓ 优点: 可以访问Handler和ModelMap
+✓ 优点: 在业务方法执行前后进行处理
+✓ 适合: 日志、权限、性能监控
+```
+
+### 2. 异步Appender
+```
+✓ 优点: 异步输出日志,不阻塞业务线程
+✓ 优点: 可配置队列大小和丢弃策略
+✓ 配置: AsyncAppender + queueSize=1024
+```
+
+### 3. JSON格式日志
+```
+✓ 优点: 可被Loki/ELK直接解析
+✓ 优点: 支持结构化查询
+✓ 工具: LogstashEncoder
+```
+
+### 4. MDC而不是参数传递
+```
+✓ 优点: 零侵入,业务代码无需改动
+✓ 优点: 自动在线程本地存储
+✓ 优点: 所有日志自动包含上下文
+```
+
+### 5. URI规范化
+```
+✓ 原始: /order/create /order/12345/delete /order/abc/pay
+✓ 规范化: /order/* (减少基数)
+✓ 好处: Prometheus label基数可控
+```
+
+## 🌐 环境配置
+
+### 本地开发 (local)
+```yaml
+Gateway:
+  order-service: http://localhost:8081
+  payment-service: http://localhost:8082
+
+Frontend:
+  Vite proxy: http://localhost:8080
+```
+
+### Docker容器 (docker)
+```yaml
+Gateway:
+  order-service: http://order-service:8081   (DNS名称)
+  payment-service: http://payment-service:8082
+
+Network:
+  All services connected via app-network bridge
+```
+
+### 生产环境 (prod - 示例)
+```yaml
+Gateway:
+  order-service: http://order-service.default.svc.cluster.local:8081
+  payment-service: http://payment-service.default.svc.cluster.local:8082
+
+Log aggregation:
+  Vector agent → Loki endpoint
+  Prometheus scrape: /actuator/prometheus
+```
+
+## 📈 可观测性支持
+
+### 日志可观测 (Structured Logging)
+```
+✓ Fields: traceId, uri_group, event_class, duration, status
+✓ Format: JSON (parseable)
+✓ Tool: Loki/ELK for Log aggregation
+```
+
+### 指标可观测 (Metrics)
+```
+✓ Requests total (按uri_group分组)
+✓ Requests errors
+✓ Request duration (直方图)
+✓ Order/Payment specific counters
+Tool: Prometheus + Vector for export
+```
+
+### 链路可观测 (Tracing)
+```
+✓ traceId: 记录在每个日志中
+✓ X-B3-TraceId: 支持Jaeger/Zipkin集成
+✓ 可通过traceId查询完整链路
+```
+
+## 🔬 测试架构
+
+### 单元测试
+```
+service层 → 业务逻辑 (MockOrderService)
+```
+
+### 集成测试
+```
+Controller → Service → 内存DB (实际执行)
+```
+
+### 端到端测试
+```
+Vue前端 → API Gateway → Order Service → Payment Service
+         (完整的HTTP链路)
+```
+
+使用QUICK_START.md中的测试脚本进行E2E测试。
+
+---
+
+**相关文档:**
+- [QUICK_START.md](QUICK_START.md) - 快速开始
+- [API.md](API.md) - API详细文档
+- [README.md](README.md) - 项目文档

+ 378 - 0
BATCH_TEST_API.md

@@ -0,0 +1,378 @@
+# 批量测试API文档
+
+这些API用于快速生成大量模拟请求,用来测试日志系统、压力测试等场景。
+
+## 订单服务测试API
+
+### 1. 生成一条随机订单
+**URL:**
+```
+GET http://localhost:8080/api/test/order
+```
+
+**示例:**
+```bash
+curl http://localhost:8080/api/test/order | jq .
+```
+
+**响应:**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "orderId": "ORD-A1B2C3D4",
+    "userId": "alice-5234",
+    "price": 234.56,
+    "status": "CREATED",
+    "description": "二手家电处理",
+    "createdAt": 1707200445123
+  }
+}
+```
+
+---
+
+### 2. 批量生成订单
+**URL:**
+```
+GET http://localhost:8080/api/test/order/batch?count=50
+GET http://localhost:8080/api/test/order/batch (默认10个)
+```
+
+**参数:**
+- `count` (可选): 要生成的订单数量,默认10个
+
+**示例:**
+```bash
+# 生成10个订单
+curl http://localhost:8080/api/test/order/batch | jq .
+
+# 生成50个订单
+curl http://localhost:8080/api/test/order/batch?count=50 | jq .
+
+# 生成100个订单
+curl http://localhost:8080/api/test/order/batch?count=100 | jq .data.stats
+```
+
+**响应:**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "total": 10,
+    "success": 10,
+    "error": 0,
+    "duration": 1245,
+    "orders": [
+      {...订单数据...}
+    ]
+  }
+}
+```
+
+---
+
+### 3. 场景测试 (创建→查询→支付→删除失败)
+**URL:**
+```
+GET http://localhost:8080/api/test/order/scenario?count=5
+```
+
+**参数:**
+- `count` (可选): 测试轮数,默认5轮
+
+**说明:**
+每轮测试都会:
+1. 创建一个订单
+2. 查询订单详情
+3. 标记订单为已支付
+4. 尝试删除订单(预期失败,因为已支付)
+
+这样可以在日志中看到完整的业务流程和错误处理。
+
+**示例:**
+```bash
+curl http://localhost:8080/api/test/order/scenario?count=3 | jq .
+```
+
+**响应:**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "scenario": "create -> query -> pay -> delete(fail)",
+    "rounds": 3,
+    "operations": 12,
+    "duration": 687
+  }
+}
+```
+
+---
+
+### 4. 压力测试
+**URL:**
+```
+GET http://localhost:8080/api/test/order/stress?duration=10
+```
+
+**参数:**
+- `duration` (可选): 测试持续时间(秒),默认10秒
+
+**说明:**
+不间断地创建订单,持续指定时间,用来测试系统吞吐量和日志系统性能。
+
+**示例:**
+```bash
+# 10秒压力测试
+curl http://localhost:8080/api/test/order/stress?duration=10 | jq .
+
+# 30秒压力测试
+curl http://localhost:8080/api/test/order/stress?duration=30 | jq .
+```
+
+**响应:**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "duration": 10234,
+    "requests": 547,
+    "success": 547,
+    "error": 0,
+    "qps": "53.50"
+  }
+}
+```
+
+---
+
+## 支付服务测试API
+
+### 1. 生成一条随机支付
+**URL:**
+```
+GET http://localhost:8080/api/test/payment
+```
+
+**示例:**
+```bash
+curl http://localhost:8080/api/test/payment | jq .
+```
+
+**响应:**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "paymentId": "PAY-X1Y2Z3W4",
+    "orderId": "ORD-123456",
+    "amount": 234.56,
+    "status": "SUCCESS",
+    "paymentMethod": "WECHAT",
+    "paidAt": 1707200500234
+  }
+}
+```
+
+---
+
+### 2. 批量生成支付
+**URL:**
+```
+GET http://localhost:8080/api/test/payment/batch?count=50
+```
+
+**参数:**
+- `count` (可选): 要生成的支付数量,默认10个
+
+**示例:**
+```bash
+curl http://localhost:8080/api/test/payment/batch?count=20 | jq .
+```
+
+**响应:**
+```json
+{
+  "code": 0,
+  "msg": "success",
+  "data": {
+    "total": 20,
+    "success": 14,
+    "error": 6,
+    "totalAmount": 3245.67,
+    "duration": 2341,
+    "payments": [...]
+  }
+}
+```
+
+---
+
+### 3. 退款场景测试
+**URL:**
+```
+GET http://localhost:8080/api/test/payment/refund?count=5
+```
+
+**参数:**
+- `count` (可选): 测试轮数,默认5轮
+
+**说明:**
+每轮测试都会:
+1. 创建一个支付
+2. 尝试退款
+
+用来测试退款失败的场景(10%失败率)。
+
+**示例:**
+```bash
+curl http://localhost:8080/api/test/payment/refund?count=10 | jq .
+```
+
+---
+
+### 4. 支付压力测试
+**URL:**
+```
+GET http://localhost:8080/api/test/payment/stress?duration=10
+```
+
+**示例:**
+```bash
+curl http://localhost:8080/api/test/payment/stress?duration=15 | jq .
+```
+
+---
+
+## 使用场景
+
+### 场景1: 快速验证日志格式
+```bash
+# 生成一条订单,查看日志
+curl http://localhost:8080/api/test/order
+
+# 查看后端Terminal的JSON日志输出
+# 验证 traceId、uri_group、event_class、duration 等字段
+```
+
+### 场景2: 生成足够的日志用于测试
+```bash
+# 生成100条订单
+curl http://localhost:8080/api/test/order/batch?count=100
+
+# 生成50笔支付
+curl http://localhost:8080/api/test/payment/batch?count=50
+```
+
+### 场景3: 测试日志系统的高吞吐处理能力
+```bash
+# 30秒的订单压力测试
+curl http://localhost:8080/api/test/order/stress?duration=30
+
+# 30秒的支付压力测试
+curl http://localhost:8080/api/test/payment/stress?duration=30
+
+# 查看日志系统是否能正常处理高频请求
+# 观察 Duration 字段是否异常增大(说明日志处理成为瓶颈)
+```
+
+### 场景4: 测试自动异常处理和日志记录
+```bash
+# 场景测试会失败删除已支付订单,产生异常日志
+curl http://localhost:8080/api/test/order/scenario?count=10
+
+# 支付服务有30%的失败率,可以看到error日志
+curl http://localhost:8080/api/test/payment/batch?count=50
+```
+
+### 场景5: 批量脚本测试
+```bash
+#!/bin/bash
+# 连续运行多个测试
+
+echo "=== 生成10个订单 ==="
+curl http://localhost:8080/api/test/order/batch?count=10
+echo ""
+
+echo "=== 生成20笔支付 ==="
+curl http://localhost:8080/api/test/payment/batch?count=20
+echo ""
+
+echo "=== 场景测试5轮 ==="
+curl http://localhost:8080/api/test/order/scenario?count=5
+echo ""
+
+echo "=== 10秒压力测试 ==="
+curl http://localhost:8080/api/test/order/stress?duration=10
+echo ""
+```
+
+保存为 `batch-test.sh` 并运行:
+```bash
+chmod +x batch-test.sh
+./batch-test.sh
+```
+
+---
+
+## 日志观察
+
+运行这些API后,在后端Terminal中观察JSON日志:
+
+```json
+{
+  "ts":"2024-02-06T10:30:45.123Z",
+  "level":"INFO",
+  "msg":"测试订单生成成功 orderId=ORD-A1B2C3D4",
+  "event_class":"order",
+  "status":"success",
+  "uri_group":"/test/*",
+  "duration":"145",
+  "app":"shop-recycle-order-service"
+}
+```
+
+关键观察:
+- ✅ `uri_group` 应该是 `/test/*` (规范化规则自动应用)
+- ✅ `event_class` 应该是 `order` 或 `payment`
+- ✅ `status` 应该是 `success` 或 `server_error`
+- ✅ `duration` 显示请求耗时
+- ✅ 所有日志共享相同的 `traceId` (如果在同一请求中)
+
+---
+
+## PowerShell 一键测试脚本
+
+```powershell
+# run-batch-tests.ps1
+
+Write-Host "=== 订单服务测试 ===" -ForegroundColor Cyan
+Write-Host "1. 生成10个订单"
+Invoke-WebRequest -Uri "http://localhost:8080/api/test/order/batch?count=10" | ConvertFrom-Json | ForEach-Object { $_.data | Format-Table total,success,error,duration -AutoSize }
+
+Start-Sleep -Seconds 2
+
+Write-Host "2. 场景测试"
+Invoke-WebRequest -Uri "http://localhost:8080/api/test/order/scenario?count=5" | ConvertFrom-Json | ForEach-Object { $_.data | Format-Table scenario,rounds,duration -AutoSize }
+
+Start-Sleep -Seconds 2
+
+Write-Host ""
+Write-Host "=== 支付服务测试 ===" -ForegroundColor Green
+Write-Host "3. 生成20笔支付"
+Invoke-WebRequest -Uri "http://localhost:8080/api/test/payment/batch?count=20" | ConvertFrom-Json | ForEach-Object { $_.data | Format-Table total,success,error,totalAmount,duration -AutoSize }
+
+Write-Host ""
+Write-Host "✅ 测试完成!" -ForegroundColor Green
+Write-Host "查看后端Terminal的JSON日志输出"
+```
+
+---
+
+**所有这些API都会生成完整的结构化JSON日志,便于验证日志系统的正确性。**

+ 419 - 0
COMPLETION_SUMMARY.md

@@ -0,0 +1,419 @@
+# 项目完成总结
+
+完整的 **Spring Cloud + Vue 日志测试系统**已经创建完成!
+
+## 📁 项目结构
+
+```
+spring-cloud-log-demo/
+├── shop-recycle-common/              # 公共模块
+│   ├── pom.xml
+│   └── src/main/java/com/shop/recycle/common/
+│       ├── interceptor/              # 日志拦截器
+│       │   └── LoggingMdcInterceptor.java
+│       ├── config/
+│       │   └── WebConfig.java
+│       └── dto/
+│           └── Response.java
+│
+├── shop-recycle-gateway/             # API网关 (port 8080)
+│   ├── pom.xml
+│   ├── Dockerfile
+│   ├── src/main/java/com/shop/recycle/gateway/
+│   │   └── GatewayApplication.java
+│   └── src/main/resources/
+│       ├── application.yml           # 路由配置
+│       └── logback-spring.xml        # 日志配置
+│
+├── shop-recycle-order-service/       # 订单服务 (port 8081)
+│   ├── pom.xml
+│   ├── Dockerfile
+│   ├── src/main/java/com/shop/recycle/order/
+│   │   ├── OrderServiceApplication.java
+│   │   ├── controller/
+│   │   │   └── OrderController.java
+│   │   ├── service/
+│   │   │   └── OrderService.java
+│   │   └── dto/
+│   │       ├── CreateOrderRequest.java
+│   │       └── OrderResponse.java
+│   └── src/main/resources/
+│       ├── application.yml
+│       └── logback-spring.xml
+│
+├── shop-recycle-payment-service/     # 支付服务 (port 8082)
+│   ├── pom.xml
+│   ├── Dockerfile
+│   ├── src/main/java/com/shop/recycle/payment/
+│   │   ├── PaymentServiceApplication.java
+│   │   ├── controller/
+│   │   │   └── PaymentController.java
+│   │   ├── service/
+│   │   │   └── PaymentService.java
+│   │   └── dto/
+│   │       ├── PaymentRequest.java
+│   │       └── PaymentResponse.java
+│   └── src/main/resources/
+│       ├── application.yml
+│       └── logback-spring.xml
+│
+├── shop-recycle-web/                 # Vue前端 (port 5173)
+│   ├── package.json
+│   ├── vite.config.js
+│   ├── index.html
+│   ├── public/
+│   └── src/
+│       ├── main.js
+│       ├── App.vue                   # 主应用
+│       ├── components/
+│       │   ├── OrderComponent.vue    # 订单管理
+│       │   └── PaymentComponent.vue  # 支付管理
+│       └── api/
+│           └── index.js              # API客户端
+│
+├── pom.xml                           # 顶层POM
+├── docker-compose.yml                # Docker编排
+├── start.sh                          # Linux/Mac启动脚本
+├── start.ps1                         # Windows PowerShell启动脚本
+├── README.md                         # 项目文档
+├── QUICK_START.md                    # 快速开始指南
+├── ARCHITECTURE.md                   # 架构设计文档
+├── API.md                            # API文档
+└── .gitignore                        # Git忽略配置
+```
+
+## ✨ 核心特性
+
+### ✅ 1. 结构化JSON日志
+- 每个请求都生成标准的JSON格式日志
+- 包含 `traceId`、`uri_group`、`event_class`、`duration` 等关键字段
+- 可直接被 Loki/ELK/Splunk 解析
+
+### ✅ 2. 日志MDC自动注入 (零侵入)
+- LoggingMdcInterceptor 自动填充所有日志字段
+- 业务代码无需修改
+- 支持链路追踪
+
+### ✅ 3. 三个微服务完整示例
+- **API Gateway** - 请求入口,自动路由
+- **Order Service** - 订单CRUD示例
+- **Payment Service** - 支付流程示例
+
+### ✅ 4. Vue前端UI
+- 可视化界面,一键生成测试流量
+- 订单管理:创建、删除、查询订单
+- 支付管理:支付、查询、退款
+- 实时显示操作结果
+
+### ✅ 5. 生产级配置
+- Logstash JSON encoder (标准日志格式)
+- 异步日志处理 (AsyncAppender+1024队列)
+- Docker & Docker Compose支持
+- 环境化配置 (local/docker/prod)
+
+### ✅ 6. 完整文档
+- 快速开始指南 (QUICK_START.md)
+- 详细API文档 (API.md)
+- 架构设计说明 (ARCHITECTURE.md)
+- README 项目文档
+
+## 🚀 快速开始 (三种方式)
+
+### 方式1: 一键启动脚本 (推荐-Windows)
+```powershell
+cd spring-cloud-log-demo
+.\start.ps1
+```
+脚本会自动启动所有服务并打开前端。
+
+### 方式2: 一键启动脚本 (Linux/Mac)
+```bash
+cd spring-cloud-log-demo
+chmod +x start.sh
+./start.sh
+```
+
+### 方式3: 手动启动各服务
+```bash
+# Terminal 1: Gateway
+cd spring-cloud-log-demo
+mvn clean install
+cd shop-recycle-gateway
+mvn spring-boot:run
+
+# Terminal 2: Order Service
+cd spring-recycle-order-service
+mvn spring-boot:run
+
+# Terminal 3: Payment Service
+cd spring-recycle-payment-service
+mvn spring-boot:run
+
+# Terminal 4: Frontend
+cd shop-recycle-web
+npm install && npm run dev
+```
+
+所有服务启动完成后,访问 **http://localhost:5173**
+
+## 📊 生成的日志示例
+
+### 订单创建日志
+```json
+{
+  "ts":"2024-02-06T10:30:45.123Z",
+  "level":"INFO",
+  "logger":"com.shop.recycle.order.service.OrderService",
+  "msg":"订单已创建 orderId=ORD-A1B2C3D4",
+  "traceId":"550e8400-e29b-41d4-a716-446655440000",
+  "uri":"/api/order/create",
+  "uri_group":"/order/*",
+  "duration":"156",
+  "userId":"user123",
+  "event_class":"order",
+  "error":"-",
+  "status":"success",
+  "thread":"http-nio-8081-exec-1",
+  "app":"shop-recycle-order-service",
+  "env":"local"
+}
+```
+
+### 支付日志
+```json
+{
+  "ts":"2024-02-06T10:30:46.234Z",
+  "level":"INFO",
+  "logger":"com.shop.recycle.payment.service.PaymentService",
+  "msg":"订单支付成功 paymentId=PAY-X1Y2Z3W4, orderId=ORD-A1B2C3D4, amount=99.99",
+  "traceId":"550e8400-e29b-41d4-a716-446655440000",
+  "uri":"/api/payment/pay",
+  "uri_group":"/payment/*",
+  "duration":"234",
+  "event_class":"payment",
+  "status":"success",
+  "app":"shop-recycle-payment-service",
+  "env":"local"
+}
+```
+
+## 🎯 测试场景
+
+### 基础流程
+1. 打开前端 http://localhost:5173
+2. 创建订单 (输入用户ID、金额、描述)
+3. 支付订单 (使用创建的订单ID)
+4. 查看后端日志
+
+### 错误场景
+- ❌ 创建负价格订单 → 看到 client_error 日志
+- ❌ 删除已支付订单 → 看到 server_error 日志
+- ❌ 支付失败 (30% 概率) → 重试支付,看到错误恢复
+
+### 高频操作
+- 循环创建订单
+- 批量支付
+- 观察 duration 和 throughput 变化
+
+## 📈 日志监控接入
+
+### 接入Loki (日志聚合)
+```bash
+# docker-compose.yml中添加
+loki:
+  image: grafana/loki:latest
+  ports:
+    - "3100:3100"
+```
+
+### 接入Prometheus (指标收集)
+```bash
+# actuator/prometheus 已内置支持
+# 在docker-compose中添加Prometheus
+prometheus:
+  image: prom/prometheus:latest
+  volumes:
+    - ./prometheus.yml:/etc/prometheus/prometheus.yml
+```
+
+### 接入Grafana (可视化)
+```bash
+# docker-compose中添加Grafana
+grafana:
+  image: grafana/grafana:latest
+  ports:
+    - "3000:3000"
+  # Add Loki as datasource: http://loki:3100
+```
+
+## 🔍 日志查询示例 (Loki)
+
+```logql
+# 所有订单相关请求
+{app="shop-recycle-order-service", event_class="order"}
+
+# 错误请求
+{status="error"}
+
+# 特定用户的请求
+{userId="user123"}
+
+# 按URI分组统计请求数
+sum(rate({json="json"}[5m])) by (uri_group)
+
+# P95延迟
+quantile_over_time(0.95,
+  {app="shop-recycle-order-service"} | json | unwrap duration_ms [5m]
+) by (uri_group)
+```
+
+## 📚 文档导览
+
+- **[QUICK_START.md](QUICK_START.md)** - 5分钟快速上手
+- **[API.md](API.md)** - 完整API文档和cURL示例
+- **[ARCHITECTURE.md](ARCHITECTURE.md)** - 系统架构深度讲解
+- **[README.md](README.md)** - 项目完整说明
+
+## 🔧 开发指南
+
+### 添加新的API端点
+
+1. 在Controller中添加方法
+2. 在Service中实现业务逻辑
+3. 日志会自动被拦截器处理
+4. MDC字段自动填充
+
+**示例:**
+```java
+@PostMapping("/order/cancel/{orderId}")
+public Response<?> cancelOrder(@PathVariable String orderId) {
+    try {
+        orderService.cancelOrder(orderId);
+        return Response.success();
+    } catch (Exception e) {
+        return Response.error(e.getMessage());
+    }
+}
+```
+
+### 自定义日志字段
+
+在业务代码中添加自定义MDC字段:
+```java
+MDC.put("custom_field", "value");
+// 会自动显示在JSON日志中
+```
+
+### 修改日志格式
+
+编辑 `logback-spring.xml` 中的pattern部分:
+```xml
+<pattern>
+{
+  "ts":"%d{...}",
+  "level":"%level",
+  ...
+  "custom_field":"%X{custom_field:-}"
+}
+</pattern>
+```
+
+## ✅ 验证清单
+
+启动前检查:
+- [ ] JDK 1.8+ 已安装
+- [ ] Maven 3.6+ 已安装
+- [ ] Node.js 14+ 已安装
+- [ ] npm 已安装
+- [ ] 没有服务占用 8080, 8081, 8082, 5173 端口
+
+启动后检查:
+- [ ] Gateway: http://localhost:8080 可访问
+- [ ] Order Service: http://localhost:8081/api/order 可访问
+- [ ] Payment Service: http://localhost:8082/api/payment 可访问
+- [ ] Frontend: http://localhost:5173 可访问
+- [ ] 创建订单成功
+- [ ] 支付订单成功 (或正确显示失败)
+- [ ] 后端Terminal显示JSON格式日志
+
+## 🆘 常见问题
+
+### Q: 为什么支付总是失败?
+A: Payment Service有30%的失败率用于测试。多次尝试即可成功。
+
+### Q: 日志为什么没有出现?
+A: 检查 logback-spring.xml 是否正确加载,查看Terminal有没有启动报错。
+
+### Q: 端口被占用怎么办?
+A: 修改 application.yml 中的 server.port 配置,或kill占用端口的进程。
+
+### Q: 前端无法连接后端?
+A: 检查所有后端服务都启动成功,查看浏览器控制台 F12 看有没有CORS错误。
+
+## 🎓 学习价值
+
+这个项目演示了:
+
+1. **Spring Cloud 微服务架构** - Gateway + Service
+2. **日志最佳实践** - MDC + JSON + 异步处理
+3. **链路追踪设计** - traceId 贯穿整个链路
+4. **可观测性** - 结构化日志 + 指标 + 链路
+5. **前后端分离** - Vue + RESTful API
+6. **零侵入日志** - 拦截器注入,业务代码不改动
+7. **生产就绪** - Docker + Config + Error Handling
+
+## 📦 部署选项
+
+### 本地开发
+```bash
+mvn clean install
+mvn spring-boot:run (各服务)
+npm run dev (前端)
+```
+
+### Docker 容器
+```bash
+docker-compose up -d
+# 访问 http://localhost:5173
+```
+
+### Kubernetes
+```bash
+# 使用 Dockerfile 构建镜像
+docker build -t order-service:1.0 shop-recycle-order-service
+# 推送到镜像仓库
+# kubectl apply -f k8s-manifests/
+```
+
+## 🚦 下一步建议
+
+1. **集成真实日志系统**
+   - 部署 Loki + Grafana
+   - 配置 Vector agent 采集日志
+
+2. **添加链路追踪**
+   - 集成 Jaeger 或 Zipkin
+   - 支持 X-B3-TraceId 头
+
+3. **扩展业务功能**
+   - 添加库存服务
+   - 添加用户服务
+   - 实现真实的数据库
+
+4. **完善测试**
+   - 单元测试
+   - 集成测试
+   - 性能测试
+
+5. **生产部署**
+   - CI/CD 流水线
+   - 蓝绿部署
+   - 灾备方案
+
+---
+
+**现在你已经拥有一个完整的、生产级别的日志测试系统!** 🎉
+
+开始使用: `.\start.ps1` (Windows) 或 `./start.sh` (Linux/Mac)
+
+有问题? 查看 QUICK_START.md 或 ARCHITECTURE.md

+ 379 - 0
QUICK_START.md

@@ -0,0 +1,379 @@
+# 快速开始指南
+
+## 📌 前置要求
+
+- **JDK 8+** (测试版本: JDK 1.8)
+- **Maven 3.6+** 
+- **Node.js 14+** 和 **npm**
+
+## ⚡ 一句命令快速启动 (推荐)
+
+在项目根目录运行:
+
+```powershell
+.\start.ps1
+```
+
+或 Linux/Mac:
+```bash
+chmod +x start.sh && ./start.sh
+```
+
+<br/>
+
+## 🚀 30秒快速启动 (Windows PowerShell)
+
+在项目根目录运行:
+
+```powershell
+.\start.ps1
+```
+
+或者手动启动:
+
+### 方式1:Terminal分别启动3个服务
+
+**Terminal 1 - API Gateway:**
+```bash
+cd spring-cloud-log-demo
+mvn clean install
+cd shop-recycle-gateway
+mvn spring-boot:run
+```
+
+**Terminal 2 - Order Service:**
+```bash
+cd spring-cloud-log-demo/shop-recycle-order-service
+mvn spring-boot:run
+```
+
+**Terminal 3 - Payment Service:**
+```bash
+cd spring-cloud-log-demo/shop-recycle-payment-service
+mvn spring-boot:run
+```
+
+**Terminal 4 - Frontend:**
+```bash
+cd spring-cloud-log-demo/shop-recycle-web
+npm install
+npm run dev
+```
+
+## 🎯 核心工作流程
+
+### Step 1: 访问前端界面
+打开浏览器访问 **http://localhost:5173**
+
+你会看到两个标签页:
+- 📦 **订单管理**: 创建、删除订单
+- 💰 **支付管理**: 支付订单、退款
+
+### Step 2: 创建订单
+在"订单管理"标签页中:
+1. 输入 `用户ID`: user123
+2. 输入 `订单金额`: 99.99
+3. 输入 `描述`: test order
+4. 点击 "创建订单" 按钮
+
+**预期结果:**
+- 页面显示 "订单创建成功!"
+- Order Service Terminal会输出 JSON 格式的日志:
+```json
+{
+  "ts":"2024-02-06T10:30:45.123Z",
+  "level":"INFO",
+  "msg":"订单已创建 orderId=ORD-XXXXX",
+  "traceId":"550e8400-e29b-41d4-a716-446655440000",
+  "uri":"/api/order/create",
+  "uri_group":"/order/*",
+  "event_class":"order",
+  "status":"success",
+  "duration":"145"
+}
+```
+
+### Step 3: 支付订单
+在"支付管理"标签页中:
+1. 输入 `订单ID`: 复制上一步的 orderId
+2. 输入 `金额`: 99.99
+3. 选择 `支付方式`: 微信/支付宝/银行卡
+4. 点击 "支付" 按钮
+
+**预期结果:**
+- 页面显示 "支付成功!" 或 "支付失败"
+- Payment Service Terminal会输出支付相关的JSON日志
+- **注意**: Payment Service有30%的失败率用于测试异常处理
+
+### Step 4: 查看日志
+观察三个服务的Terminal输出,你会看到:
+
+**关键字段解析:**
+
+| 字段 | 含义 | 示例 |
+|------|------|------|
+| `traceId` | 全链路追踪ID | `550e8400-e29b-41d4` |
+| `uri_group` | 规范化URL分组 | `/order/*`, `/payment/*` |
+| `event_class` | 业务事件分类 | `order`, `payment` |
+| `duration` | 请求耗时(毫秒) | `145` |
+| `status` | 请求状态 | `success`, `client_error`, `server_error` |
+| `app` | 应用名 | `shop-recycle-order-service` |
+| `env` | 环境 | `local` |
+
+## � 无UI批量模拟 - URL API测试
+
+**不想用UI?一行命令生成100条订单!**
+
+### 快速命令示例
+
+```bash
+# 生成1条随机订单
+curl http://localhost:8080/api/test/order
+
+# 生成10条订单
+curl http://localhost:8080/api/test/order/batch?count=10
+
+# 生成50条订单
+curl http://localhost:8080/api/test/order/batch?count=50
+
+# 生成100条订单 (大量日志测试)
+curl http://localhost:8080/api/test/order/batch?count=100
+
+# 进行10秒压力测试,观察QPS
+curl http://localhost:8080/api/test/order/stress?duration=10
+
+# 生成10笔支付
+curl http://localhost:8080/api/test/payment/batch?count=10
+
+# 支付场景测试:支付 + 退款
+curl "http://localhost:8080/api/test/payment/refund?count=5"
+```
+
+### 完整脚本示例 (快速生成大量日志)
+
+```bash
+#!/bin/bash
+# save as: run-batch-test.sh
+
+echo "🚀 开始批量生成模拟数据..."
+echo ""
+
+echo "📦 生成50个订单..."
+curl -s http://localhost:8080/api/test/order/batch?count=50 | jq '.data | {total, success, error, duration}'
+echo ""
+
+echo "💰 生成30笔支付..."
+curl -s http://localhost:8080/api/test/payment/batch?count=30 | jq '.data | {total, success, error, totalAmount, duration}'
+echo ""
+
+echo "📊 场景测试(10轮)..."
+curl -s http://localhost:8080/api/test/order/scenario?count=10 | jq '.data'
+echo ""
+
+echo "⚡ 压力测试(10秒)..."
+curl -s http://localhost:8080/api/test/order/stress?duration=10 | jq '.data | {requests, success, error, qps, duration}'
+echo ""
+
+echo "✅ 完成!现在检查后端Terminal的JSON日志输出"
+```
+
+运行:
+```bash
+chmod +x run-batch-test.sh
+./run-batch-test.sh
+```
+
+### PowerShell 脚本 (Windows)
+
+```powershell
+# save as: run-batch-test.ps1
+
+Write-Host "🚀 批量生成模拟数据" -ForegroundColor Cyan
+Write-Host ""
+
+Write-Host "📦 生成50个订单..." -ForegroundColor Yellow
+(Invoke-WebRequest -Uri "http://localhost:8080/api/test/order/batch?count=50").Content | ConvertFrom-Json | ForEach-Object { $_.data }
+
+Write-Host ""
+Write-Host "💰 生成30笔支付..." -ForegroundColor Yellow  
+(Invoke-WebRequest -Uri "http://localhost:8080/api/test/payment/batch?count=30").Content | ConvertFrom-Json | ForEach-Object { $_.data }
+
+Write-Host ""
+Write-Host "✅ 完成!" -ForegroundColor Green
+Write-Host "查看后端Terminal窗口的JSON日志输出"
+```
+
+运行:
+```powershell
+.\run-batch-test.ps1
+```
+
+### 更多API选项
+
+详见 [BATCH_TEST_API.md](BATCH_TEST_API.md)
+
+| API | 说明 |
+|-----|------|
+| `GET /api/test/order` | 生成1个订单 |
+| `GET /api/test/order/batch?count=N` | 生成N个订单 |
+| `GET /api/test/order/scenario?count=N` | N轮业务场景测试 |
+| `GET /api/test/order/stress?duration=10` | 10秒压力测试 |
+| `GET /api/test/payment` | 生成1笔支付 |
+| `GET /api/test/payment/batch?count=N` | 生成N笔支付 |
+| `GET /api/test/payment/refund?count=N` | N轮支付+退款测试 |
+| `GET /api/test/payment/stress?duration=10` | 支付压力测试 |
+
+## �📡 测试不同的业务场景
+
+### 场景1: 创建无效订单(价格为负数)
+1. 输入负数价格: -50
+2. 点击 "创建订单"
+3. **预期**: 错误信息 "price must be > 0"
+4. **日志**: status="client_error", error="java.lang.IllegalArgumentException"
+
+### 场景2: 删除已支付订单(应该失败)
+1. 先创建订单
+2. 点击支付标记 "支付订单"
+3. 点击 "删除订单"
+4. **预期**: 错误信息 "Paid order cannot be deleted"
+5. **日志**: 观察错误传播链路
+
+### 场景3: 支付失败重试
+1. 支付订单,如果失败(30%概率)
+2. 再次点击支付按钮
+3. **预期**: 最终会成功(70%成功率)
+4. **日志**: 观察 duration 时间的变化
+
+## 🔑 关键日志观察点
+
+### 打开后端Terminal,观察JSON日志结构:
+
+**订单创建日志:**
+```
+{
+  "ts":"2024-02-06T10:30:45.123Z",
+  "level":"INFO",
+  "logger":"com.shop.recycle.order.service.OrderService",
+  "msg":"订单已创建 orderId=ORD-A1B2C3D4",
+  "traceId":"abc-123-def-456",
+  "uri":"/api/order/create",
+  "uri_group":"/order/*",
+  "duration":"156",
+  "userId":"user123",
+  "event_class":"order",
+  "error":"-",
+  "status":"success",
+  "thread":"http-nio-8081-exec-1",
+  "app":"shop-recycle-order-service",
+  "env":"local"
+}
+```
+
+**日志中的关键观察:**
+1. ✅ **traceId** 会在整个请求链路中保持一致
+2. ✅ **uri_group** 自动规范化(/order/create → /order/*)
+3. ✅ **event_class** 自动分类(order/payment/auth/api)
+4. ✅ **duration** 精确到毫秒
+5. ✅ **status** 自动判定(success/client_error/server_error)
+6. ✅ **app** 标识发出日志的服务
+7. ✅ **env** 标识环境(local/dev/prod)
+
+## 🐛 故障排查
+
+### 问题1: "无法连接到服务"
+```
+检查清单:
+□ 后端服务是否全部启动 (查看Terminal有没有"Started XXXApplication")
+□ 端口是否被占用: netstat -ano | findstr :8080 (Windows)
+□ 防火墙是否阻止本地连接
+□ 前端proxy配置是否正确 (vite.config.js)
+```
+
+### 问题2: "日志未出现"
+```
+检查清单:
+□ logback-spring.xml是否加载成功
+□ logging.level是否配置为DEBUG
+□ Spring Boot是否成功启动(查看Terminal最后一行)
+□ 是否有异常堆栈信息
+```
+
+### 问题3: "支付总是失败"
+```
+这是正常现象! Payment Service有30%的失败率用于测试。
+多次点击"支付"按钮即可成功(70%成功率)。
+```
+
+### 问题4: "前端显示404错误"
+```
+解决方案:
+1. 确认后端API Gateway启动成功 (port 8080)
+2. 清除浏览器缓存 (Ctrl+Shift+Delete)
+3. 检查vite.config.js中proxy配置
+4. 查看浏览器开发者工具 (F12) 的Network标签
+```
+
+## 📊 进阶:接入数据采集系统
+
+### 配置Loki(日志聚合)
+
+在生产环境中,你可以配置Vector采集日志到Loki:
+
+```yaml
+# vector.toml配置示例
+[sources.app_logs]
+type = "file"
+include = ["/var/log/app/*.log"]
+
+[transforms.parse_json]
+type = "remap"
+inputs = ["app_logs"]
+source = "parsed = parse_json!(.message)"
+
+[sinks.loki]
+type = "loki"
+inputs = ["parse_json"]
+endpoint = "http://loki:3100"
+labels.app = "{{ app }}"
+labels.event_class = "{{ event_class }}"
+```
+
+### 配置Prometheus(指标统计)
+
+Vector会自动导出Prometheus metrics:
+
+```promql
+# QPS查询 (Prometheus)
+rate(shop_recycle_requests_total[1m])
+
+# 订单成功率
+rate(shop_recycle_orders_total - shop_recycle_orders_failed_total[1m]) 
+/
+rate(shop_recycle_orders_total[1m])
+
+# P95延迟
+histogram_quantile(0.95, rate(shop_recycle_request_duration_ms_bucket[5m]))
+```
+
+## 📚 更多资源
+
+- [Log.md](../Log.md) - 详细的日志架构设计文档
+- [README.md](README.md) - 项目完整文档
+- [Spring Cloud官方文档](https://spring.io/cloud)
+- [Logstash Logback Encoder](https://github.com/logstash/logstash-logback-encoder)
+
+## ✅ 验证清单
+
+在开始测试前,确保你能看到以下迹象:
+
+- [ ] 三个服务都启动成功,Terminal显示 "Started XXXApplication"
+- [ ] 前端能访问 http://localhost:5173
+- [ ] 能在创建订单后看到 "orderId" 返回
+- [ ] 后端Terminal显示JSON格式的日志 (不是纯文本)
+- [ ] 日志包含 traceId、uri_group、event_class 等字段
+
+一旦都满足,说明你的日志系统已经准备好测试!
+
+---
+
+**需要帮助?** 查看 README.md 获取完整文档

+ 254 - 0
README.md

@@ -0,0 +1,254 @@
+# Spring Cloud日志测试演示系统
+
+完整的Spring Cloud + Vue前端日志系统,用于测试和演示结构化JSON日志的生成。
+
+## ✨ 核心特性
+
+✅ **无UI快速测试** - 一条命令生成100个订单,观看JSON日志  
+✅ **结构化JSON日志** - 完全兼容Loki/ELK/Splunk的日志格式  
+✅ **零侵入MDC** - 业务代码无需改动,拦截器自动注入日志字段  
+✅ **前后端完整** - Vue UI + Spring Cloud Gateway + 微服务  
+✅ **多种测试模式** - 单条、批量、场景、压力测试  
+✅ **生产级配置** - Docker支持、日志异步处理、环境化配置  
+
+## 📋 项目结构
+
+```
+spring-cloud-log-demo/
+├── shop-recycle-common/              # 公共模块 (日志拦截器、DTO)
+├── shop-recycle-gateway/             # API网关 (端口 8080)
+├── shop-recycle-order-service/       # 订单服务 (端口 8081)
+├── shop-recycle-payment-service/     # 支付服务 (端口 8082)
+└── shop-recycle-web/                 # Vue前端 (端口 5173)
+```
+
+## 🚀 快速开始
+
+### 1. 后端服务启动
+
+**前置条件:**
+- JDK 1.8+ 
+- Maven 3.6+
+
+**构建项目:**
+```bash
+cd spring-cloud-log-demo
+mvn clean install
+```
+
+**快速命令 - 启动所有服务 (Windows PowerShell):**
+```powershell
+.\start.ps1
+```
+
+或 Linux/Mac:
+```bash
+chmod +x start.sh && ./start.sh
+```
+
+### 2. 快速测试 (无需UI!)
+
+**一句命令生成50个订单,观看日志:**
+```bash
+curl http://localhost:8080/api/test/order/batch?count=50
+```
+
+**在后端Terminal中立即看到类似的JSON日志:**
+```json
+{
+  "ts":"2024-02-06T10:30:45.123Z",
+  "level":"INFO",
+  "msg":"测试订单生成成功 orderId=ORD-A1B2C3D4",
+  "traceId":"550e8400-e29b-41d4-a716",
+  "uri_group":"/test/*",
+  "event_class":"order",
+  "status":"success",
+  "duration":"145",
+  "app":"shop-recycle-order-service"
+}
+```
+
+### 3. 或使用前端UI
+
+访问 `http://localhost:5173`,点击按钮一键生成数据。
+
+## 📚 详细文档
+
+- **[QUICK_START.md](QUICK_START.md)** - 5分钟快速上手 ⭐ 新手必读
+- **[BATCH_TEST_API.md](BATCH_TEST_API.md)** - 无UI批量测试API (一条命令test)
+- **[API.md](API.md)** - 完整API文档
+- **[ARCHITECTURE.md](ARCHITECTURE.md)** - 系统架构设计
+
+##  日志观察
+
+### 生成的日志格式 (JSON)
+
+每个请求都会生成以下格式的结构化日志:
+
+```json
+{
+  "ts":"2024-02-06T10:30:45.123Z",
+  "level":"INFO",
+  "logger":"com.shop.recycle.order.service.OrderService",
+  "msg":"订单已创建 orderId=ORD-12345678",
+  "traceId":"550e8400-e29b-41d4-a716-446655440000",
+  "uri":"/api/order/create",
+  "uri_group":"/order/*",
+  "duration":"145",
+  "userId":"user123",
+  "event_class":"order",
+  "error":"-",
+  "status":"success",
+  "thread":"http-nio-8081-exec-1",
+  "app":"shop-recycle-order-service",
+  "env":"local"
+}
+```
+
+### 关键字段说明
+
+| 字段 | 含义 | 示例 |
+|------|------|------|
+| `traceId` | 链路追踪ID | 550e8400-e29b-41d4 |
+| `uri_group` | 规范化URI分组 | /order/* |
+| `event_class` | 业务事件分类 | order, payment |
+| `duration` | 请求耗时(ms) | 145 |
+| `status` | 请求状态 | success, client_error, server_error |
+| `app` | 应用名 | shop-recycle-order-service |
+| `env` | 环境标识 | local, dev, prod |
+
+## 🧪 Testing Scenarios 测试场景
+
+### Order Service 订单服务
+
+**场景1: 创建订单**
+```bash
+curl -X POST http://localhost:8080/api/order/create \
+  -H "Content-Type: application/json" \
+  -H "X-User-Id: user001" \
+  -d '{
+    "userId":"user001",
+    "price":99.99,
+    "description":"test order"
+  }'
+```
+
+**场景2: 删除订单**
+```bash
+curl -X POST http://localhost:8080/api/order/delete/ORD-XXXXXXXX
+```
+
+### Payment Service 支付服务
+
+**场景3: 支付订单**
+```bash
+curl -X POST http://localhost:8080/api/payment/pay \
+  -H "Content-Type: application/json" \
+  -d '{
+    "orderId":"ORD-XXXXXXXX",
+    "amount":99.99,
+    "paymentMethod":"WECHAT"
+  }'
+```
+
+**场景4: 退款**
+```bash
+curl -X POST http://localhost:8080/api/payment/refund/PAY-XXXXXXXX
+```
+
+## 📝 实现的日志功能
+
+✅ **自动MDC注入** - LoggingMdcInterceptor自动填充:
+- traceId (链路追踪)
+- uri / uri_group (URL规范化)
+- event_class (事件分类)
+- duration (请求耗时)
+- status (HTTP状态)
+
+✅ **异步日志输出** - AsyncAppender确保业务不阻塞
+
+✅ **JSON结构化输出** - LogstashEncoder生成可被Loki/ELK解析的JSON
+
+✅ **零业务代码侵入** - 拦截器层统一处理,业务代码无需改动
+
+## 🔍 日志监控查询
+
+### 命令行查询示例
+
+查看所有订单相关日志:
+```bash
+grep "event_class.*order" logs/app.log | jq .
+```
+
+查看失败请求:
+```bash
+grep "status.*error" logs/app.log | jq '.[] | {uri_group, error, duration}'
+```
+
+P95延迟分析:
+```bash
+grep "duration" logs/app.log | jq '.duration' | sort -n | tail -5
+```
+
+### 接入Loki/Grafana后的查询
+
+订单QPS (Loki LogQL):
+```logql
+sum by (uri_group) (rate({app="shop-recycle-order-service", event_class="order"}[1m]))
+```
+
+错误率告警 (Prometheus):
+```promql
+(rate(requests_total{status="error"}[5m]) / rate(requests_total[5m])) > 0.05
+```
+
+## 📦 打包部署
+
+### Docker部署
+
+构建Gateway镜像:
+```bash
+cd shop-recycle-gateway
+mvn clean package docker:build
+```
+
+启动容器:
+```bash
+docker run -d -p 8080:8080 \
+  -e SPRING_PROFILES_ACTIVE=prod \
+  shop-recycle-gateway:1.0.0
+```
+
+## 🔧 故障排查
+
+**问题1: 前端无法连接后端**
+```
+检查: 
+1. 所有后端服务是否启动
+2. http://localhost:8080 是否可访问
+3. 浏览器控制台是否有CORS错误
+```
+
+**问题2: 日志未出现**
+```
+1. 检查logback-spring.xml是否正确加载
+2. 确认logging.level配置为DEBUG
+3. 查看启动日志是否有错误
+```
+
+**问题3: 支付服务报错**
+```
+Payment Service有30%的失败率用于测试异常处理
+重新尝试支付请求即可
+```
+
+## 📚 相关资源
+
+- [Spring Cloud官方文档](https://spring.io/cloud)
+- [Logstash Logback Encoder](https://github.com/logstash/logstash-logback-encoder)
+- [Loki日志聚合](https://grafana.com/oss/loki/)
+- [Vue 3文档](https://vuejs.org/)
+
+## 许可证
+
+MIT License

+ 48 - 0
docker-compose.yml

@@ -0,0 +1,48 @@
+version: '3.8'
+
+services:
+  # API Gateway
+  gateway:
+    build:
+      context: ./shop-recycle-gateway
+      dockerfile: Dockerfile
+    ports:
+      - "8080:8080"
+    environment:
+      - SPRING_PROFILES_ACTIVE=docker
+      - SPRING_APPLICATION_NAME=shop-recycle-gateway
+    depends_on:
+      - order-service
+      - payment-service
+    networks:
+      - app-network
+
+  # Order Service
+  order-service:
+    build:
+      context: ./shop-recycle-order-service
+      dockerfile: Dockerfile
+    ports:
+      - "8081:8081"
+    environment:
+      - SPRING_PROFILES_ACTIVE=docker
+      - SPRING_APPLICATION_NAME=shop-recycle-order-service
+    networks:
+      - app-network
+
+  # Payment Service
+  payment-service:
+    build:
+      context: ./shop-recycle-payment-service
+      dockerfile: Dockerfile
+    ports:
+      - "8082:8082"
+    environment:
+      - SPRING_PROFILES_ACTIVE=docker
+      - SPRING_APPLICATION_NAME=shop-recycle-payment-service
+    networks:
+      - app-network
+
+networks:
+  app-network:
+    driver: bridge

+ 82 - 0
pom.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.shop.recycle</groupId>
+    <artifactId>spring-cloud-log-demo</artifactId>
+    <version>1.0.0</version>
+    <packaging>pom</packaging>
+    <name>Spring Cloud Log Demo</name>
+
+    <modules>
+        <module>shop-recycle-gateway</module>
+        <module>shop-recycle-order-service</module>
+        <module>shop-recycle-payment-service</module>
+        <module>shop-recycle-common</module>
+    </modules>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <spring-boot.version>2.7.0</spring-boot.version>
+        <spring-cloud.version>2021.0.0</spring-cloud.version>
+        <spring-cloud-alibaba.version>2021.0.3.0</spring-cloud-alibaba.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring-cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${spring-cloud-alibaba.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <!-- Log JSON -->
+        <dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+            <version>7.2</version>
+        </dependency>
+        
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.24</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 22 - 0
shop-recycle-common/pom.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.shop.recycle</groupId>
+        <artifactId>spring-cloud-log-demo</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>shop-recycle-common</artifactId>
+    <name>Shop Recycle Common</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 20 - 0
shop-recycle-common/src/main/java/com/shop/recycle/common/config/WebConfig.java

@@ -0,0 +1,20 @@
+package com.shop.recycle.common.config;
+
+import com.shop.recycle.common.interceptor.LoggingMdcInterceptor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Web配置 - 注册日志MDC拦截器
+ */
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new LoggingMdcInterceptor())
+                .addPathPatterns("/**")
+                .excludePathPatterns("/health", "/actuator/**");
+    }
+}

+ 51 - 0
shop-recycle-common/src/main/java/com/shop/recycle/common/dto/Response.java

@@ -0,0 +1,51 @@
+package com.shop.recycle.common.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 统一响应对象
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Response<T> {
+    private int code;
+    private String msg;
+    private T data;
+
+    public static <T> Response<T> success(T data) {
+        return Response.<T>builder()
+                .code(0)
+                .msg("success")
+                .data(data)
+                .build();
+    }
+
+    public static <T> Response<T> success() {
+        return Response.<T>builder()
+                .code(0)
+                .msg("success")
+                .data(null)
+                .build();
+    }
+
+    public static <T> Response<T> error(int code, String msg) {
+        return Response.<T>builder()
+                .code(code)
+                .msg(msg)
+                .data(null)
+                .build();
+    }
+
+    public static <T> Response<T> error(String msg) {
+        return Response.<T>builder()
+                .code(-1)
+                .msg(msg)
+                .data(null)
+                .build();
+    }
+}

+ 105 - 0
shop-recycle-common/src/main/java/com/shop/recycle/common/interceptor/LoggingMdcInterceptor.java

@@ -0,0 +1,105 @@
+package com.shop.recycle.common.interceptor;
+
+import org.slf4j.MDC;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.UUID;
+
+/**
+ * 日志MDC上下文拦截器
+ * 自动填充traceId、uri、uri_group、event_class等字段
+ */
+public class LoggingMdcInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        // 处理 traceId
+        String traceId = MDC.get("traceId");
+        if (traceId == null) {
+            traceId = request.getHeader("X-B3-TraceId");
+            if (traceId == null) {
+                traceId = UUID.randomUUID().toString();
+            }
+            MDC.put("traceId", traceId);
+        }
+
+        // 处理 uri 和 uri_group
+        String uri = request.getRequestURI();
+        MDC.put("uri", uri);
+        MDC.put("uri_group", normalizeUri(uri));
+
+        // 处理 userId
+        String userId = request.getHeader("X-User-Id");
+        if (userId != null) {
+            MDC.put("userId", userId);
+        }
+
+        // 处理 event_class
+        MDC.put("event_class", deriveEventClass(uri));
+
+        // 记录开始时间
+        MDC.put("start_time", String.valueOf(System.currentTimeMillis()));
+
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+        try {
+            // 计算耗时
+            String startTime = MDC.get("start_time");
+            if (startTime != null) {
+                long duration = System.currentTimeMillis() - Long.parseLong(startTime);
+                MDC.put("duration", String.valueOf(duration));
+            }
+
+            // 处理错误信息
+            if (ex != null || response.getStatus() >= 400) {
+                MDC.put("error", ex != null ? ex.toString() : "HTTP_" + response.getStatus());
+            }
+
+            // 处理状态
+            if (response.getStatus() >= 400) {
+                MDC.put("status", response.getStatus() >= 500 ? "server_error" : "client_error");
+            } else {
+                MDC.put("status", "success");
+            }
+        } finally {
+            MDC.clear();
+        }
+    }
+
+    /**
+     * 规范化URI为uri_group
+     */
+    private String normalizeUri(String uri) {
+        if (uri.startsWith("/order/")) {
+            return "/order/*";
+        }
+        if (uri.startsWith("/payment/")) {
+            return "/payment/*";
+        }
+        if (uri.startsWith("/gateway/")) {
+            return "/gateway/*";
+        }
+        return uri;
+    }
+
+    /**
+     * 从URI衍生event_class
+     */
+    private String deriveEventClass(String uri) {
+        if (uri.contains("order")) {
+            return "order";
+        }
+        if (uri.contains("payment")) {
+            return "payment";
+        }
+        if (uri.contains("login")) {
+            return "auth";
+        }
+        return "api";
+    }
+}

+ 19 - 0
shop-recycle-gateway/Dockerfile

@@ -0,0 +1,19 @@
+FROM maven:3.8-openjdk-8 AS builder
+
+WORKDIR /build
+
+COPY pom.xml .
+RUN mvn dependency:go-offline
+
+COPY src src
+RUN mvn clean package -DskipTests
+
+FROM openjdk:8-jre-slim
+
+WORKDIR /app
+
+COPY --from=builder /build/target/*.jar app.jar
+
+EXPOSE 8080
+
+ENTRYPOINT ["java", "-jar", "app.jar"]

+ 38 - 0
shop-recycle-gateway/pom.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.shop.recycle</groupId>
+        <artifactId>spring-cloud-log-demo</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>shop-recycle-gateway</artifactId>
+    <name>Shop Recycle Gateway</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.shop.recycle</groupId>
+            <artifactId>shop-recycle-common</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-gateway</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 17 - 0
shop-recycle-gateway/src/main/java/com/shop/recycle/gateway/GatewayApplication.java

@@ -0,0 +1,17 @@
+package com.shop.recycle.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * API网关启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class GatewayApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(GatewayApplication.class, args);
+    }
+}

+ 72 - 0
shop-recycle-gateway/src/main/resources/application.yml

@@ -0,0 +1,72 @@
+spring:
+  application:
+    name: shop-recycle-gateway
+  profiles:
+    active: local
+  cloud:
+    gateway:
+      routes:
+        # 订单服务路由
+        - id: order-service
+          uri: http://localhost:8081
+          predicates:
+            - Path=/api/order/**
+          filters:
+            - StripPrefix=1
+        
+        # 支付服务路由
+        - id: payment-service
+          uri: http://localhost:8082
+          predicates:
+            - Path=/api/payment/**
+          filters:
+            - StripPrefix=1
+
+server:
+  port: 8080
+  servlet:
+    context-path: /
+
+eureka:
+  client:
+    enabled: false
+  instance:
+    prefer-ip-address: true
+
+logging:
+  level:
+    root: INFO
+    com.shop.recycle: DEBUG
+    org.springframework.cloud.gateway: DEBUG
+
+---
+spring:
+  config:
+    activate:
+      on-profile: docker
+  cloud:
+    gateway:
+      routes:
+        # 订单服务路由
+        - id: order-service
+          uri: http://order-service:8081
+          predicates:
+            - Path=/api/order/**
+          filters:
+            - StripPrefix=1
+        
+        # 支付服务路由
+        - id: payment-service
+          uri: http://payment-service:8082
+          predicates:
+            - Path=/api/payment/**
+          filters:
+            - StripPrefix=1
+
+server:
+  port: 8080
+
+logging:
+  level:
+    root: INFO
+    com.shop.recycle: DEBUG

+ 45 - 0
shop-recycle-gateway/src/main/resources/logback-spring.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <springProperty name="app" source="spring.application.name" />
+    <springProperty name="env" source="spring.profiles.active" defaultValue="local" />
+
+    <!-- 异步Appender,将日志异步输出到JSON格式 -->
+    <appender name="ASYNC_JSON" class="ch.qos.logback.classic.AsyncAppender">
+        <queueSize>1024</queueSize>
+        <discardingThreshold>0</discardingThreshold>
+        <appender-ref ref="JSON_CONSOLE" />
+    </appender>
+
+    <!-- JSON格式输出到控制台 -->
+    <appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
+            <includeContext>true</includeContext>
+            <customFields>{"app":"${app}","env":"${env}"}</customFields>
+            <provider class="net.logstash.logback.composite.loggingevent.LoggingEventJsonProvider">
+                <pattern>
+{
+  "ts":"%d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z'}",
+  "level":"%level",
+  "logger":"%logger{36}",
+  "msg":"%msg",
+  "traceId":"%X{traceId:-}",
+  "uri":"%X{uri:-}",
+  "uri_group":"%X{uri_group:-}",
+  "duration":"%X{duration:-}",
+  "userId":"%X{userId:-}",
+  "event_class":"%X{event_class:-}",
+  "error":"%X{error:-}",
+  "status":"%X{status:-}",
+  "thread":"%thread"
+}
+                </pattern>
+            </provider>
+        </encoder>
+    </appender>
+
+    <root level="INFO">
+        <appender-ref ref="ASYNC_JSON" />
+    </root>
+
+    <logger name="com.shop.recycle" level="DEBUG" />
+</configuration>

+ 19 - 0
shop-recycle-order-service/Dockerfile

@@ -0,0 +1,19 @@
+FROM maven:3.8-openjdk-8 AS builder
+
+WORKDIR /build
+
+COPY pom.xml .
+RUN mvn dependency:go-offline
+
+COPY src src
+RUN mvn clean package -DskipTests
+
+FROM openjdk:8-jre-slim
+
+WORKDIR /app
+
+COPY --from=builder /build/target/*.jar app.jar
+
+EXPOSE 8081
+
+ENTRYPOINT ["java", "-jar", "app.jar"]

+ 43 - 0
shop-recycle-order-service/pom.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.shop.recycle</groupId>
+        <artifactId>spring-cloud-log-demo</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>shop-recycle-order-service</artifactId>
+    <name>Shop Recycle Order Service</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.shop.recycle</groupId>
+            <artifactId>shop-recycle-common</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 19 - 0
shop-recycle-order-service/src/main/java/com/shop/recycle/order/OrderServiceApplication.java

@@ -0,0 +1,19 @@
+package com.shop.recycle.order;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+/**
+ * 订单服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients
+public class OrderServiceApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(OrderServiceApplication.class, args);
+    }
+}

+ 132 - 0
shop-recycle-order-service/src/main/java/com/shop/recycle/order/controller/OrderController.java

@@ -0,0 +1,132 @@
+package com.shop.recycle.order.controller;
+
+import com.shop.recycle.common.dto.Response;
+import com.shop.recycle.order.dto.CreateOrderRequest;
+import com.shop.recycle.order.dto.OrderResponse;
+import com.shop.recycle.order.service.OrderService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 订单控制器
+ */
+@RestController
+@RequestMapping("/order")
+@CrossOrigin(origins = "*", maxAge = 3600)
+public class OrderController {
+
+    @Autowired
+    private OrderService orderService;
+
+    /**
+     * 创建订单
+     */
+    @PostMapping("/create")
+    public Response<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) {
+        try {
+            OrderResponse order = orderService.createOrder(request);
+            return Response.success(order);
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 删除订单
+     */
+    @PostMapping("/delete/{orderId}")
+    public Response<?> deleteOrder(@PathVariable String orderId) {
+        try {
+            orderService.deleteOrder(orderId);
+            return Response.success();
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 获取订单详情
+     */
+    @GetMapping("/{orderId}")
+    public Response<OrderResponse> getOrder(@PathVariable String orderId) {
+        try {
+            OrderResponse order = orderService.getOrder(orderId);
+            return Response.success(order);
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 支付订单(这里只是标记状态,实际支付由payment-service处理)
+     */
+    @PostMapping("/{orderId}/pay")
+    public Response<?> payOrder(@PathVariable String orderId) {
+        try {
+            orderService.updateOrderStatus(orderId, "PAID");
+            return Response.success();
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 测试接口:生成模拟订单
+     * 使用方式:
+     *   - GET /order/test/mock?count=5      生成5条订单
+     *   - GET /order/test/mock              生成1条订单
+     */
+    @GetMapping("/test/mock")
+    public Response<List<OrderResponse>> createMockOrders(
+            @RequestParam(value = "count", defaultValue = "1") Integer count) {
+        try {
+            List<OrderResponse> orders = new ArrayList<>();
+            
+            for (int i = 0; i < count; i++) {
+                CreateOrderRequest mockRequest = orderService.generateMockOrderRequest();
+                OrderResponse order = orderService.createOrder(mockRequest);
+                orders.add(order);
+                
+                // 避免请求过快
+                if (i < count - 1) {
+                    Thread.sleep(100);
+                }
+            }
+            
+            return Response.success(orders);
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * URL测试示例页面
+     */
+    @GetMapping("/test/help")
+    public String testHelp() {
+        return "<html>\n" +
+                "<head><meta charset='UTF-8'><title>Order API Test Help</title></head>\n" +
+                "<body style='font-family:Arial;margin:20px'>\n" +
+                "<h1>📦 订单服务 - URL测试接口</h1>\n" +
+                "<p>使用以下URL直接生成模拟订单消息(产生日志):</p>\n" +
+                "<h2>单个订单</h2>\n" +
+                "<p><a href='http://localhost:8081/api/order/test/mock' target='_blank'>http://localhost:8081/api/order/test/mock</a></p>\n" +
+                "<p>生成1条随机订单</p>\n" +
+                "<h2>批量订单</h2>\n" +
+                "<p><a href='http://localhost:8081/api/order/test/mock?count=10' target='_blank'>http://localhost:8081/api/order/test/mock?count=10</a></p>\n" +
+                "<p>生成10条随机订单</p>\n" +
+                "<p><a href='http://localhost:8081/api/order/test/mock?count=100' target='_blank'>http://localhost:8081/api/order/test/mock?count=100</a></p>\n" +
+                "<p>生成100条随机订单(用于压力测试)</p>\n" +
+                "<h2>其他操作</h2>\n" +
+                "<p><strong>查询订单:</strong> GET <code>/api/order/{orderId}</code></p>\n" +
+                "<p><strong>支付订单:</strong> POST <code>/api/order/{orderId}/pay</code></p>\n" +
+                "<p><strong>删除订单:</strong> POST <code>/api/order/delete/{orderId}</code></p>\n" +
+                "<hr/>\n" +
+                "<p>💡 查看后端Terminal可以看到生成的JSON日志</p>\n" +
+                "</body>\n" +
+                "</html>";
+    }
+}

+ 225 - 0
shop-recycle-order-service/src/main/java/com/shop/recycle/order/controller/OrderTestController.java

@@ -0,0 +1,225 @@
+package com.shop.recycle.order.controller;
+
+import com.shop.recycle.common.dto.Response;
+import com.shop.recycle.order.dto.CreateOrderRequest;
+import com.shop.recycle.order.dto.OrderResponse;
+import com.shop.recycle.order.service.OrderService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 测试控制器 - 用于生成随机模拟数据和测试日志系统
+ */
+@RestController
+@RequestMapping("/test")
+@CrossOrigin(origins = "*", maxAge = 3600)
+public class OrderTestController {
+
+    private static final Logger log = LoggerFactory.getLogger(OrderTestController.class);
+
+    @Autowired
+    private OrderService orderService;
+
+    // 预定义的用户名
+    private static final String[] USER_NAMES = {"alice", "bob", "charlie", "david", "emma", "frank", "grace", "henry"};
+    private static final String[] DESCRIPTIONS = {
+        "回收电子产品", "二手家电处理", "旧家具回收", "废金属回收",
+        "包装材料回收", "废纸回收", "塑料回收", "玻璃回收"
+    };
+
+    /**
+     * 生成一条随机订单
+     * GET /test/order
+     */
+    @GetMapping("/order")
+    public Response<OrderResponse> generateRandomOrder() {
+        try {
+            CreateOrderRequest request = generateRandomOrderRequest();
+            OrderResponse order = orderService.createOrder(request);
+            
+            MDC.put("event_class", "order");
+            log.info("测试订单生成成功 orderId={}", order.getOrderId());
+            
+            return Response.success(order);
+        } catch (Exception e) {
+            MDC.put("event_class", "order");
+            log.error("测试订单生成失败", e);
+            return Response.error("生成失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量生成订单
+     * GET /test/order/batch?count=10
+     * GET /test/order/batch (默认10个)
+     */
+    @GetMapping("/order/batch")
+    public Response<Map<String, Object>> batchGenerateOrders(
+            @RequestParam(defaultValue = "10") int count) {
+        
+        long startTime = System.currentTimeMillis();
+        List<OrderResponse> orders = new ArrayList<>();
+        int successCount = 0;
+        int errorCount = 0;
+        
+        log.info("开始批量生成 {} 个订单", count);
+        
+        for (int i = 0; i < count; i++) {
+            try {
+                CreateOrderRequest request = generateRandomOrderRequest();
+                OrderResponse order = orderService.createOrder(request);
+                orders.add(order);
+                successCount++;
+                
+                // 模拟一些请求间隔,避免过快
+                Thread.sleep(50);
+            } catch (Exception e) {
+                errorCount++;
+                log.warn("第 {} 个订单生成失败", i + 1, e);
+            }
+        }
+        
+        long duration = System.currentTimeMillis() - startTime;
+        
+        MDC.put("event_class", "order");
+        log.info("批量生成完成 total={}, success={}, error={}, duration={}ms",
+                count, successCount, errorCount, duration);
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("total", count);
+        result.put("success", successCount);
+        result.put("error", errorCount);
+        result.put("duration", duration);
+        result.put("orders", orders);
+        
+        return Response.success(result);
+    }
+
+    /**
+     * 批量生成并分别执行不同操作的订单
+     * GET /test/order/scenario?count=5
+     * 模拟: 创建 -> 查询 -> 标记支付 -> 尝试删除(失败)
+     */
+    @GetMapping("/order/scenario")
+    public Response<Map<String, Object>> scenarioTest(
+            @RequestParam(defaultValue = "5") int count) {
+        
+        long startTime = System.currentTimeMillis();
+        int operationCount = 0;
+        
+        log.info("开始场景测试,共 {} 轮", count);
+        
+        for (int i = 0; i < count; i++) {
+            try {
+                // 1. 创建订单
+                CreateOrderRequest request = generateRandomOrderRequest();
+                OrderResponse order = orderService.createOrder(request);
+                operationCount++;
+                
+                // 2. 查询订单
+                orderService.getOrder(order.getOrderId());
+                operationCount++;
+                
+                // 3. 标记支付
+                orderService.updateOrderStatus(order.getOrderId(), "PAID");
+                operationCount++;
+                
+                // 4. 尝试删除(应该失败)
+                try {
+                    orderService.deleteOrder(order.getOrderId());
+                } catch (IllegalArgumentException e) {
+                    log.info("预期的删除失败: {}", e.getMessage());
+                }
+                operationCount++;
+                
+                Thread.sleep(100);
+            } catch (Exception e) {
+                log.error("场景测试出错", e);
+            }
+        }
+        
+        long duration = System.currentTimeMillis() - startTime;
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("scenario", "create -> query -> pay -> delete(fail)");
+        result.put("rounds", count);
+        result.put("operations", operationCount);
+        result.put("duration", duration);
+        
+        MDC.put("event_class", "order");
+        log.info("场景测试完成 rounds={}, operations={}, duration={}ms", count, operationCount, duration);
+        
+        return Response.success(result);
+    }
+
+    /**
+     * 高频请求压测
+     * GET /test/order/stress?duration=10 (持续10秒)
+     */
+    @GetMapping("/order/stress")
+    public Response<Map<String, Object>> stressTest(
+            @RequestParam(defaultValue = "10") int duration) {
+        
+        long startTime = System.currentTimeMillis();
+        long endTime = startTime + (duration * 1000L);
+        int requestCount = 0;
+        int successCount = 0;
+        int errorCount = 0;
+        
+        log.info("开始压力测试,持续 {} 秒", duration);
+        
+        while (System.currentTimeMillis() < endTime) {
+            try {
+                CreateOrderRequest request = generateRandomOrderRequest();
+                orderService.createOrder(request);
+                successCount++;
+                requestCount++;
+            } catch (Exception e) {
+                errorCount++;
+                requestCount++;
+            }
+        }
+        
+        long actualDuration = System.currentTimeMillis() - startTime;
+        double qps = (requestCount * 1000.0) / actualDuration;
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("duration", actualDuration);
+        result.put("requests", requestCount);
+        result.put("success", successCount);
+        result.put("error", errorCount);
+        result.put("qps", String.format("%.2f", qps));
+        
+        MDC.put("event_class", "order");
+        log.info("压力测试完成 requests={}, qps={}, duration={}ms", requestCount, String.format("%.2f", qps), actualDuration);
+        
+        return Response.success(result);
+    }
+
+    /**
+     * 生成随机订单请求
+     */
+    private CreateOrderRequest generateRandomOrderRequest() {
+        ThreadLocalRandom random = ThreadLocalRandom.current();
+        
+        String userId = USER_NAMES[random.nextInt(USER_NAMES.length)] + "-" + 
+                       random.nextInt(10000);
+        Double price = (random.nextDouble() * 500 + 10);
+        String description = DESCRIPTIONS[random.nextInt(DESCRIPTIONS.length)];
+        
+        return CreateOrderRequest.builder()
+                .userId(userId)
+                .price(price)
+                .description(description)
+                .build();
+    }
+}

+ 19 - 0
shop-recycle-order-service/src/main/java/com/shop/recycle/order/dto/CreateOrderRequest.java

@@ -0,0 +1,19 @@
+package com.shop.recycle.order.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 创建订单请求
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class CreateOrderRequest {
+    private String userId;
+    private Double price;
+    private String description;
+}

+ 22 - 0
shop-recycle-order-service/src/main/java/com/shop/recycle/order/dto/OrderResponse.java

@@ -0,0 +1,22 @@
+package com.shop.recycle.order.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 订单响应
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class OrderResponse {
+    private String orderId;
+    private String userId;
+    private Double price;
+    private String status; // CREATED, PAID, CANCELLED
+    private String description;
+    private Long createdAt;
+}

+ 149 - 0
shop-recycle-order-service/src/main/java/com/shop/recycle/order/service/OrderService.java

@@ -0,0 +1,149 @@
+package com.shop.recycle.order.service;
+
+import com.shop.recycle.order.dto.CreateOrderRequest;
+import com.shop.recycle.order.dto.OrderResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * 订单业务逻辑
+ */
+@Service
+public class OrderService {
+
+    private static final Logger log = LoggerFactory.getLogger(OrderService.class);
+    private static final Random random = new Random();
+    
+    // 模拟数据库存储
+    private static final Map<String, OrderResponse> orderDB = new HashMap<>();
+
+    // 模拟数据集合
+    private static final String[] USER_NAMES = {"alice", "bob", "charlie", "david", "emma", "frank", "grace", "henry", "ivan", "julia"};
+    private static final String[] DESCRIPTIONS = {"回收电子产品", "二手家电处理", "旧家具回收", "废金属回收", "包装材料回收", "废纸回收", "塑料回收", "玻璃回收"};
+
+    /**
+     * 生成模拟订单请求
+     */
+    public CreateOrderRequest generateMockOrderRequest() {
+        String userId = USER_NAMES[random.nextInt(USER_NAMES.length)] + "-" + random.nextInt(10000);
+        double price = (random.nextDouble() * 500) + 10;
+        String description = DESCRIPTIONS[random.nextInt(DESCRIPTIONS.length)];
+        
+        return CreateOrderRequest.builder()
+                .userId(userId)
+                .price(Math.round(price * 100.0) / 100.0)
+                .description(description)
+                .build();
+    }
+
+    /**
+     * 创建订单
+     */
+    public OrderResponse createOrder(CreateOrderRequest request) {
+        log.info("订单创建逻辑开始 [{}]", request.getDescription());
+        
+        String orderId = "ORD-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
+        long now = System.currentTimeMillis();
+
+        try {
+            // 模拟业务处理
+            if (request.getPrice() <= 0) {
+                MDC.put("event_class", "order");
+                log.error("订单价格不合法: {}", request.getPrice());
+                throw new IllegalArgumentException("price must be > 0");
+            }
+
+            OrderResponse order = OrderResponse.builder()
+                    .orderId(orderId)
+                    .userId(request.getUserId())
+                    .price(request.getPrice())
+                    .description(request.getDescription())
+                    .status("CREATED")
+                    .createdAt(now)
+                    .build();
+
+            // 模拟保存到数据库
+            orderDB.put(orderId, order);
+            
+            MDC.put("event_class", "order");
+            log.info("订单已创建 orderId={}", orderId);
+            
+            return order;
+        } catch (Exception e) {
+            MDC.put("event_class", "order");
+            log.error("订单创建失败", e);
+            throw e;
+        }
+    }
+
+    /**
+     * 删除订单
+     */
+    public void deleteOrder(String orderId) {
+        log.info("订单删除逻辑开始 orderId={}", orderId);
+        
+        try {
+            OrderResponse order = orderDB.get(orderId);
+            if (order == null) {
+                MDC.put("event_class", "order");
+                log.warn("订单不存在 orderId={}", orderId);
+                throw new IllegalArgumentException("Order not found: " + orderId);
+            }
+
+            if ("PAID".equals(order.getStatus())) {
+                MDC.put("event_class", "order");
+                log.error("已支付订单无法删除 orderId={}", orderId);
+                throw new IllegalArgumentException("Paid order cannot be deleted");
+            }
+
+            orderDB.remove(orderId);
+            MDC.put("event_class", "order");
+            log.info("订单已删除 orderId={}", orderId);
+        } catch (Exception e) {
+            MDC.put("event_class", "order");
+            log.error("订单删除失败 orderId={}", orderId, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 获取订单详情
+     */
+    public OrderResponse getOrder(String orderId) {
+        log.info("查询订单 orderId={}", orderId);
+        
+        OrderResponse order = orderDB.get(orderId);
+        if (order == null) {
+            MDC.put("event_class", "order");
+            log.warn("订单不存在 orderId={}", orderId);
+            throw new IllegalArgumentException("Order not found: " + orderId);
+        }
+        
+        return order;
+    }
+
+    /**
+     * 更新订单状态
+     */
+    public void updateOrderStatus(String orderId, String status) {
+        log.info("更新订单状态 orderId={}, status={}", orderId, status);
+        
+        OrderResponse order = orderDB.get(orderId);
+        if (order == null) {
+            MDC.put("event_class", "order");
+            log.warn("订单不存在 orderId={}", orderId);
+            throw new IllegalArgumentException("Order not found: " + orderId);
+        }
+
+        order.setStatus(status);
+        MDC.put("event_class", "order");
+        log.info("订单状态已更新 orderId={}, newStatus={}", orderId, status);
+    }
+}

+ 39 - 0
shop-recycle-order-service/src/main/resources/application.yml

@@ -0,0 +1,39 @@
+spring:
+  application:
+    name: shop-recycle-order-service
+  profiles:
+    active: local
+
+server:
+  port: 8081
+  servlet:
+    context-path: /api
+
+eureka:
+  client:
+    enabled: false
+  instance:
+    prefer-ip-address: true
+
+logging:
+  level:
+    root: INFO
+    com.shop.recycle: DEBUG
+
+---
+spring:
+  config:
+    activate:
+      on-profile: docker
+  application:
+    name: shop-recycle-order-service
+
+server:
+  port: 8081
+  servlet:
+    context-path: /api
+
+logging:
+  level:
+    root: INFO
+    com.shop.recycle: DEBUG

+ 52 - 0
shop-recycle-order-service/src/main/resources/logback-spring.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <springProperty name="app" source="spring.application.name" />
+    <springProperty name="env" source="spring.profiles.active" defaultValue="local" />
+
+    <!-- 异步Appender,将日志异步输出到JSON格式 -->
+    <appender name="ASYNC_JSON" class="ch.qos.logback.classic.AsyncAppender">
+        <queueSize>1024</queueSize>
+        <discardingThreshold>0</discardingThreshold>
+        <appender-ref ref="JSON_CONSOLE" />
+    </appender>
+
+    <!-- JSON格式输出到控制台 -->
+    <appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
+            <includeContext>true</includeContext>
+            <customFields>{"app":"${app}","env":"${env}"}</customFields>
+            <provider class="net.logstash.logback.composite.loggingevent.LoggingEventJsonProvider">
+                <pattern>
+{
+  "ts":"%d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z'}",
+  "level":"%level",
+  "logger":"%logger{36}",
+  "msg":"%msg",
+  "traceId":"%X{traceId:-}",
+  "uri":"%X{uri:-}",
+  "uri_group":"%X{uri_group:-}",
+  "duration":"%X{duration:-}",
+  "userId":"%X{userId:-}",
+  "event_class":"%X{event_class:-}",
+  "error":"%X{error:-}",
+  "status":"%X{status:-}",
+  "thread":"%thread"
+}
+                </pattern>
+            </provider>
+        </encoder>
+    </appender>
+
+    <!-- 保留简单文本格式便于本地开发调试 -->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="INFO">
+        <appender-ref ref="ASYNC_JSON" />
+    </root>
+
+    <logger name="com.shop.recycle" level="DEBUG" />
+</configuration>

+ 19 - 0
shop-recycle-payment-service/Dockerfile

@@ -0,0 +1,19 @@
+FROM maven:3.8-openjdk-8 AS builder
+
+WORKDIR /build
+
+COPY pom.xml .
+RUN mvn dependency:go-offline
+
+COPY src src
+RUN mvn clean package -DskipTests
+
+FROM openjdk:8-jre-slim
+
+WORKDIR /app
+
+COPY --from=builder /build/target/*.jar app.jar
+
+EXPOSE 8082
+
+ENTRYPOINT ["java", "-jar", "app.jar"]

+ 43 - 0
shop-recycle-payment-service/pom.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.shop.recycle</groupId>
+        <artifactId>spring-cloud-log-demo</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>shop-recycle-payment-service</artifactId>
+    <name>Shop Recycle Payment Service</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.shop.recycle</groupId>
+            <artifactId>shop-recycle-common</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 19 - 0
shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/PaymentServiceApplication.java

@@ -0,0 +1,19 @@
+package com.shop.recycle.payment;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+/**
+ * 支付服务启动类
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients
+public class PaymentServiceApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(PaymentServiceApplication.class, args);
+    }
+}

+ 118 - 0
shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/controller/PaymentController.java

@@ -0,0 +1,118 @@
+package com.shop.recycle.payment.controller;
+
+import com.shop.recycle.common.dto.Response;
+import com.shop.recycle.payment.dto.PaymentRequest;
+import com.shop.recycle.payment.dto.PaymentResponse;
+import com.shop.recycle.payment.service.PaymentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 支付控制器
+ */
+@RestController
+@RequestMapping("/payment")
+@CrossOrigin(origins = "*", maxAge = 3600)
+public class PaymentController {
+
+    @Autowired
+    private PaymentService paymentService;
+
+    /**
+     * 支付订单
+     */
+    @PostMapping("/pay")
+    public Response<PaymentResponse> payOrder(@RequestBody PaymentRequest request) {
+        try {
+            PaymentResponse payment = paymentService.payOrder(request);
+            return Response.success(payment);
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 退款
+     */
+    @PostMapping("/refund/{paymentId}")
+    public Response<?> refund(@PathVariable String paymentId) {
+        try {
+            paymentService.refund(paymentId);
+            return Response.success();
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 获取支付详情
+     */
+    @GetMapping("/{paymentId}")
+    public Response<PaymentResponse> getPayment(@PathVariable String paymentId) {
+        try {
+            PaymentResponse payment = paymentService.getPayment(paymentId);
+            return Response.success(payment);
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 测试接口:生成模拟支付
+     * 使用方式:
+     *   - GET /payment/test/mock?count=5      生成5条支付
+     *   - GET /payment/test/mock              生成1条支付
+     */
+    @GetMapping("/test/mock")
+    public Response<List<PaymentResponse>> createMockPayments(
+            @RequestParam(value = "count", defaultValue = "1") Integer count) {
+        try {
+            List<PaymentResponse> payments = new ArrayList<>();
+            
+            for (int i = 0; i < count; i++) {
+                PaymentRequest mockRequest = paymentService.generateMockPaymentRequest();
+                PaymentResponse payment = paymentService.payOrder(mockRequest);
+                payments.add(payment);
+                
+                // 避免请求过快
+                if (i < count - 1) {
+                    Thread.sleep(100);
+                }
+            }
+            
+            return Response.success(payments);
+        } catch (Exception e) {
+            return Response.error(e.getMessage());
+        }
+    }
+
+    /**
+     * URL测试示例页面
+     */
+    @GetMapping("/test/help")
+    public String testHelp() {
+        return "<html>\n" +
+                "<head><meta charset='UTF-8'><title>Payment API Test Help</title></head>\n" +
+                "<body style='font-family:Arial;margin:20px'>\n" +
+                "<h1>💰 支付服务 - URL测试接口</h1>\n" +
+                "<p>使用以下URL直接生成模拟支付消息(产生日志):</p>\n" +
+                "<h2>单个支付</h2>\n" +
+                "<p><a href='http://localhost:8082/api/payment/test/mock' target='_blank'>http://localhost:8082/api/payment/test/mock</a></p>\n" +
+                "<p>生成1条随机支付(40%概率为超时失败)</p>\n" +
+                "<h2>批量支付</h2>\n" +
+                "<p><a href='http://localhost:8082/api/payment/test/mock?count=10' target='_blank'>http://localhost:8082/api/payment/test/mock?count=10</a></p>\n" +
+                "<p>生成10条随机支付</p>\n" +
+                "<p><a href='http://localhost:8082/api/payment/test/mock?count=50' target='_blank'>http://localhost:8082/api/payment/test/mock?count=50</a></p>\n" +
+                "<p>生成50条随机支付(用于压力测试)</p>\n" +
+                "<h2>其他操作</h2>\n" +
+                "<p><strong>查询支付:</strong> GET <code>/api/payment/{paymentId}</code></p>\n" +
+                "<p><strong>退款:</strong> POST <code>/api/payment/refund/{paymentId}</code></p>\n" +
+                "<hr/>\n" +
+                "<p>💡 支付有30%失败率,模拟实际场景。查看后端Terminal可以看到生成的JSON日志</p>\n" +
+                "</body>\n" +
+                "</html>";
+    }
+}

+ 219 - 0
shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/controller/PaymentTestController.java

@@ -0,0 +1,219 @@
+package com.shop.recycle.payment.controller;
+
+import com.shop.recycle.common.dto.Response;
+import com.shop.recycle.payment.dto.PaymentRequest;
+import com.shop.recycle.payment.dto.PaymentResponse;
+import com.shop.recycle.payment.service.PaymentService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 测试控制器 - 用于生成随机支付模拟数据和测试日志系统
+ */
+@RestController
+@RequestMapping("/test")
+@CrossOrigin(origins = "*", maxAge = 3600)
+public class PaymentTestController {
+
+    private static final Logger log = LoggerFactory.getLogger(PaymentTestController.class);
+
+    @Autowired
+    private PaymentService paymentService;
+
+    private static final String[] PAYMENT_METHODS = {"CARD", "WECHAT", "ALIPAY"};
+    private static final String[] USER_NAMES = {"alice", "bob", "charlie", "david", "emma", "frank", "grace", "henry"};
+
+    /**
+     * 生成一条随机支付
+     * GET /test/payment
+     */
+    @GetMapping("/payment")
+    public Response<PaymentResponse> generateRandomPayment() {
+        try {
+            PaymentRequest request = generateRandomPaymentRequest();
+            PaymentResponse payment = paymentService.payOrder(request);
+            
+            MDC.put("event_class", "payment");
+            log.info("测试支付生成成功 paymentId={}", payment.getPaymentId());
+            
+            return Response.success(payment);
+        } catch (Exception e) {
+            MDC.put("event_class", "payment");
+            log.error("测试支付生成失败", e);
+            return Response.error("生成失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量生成支付
+     * GET /test/payment/batch?count=10
+     */
+    @GetMapping("/payment/batch")
+    public Response<Map<String, Object>> batchGeneratePayments(
+            @RequestParam(defaultValue = "10") int count) {
+        
+        long startTime = System.currentTimeMillis();
+        List<PaymentResponse> payments = new ArrayList<>();
+        int successCount = 0;
+        int errorCount = 0;
+        double totalAmount = 0;
+        
+        log.info("开始批量生成 {} 笔支付", count);
+        
+        for (int i = 0; i < count; i++) {
+            try {
+                PaymentRequest request = generateRandomPaymentRequest();
+                PaymentResponse payment = paymentService.payOrder(request);
+                payments.add(payment);
+                successCount++;
+                totalAmount += payment.getAmount();
+                
+                Thread.sleep(100);
+            } catch (Exception e) {
+                errorCount++;
+                log.warn("第 {} 笔支付生成失败", i + 1, e);
+            }
+        }
+        
+        long duration = System.currentTimeMillis() - startTime;
+        
+        MDC.put("event_class", "payment");
+        log.info("支付批量生成完成 total={}, success={}, error={}, amount={}, duration={}ms",
+                count, successCount, errorCount, totalAmount, duration);
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("total", count);
+        result.put("success", successCount);
+        result.put("error", errorCount);
+        result.put("totalAmount", totalAmount);
+        result.put("duration", duration);
+        result.put("payments", payments);
+        
+        return Response.success(result);
+    }
+
+    /**
+     * 支付+退款场景测试
+     * GET /test/payment/refund?count=5
+     */
+    @GetMapping("/payment/refund")
+    public Response<Map<String, Object>> refundScenarioTest(
+            @RequestParam(defaultValue = "5") int count) {
+        
+        long startTime = System.currentTimeMillis();
+        int refundSuccess = 0;
+        int refundError = 0;
+        
+        log.info("开始退款场景测试,共 {} 轮", count);
+        
+        for (int i = 0; i < count; i++) {
+            try {
+                // 1. 生成支付
+                PaymentRequest payRequest = generateRandomPaymentRequest();
+                PaymentResponse payment = paymentService.payOrder(payRequest);
+                
+                // 2. 退款
+                try {
+                    paymentService.refund(payment.getPaymentId());
+                    refundSuccess++;
+                } catch (Exception refundError1) {
+                    refundError++;
+                    log.info("退款失败(可能是网络模拟): {}", refundError1.getMessage());
+                }
+                
+                Thread.sleep(150);
+            } catch (Exception e) {
+                log.error("退款场景测试出错", e);
+            }
+        }
+        
+        long duration = System.currentTimeMillis() - startTime;
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("scenario", "payment -> refund");
+        result.put("rounds", count);
+        result.put("refundSuccess", refundSuccess);
+        result.put("refundError", refundError);
+        result.put("duration", duration);
+        
+        MDC.put("event_class", "payment");
+        log.info("退款场景测试完成 rounds={}, success={}, error={}, duration={}ms",
+                count, refundSuccess, refundError, duration);
+        
+        return Response.success(result);
+    }
+
+    /**
+     * 支付压力测试
+     * GET /test/payment/stress?duration=10
+     */
+    @GetMapping("/payment/stress")
+    public Response<Map<String, Object>> stressTest(
+            @RequestParam(defaultValue = "10") int duration) {
+        
+        long startTime = System.currentTimeMillis();
+        long endTime = startTime + (duration * 1000L);
+        int requestCount = 0;
+        int successCount = 0;
+        int errorCount = 0;
+        double totalAmount = 0;
+        
+        log.info("开始支付压力测试,持续 {} 秒", duration);
+        
+        while (System.currentTimeMillis() < endTime) {
+            try {
+                PaymentRequest request = generateRandomPaymentRequest();
+                PaymentResponse payment = paymentService.payOrder(request);
+                successCount++;
+                totalAmount += payment.getAmount();
+                requestCount++;
+            } catch (Exception e) {
+                errorCount++;
+                requestCount++;
+            }
+        }
+        
+        long actualDuration = System.currentTimeMillis() - startTime;
+        double qps = (requestCount * 1000.0) / actualDuration;
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("duration", actualDuration);
+        result.put("requests", requestCount);
+        result.put("success", successCount);
+        result.put("error", errorCount);
+        result.put("totalAmount", totalAmount);
+        result.put("qps", String.format("%.2f", qps));
+        
+        MDC.put("event_class", "payment");
+        log.info("支付压力测试完成 requests={}, qps={}, totalAmount={}, duration={}ms",
+                requestCount, String.format("%.2f", qps), totalAmount, actualDuration);
+        
+        return Response.success(result);
+    }
+
+    /**
+     * 生成随机支付请求
+     */
+    private PaymentRequest generateRandomPaymentRequest() {
+        ThreadLocalRandom random = ThreadLocalRandom.current();
+        
+        String orderId = "ORD-" + random.nextInt(100000, 999999);
+        Double amount = (random.nextDouble() * 500 + 10);
+        String paymentMethod = PAYMENT_METHODS[random.nextInt(PAYMENT_METHODS.length)];
+        
+        return PaymentRequest.builder()
+                .orderId(orderId)
+                .amount(amount)
+                .paymentMethod(paymentMethod)
+                .build();
+    }
+}

+ 19 - 0
shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/dto/PaymentRequest.java

@@ -0,0 +1,19 @@
+package com.shop.recycle.payment.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 支付请求
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class PaymentRequest {
+    private String orderId;
+    private Double amount;
+    private String paymentMethod; // CARD, WECHAT, ALIPAY
+}

+ 22 - 0
shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/dto/PaymentResponse.java

@@ -0,0 +1,22 @@
+package com.shop.recycle.payment.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 支付响应
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class PaymentResponse {
+    private String paymentId;
+    private String orderId;
+    private Double amount;
+    private String status; // PENDING, SUCCESS, FAILED
+    private String paymentMethod;
+    private Long paidAt;
+}

+ 144 - 0
shop-recycle-payment-service/src/main/java/com/shop/recycle/payment/service/PaymentService.java

@@ -0,0 +1,144 @@
+package com.shop.recycle.payment.service;
+
+import com.shop.recycle.payment.dto.PaymentRequest;
+import com.shop.recycle.payment.dto.PaymentResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * 支付业务逻辑
+ */
+@Service
+public class PaymentService {
+
+    private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
+    private static final Random random = new Random();
+    
+    // 模拟数据库存储
+    private static final Map<String, PaymentResponse> paymentDB = new HashMap<>();
+
+    // 模拟数据集合
+    private static final String[] PAYMENT_METHODS = {"CARD", "WECHAT", "ALIPAY"};
+
+    /**
+     * 生成模拟支付请求
+     */
+    public PaymentRequest generateMockPaymentRequest() {
+        // 使用随机的订单ID
+        String orderId = "ORD-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
+        double amount = (random.nextDouble() * 500) + 10;
+        String paymentMethod = PAYMENT_METHODS[random.nextInt(PAYMENT_METHODS.length)];
+        
+        return PaymentRequest.builder()
+                .orderId(orderId)
+                .amount(Math.round(amount * 100.0) / 100.0)
+                .paymentMethod(paymentMethod)
+                .build();
+    }
+
+    /**
+     * 支付订单
+     */
+    public PaymentResponse payOrder(PaymentRequest request) {
+        log.info("订单支付逻辑开始 orderId={}", request.getOrderId());
+        
+        String paymentId = "PAY-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
+        long now = System.currentTimeMillis();
+
+        try {
+            // 模拟业务处理
+            if (request.getAmount() <= 0) {
+                MDC.put("event_class", "payment");
+                log.error("支付金额不合法: {}", request.getAmount());
+                throw new IllegalArgumentException("amount must be > 0");
+            }
+
+            // 模拟调用第三方支付(30%失败率)
+            if (Math.random() < 0.3) {
+                MDC.put("event_class", "payment");
+                log.error("第三方支付失败 orderId={}, method={}", request.getOrderId(), request.getPaymentMethod());
+                throw new RuntimeException("Third party payment failed");
+            }
+
+            PaymentResponse payment = PaymentResponse.builder()
+                    .paymentId(paymentId)
+                    .orderId(request.getOrderId())
+                    .amount(request.getAmount())
+                    .paymentMethod(request.getPaymentMethod())
+                    .status("SUCCESS")
+                    .paidAt(now)
+                    .build();
+
+            paymentDB.put(paymentId, payment);
+            
+            MDC.put("event_class", "payment");
+            log.info("订单支付成功 paymentId={}, orderId={}, amount={}", paymentId, request.getOrderId(), request.getAmount());
+            
+            return payment;
+        } catch (Exception e) {
+            MDC.put("event_class", "payment");
+            log.error("订单支付失败 orderId={}", request.getOrderId(), e);
+            throw e;
+        }
+    }
+
+    /**
+     * 退款
+     */
+    public void refund(String paymentId) {
+        log.info("退款逻辑开始 paymentId={}", paymentId);
+        
+        try {
+            PaymentResponse payment = paymentDB.get(paymentId);
+            if (payment == null) {
+                MDC.put("event_class", "payment");
+                log.warn("支付记录不存在 paymentId={}", paymentId);
+                throw new IllegalArgumentException("Payment not found: " + paymentId);
+            }
+
+            if (!"SUCCESS".equals(payment.getStatus())) {
+                MDC.put("event_class", "payment");
+                log.error("非成功的支付无法退款 paymentId={}, status={}", paymentId, payment.getStatus());
+                throw new IllegalArgumentException("Only success payment can be refunded");
+            }
+
+            // 模拟调用支付网关退款
+            if (Math.random() < 0.1) {
+                MDC.put("event_class", "payment");
+                log.error("第三方支付退款失败 paymentId={}", paymentId);
+                throw new RuntimeException("Third party refund failed");
+            }
+
+            payment.setStatus("REFUNDED");
+            MDC.put("event_class", "payment");
+            log.info("退款成功 paymentId={}, amount={}", paymentId, payment.getAmount());
+        } catch (Exception e) {
+            MDC.put("event_class", "payment");
+            log.error("退款失败 paymentId={}", paymentId, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 获取支付详情
+     */
+    public PaymentResponse getPayment(String paymentId) {
+        log.info("查询支付详情 paymentId={}", paymentId);
+        
+        PaymentResponse payment = paymentDB.get(paymentId);
+        if (payment == null) {
+            MDC.put("event_class", "payment");
+            log.warn("支付记录不存在 paymentId={}", paymentId);
+            throw new IllegalArgumentException("Payment not found: " + paymentId);
+        }
+        
+        return payment;
+    }
+}

+ 39 - 0
shop-recycle-payment-service/src/main/resources/application.yml

@@ -0,0 +1,39 @@
+spring:
+  application:
+    name: shop-recycle-payment-service
+  profiles:
+    active: local
+
+server:
+  port: 8082
+  servlet:
+    context-path: /api
+
+eureka:
+  client:
+    enabled: false
+  instance:
+    prefer-ip-address: true
+
+logging:
+  level:
+    root: INFO
+    com.shop.recycle: DEBUG
+
+---
+spring:
+  config:
+    activate:
+      on-profile: docker
+  application:
+    name: shop-recycle-payment-service
+
+server:
+  port: 8082
+  servlet:
+    context-path: /api
+
+logging:
+  level:
+    root: INFO
+    com.shop.recycle: DEBUG

+ 52 - 0
shop-recycle-payment-service/src/main/resources/logback-spring.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <springProperty name="app" source="spring.application.name" />
+    <springProperty name="env" source="spring.profiles.active" defaultValue="local" />
+
+    <!-- 异步Appender,将日志异步输出到JSON格式 -->
+    <appender name="ASYNC_JSON" class="ch.qos.logback.classic.AsyncAppender">
+        <queueSize>1024</queueSize>
+        <discardingThreshold>0</discardingThreshold>
+        <appender-ref ref="JSON_CONSOLE" />
+    </appender>
+
+    <!-- JSON格式输出到控制台 -->
+    <appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
+            <includeContext>true</includeContext>
+            <customFields>{"app":"${app}","env":"${env}"}</customFields>
+            <provider class="net.logstash.logback.composite.loggingevent.LoggingEventJsonProvider">
+                <pattern>
+{
+  "ts":"%d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z'}",
+  "level":"%level",
+  "logger":"%logger{36}",
+  "msg":"%msg",
+  "traceId":"%X{traceId:-}",
+  "uri":"%X{uri:-}",
+  "uri_group":"%X{uri_group:-}",
+  "duration":"%X{duration:-}",
+  "userId":"%X{userId:-}",
+  "event_class":"%X{event_class:-}",
+  "error":"%X{error:-}",
+  "status":"%X{status:-}",
+  "thread":"%thread"
+}
+                </pattern>
+            </provider>
+        </encoder>
+    </appender>
+
+    <!-- 保留简单文本格式便于本地开发调试 -->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="INFO">
+        <appender-ref ref="ASYNC_JSON" />
+    </root>
+
+    <logger name="com.shop.recycle" level="DEBUG" />
+</configuration>

+ 13 - 0
shop-recycle-web/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Shop Recycle - 日志测试平台</title>
+</head>
+<body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+</body>
+</html>

+ 24 - 0
shop-recycle-web/package.json

@@ -0,0 +1,24 @@
+{
+  "name": "shop-recycle-web",
+  "version": "1.0.0",
+  "description": "Shop Recycle Web Frontend for Log Testing",
+  "main": "index.js",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview",
+    "lint": "eslint src"
+  },
+  "dependencies": {
+    "vue": "^3.3.0",
+    "axios": "^1.4.0",
+    "element-plus": "^2.3.0"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^4.0.0",
+    "vite": "^4.0.0",
+    "@babel/core": "^7.0.0",
+    "@babel/preset-env": "^7.0.0"
+  }
+}

+ 147 - 0
shop-recycle-web/src/App.vue

@@ -0,0 +1,147 @@
+<template>
+  <div id="app">
+    <header class="app-header">
+      <h1>🔍 商城回收系统 - 日志测试平台</h1>
+      <p class="subtitle">Spring Cloud微服务日志完整链路演示</p>
+    </header>
+    
+    <nav class="tab-nav">
+      <button 
+        v-for="tab in tabs" 
+        :key="tab"
+        @click="activeTab = tab"
+        :class="['tab-btn', { active: activeTab === tab }]"
+      >
+        {{ tab === 'order' ? '📦 订单管理' : '💰 支付管理' }}
+      </button>
+    </nav>
+
+    <div class="content">
+      <OrderComponent v-if="activeTab === 'order'" />
+      <PaymentComponent v-if="activeTab === 'payment'" />
+    </div>
+
+    <footer class="app-footer">
+      <p>💡 提示:每个操作都会在后端生成JSON格式的结构化日志,包含:
+        <br/>traceId(链路追踪ID)、uri_group(规范化URI)、event_class(事件分类)、duration(耗时)
+      </p>
+      <p>查看后端控制台输出,观看JSON格式日志的生成 | <code>curl http://localhost:8080/api/health</code> 查看服务健康状态</p>
+    </footer>
+  </div>
+</template>
+
+<script>
+import OrderComponent from './components/OrderComponent.vue'
+import PaymentComponent from './components/PaymentComponent.vue'
+
+export default {
+  name: 'App',
+  components: {
+    OrderComponent,
+    PaymentComponent
+  },
+  data() {
+    return {
+      activeTab: 'order',
+      tabs: ['order', 'payment']
+    }
+  }
+}
+</script>
+
+<style>
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+body {
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  min-height: 100vh;
+}
+
+#app {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.app-header {
+  background: rgba(255, 255, 255, 0.95);
+  padding: 30px;
+  text-align: center;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  margin-bottom: 20px;
+}
+
+.app-header h1 {
+  color: #333;
+  margin-bottom: 10px;
+  font-size: 28px;
+}
+
+.subtitle {
+  color: #666;
+  font-size: 14px;
+}
+
+.tab-nav {
+  display: flex;
+  justify-content: center;
+  gap: 20px;
+  padding: 0 20px 20px;
+  flex-wrap: wrap;
+}
+
+.tab-btn {
+  padding: 12px 30px;
+  font-size: 16px;
+  border: 2px solid white;
+  background: transparent;
+  color: white;
+  border-radius: 25px;
+  cursor: pointer;
+  transition: all 0.3s;
+  font-weight: 500;
+}
+
+.tab-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
+}
+
+.tab-btn.active {
+  background: white;
+  color: #667eea;
+}
+
+.content {
+  flex: 1;
+  padding-bottom: 20px;
+}
+
+.app-footer {
+  background: rgba(255, 255, 255, 0.9);
+  padding: 20px;
+  text-align: center;
+  margin-top: auto;
+  border-top: 1px solid #eee;
+}
+
+.app-footer p {
+  color: #666;
+  font-size: 13px;
+  line-height: 1.6;
+  margin: 10px 0;
+}
+
+.app-footer code {
+  background: #f0f0f0;
+  padding: 2px 6px;
+  border-radius: 3px;
+  font-family: 'Courier New', monospace;
+  color: #d63384;
+}
+</style>

+ 48 - 0
shop-recycle-web/src/api/index.js

@@ -0,0 +1,48 @@
+import axios from 'axios'
+
+const API_BASE = '/api'
+
+// 创建axios实例
+const instance = axios.create({
+  baseURL: API_BASE,
+  timeout: 10000,
+  headers: {
+    'Content-Type': 'application/json'
+  }
+})
+
+// 请求拦截器
+instance.interceptors.request.use(config => {
+  // 添加X-User-Id头用于日志链路追踪
+  config.headers['X-User-Id'] = localStorage.getItem('userId') || 'user-' + Math.random().toString(36).substr(2, 9)
+  return config
+})
+
+// 订单API
+export const orderAPI = {
+  // 创建订单
+  create: (data) => instance.post('/order/create', data),
+  
+  // 删除订单
+  delete: (orderId) => instance.post(`/order/delete/${orderId}`),
+  
+  // 获取订单详情
+  get: (orderId) => instance.get(`/order/${orderId}`),
+  
+  // 支付订单
+  pay: (orderId) => instance.post(`/order/${orderId}/pay`)
+}
+
+// 支付API
+export const paymentAPI = {
+  // 支付订单
+  pay: (data) => instance.post('/payment/pay', data),
+  
+  // 退款
+  refund: (paymentId) => instance.post(`/payment/refund/${paymentId}`),
+  
+  // 获取支付详情
+  get: (paymentId) => instance.get(`/payment/${paymentId}`)
+}
+
+export default instance

+ 475 - 0
shop-recycle-web/src/components/OrderComponent.vue

@@ -0,0 +1,475 @@
+<template>
+  <div class="order-container">
+    <h2>📦 订单管理 (一键生成)</h2>
+    
+    <div class="quick-action-section">
+      <div class="action-group">
+        <h3>快速操作 - 自动生成随机数据</h3>
+        <div class="button-grid">
+          <button @click="handleCreateOrder" class="btn btn-primary btn-large">
+            <div class="btn-icon">🆕</div>
+            <div class="btn-text">创建订单</div>
+            <div class="btn-desc">随机生成一条订单</div>
+          </button>
+          
+          <button @click="handlePayOrder" class="btn btn-success btn-large">
+            <div class="btn-icon">💳</div>
+            <div class="btn-text">创建+支付</div>
+            <div class="btn-desc">创建订单并立即支付</div>
+          </button>
+          
+          <button @click="handleBatchCreate" class="btn btn-info btn-large">
+            <div class="btn-icon">📊</div>
+            <div class="btn-text">批量生成</div>
+            <div class="btn-desc">生成10条订单</div>
+          </button>
+        </div>
+        <span v-if="message" :class="['message', message.type]">{{ message.text }}</span>
+      </div>
+    </div>
+
+    <div class="result-section" v-if="latestOrder">
+      <h3>最新生成的订单</h3>
+      <div class="order-info">
+        <p><strong>订单ID:</strong> <code>{{ latestOrder.orderId }}</code></p>
+        <p><strong>用户ID:</strong> {{ latestOrder.userId }}</p>
+        <p><strong>金额:</strong> ¥{{ latestOrder.price }}</p>
+        <p><strong>状态:</strong> <span :class="['status-badge', latestOrder.status.toLowerCase()]">{{ latestOrder.status }}</span></p>
+        <p><strong>描述:</strong> {{ latestOrder.description }}</p>
+      </div>
+    </div>
+
+    <div class="stats-section" v-if="operationCount > 0">
+      <h3>操作统计</h3>
+      <div class="stats-grid">
+        <div class="stat-item">
+          <div class="stat-number">{{ operationCount }}</div>
+          <div class="stat-label">总操作数</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-number">{{ successCount }}</div>
+          <div class="stat-label">成功</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-number">{{ errorCount }}</div>
+          <div class="stat-label">失败</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { orderAPI, paymentAPI } from '../api/index.js'
+
+export default {
+  name: 'OrderComponent',
+  data() {
+    return {
+      message: null,
+      latestOrder: null,
+      operationCount: 0,
+      successCount: 0,
+      errorCount: 0
+    }
+  },
+  methods: {
+    // 生成随机用户ID
+    generateUserId() {
+      const names = ['alice', 'bob', 'charlie', 'david', 'emma', 'frank', 'grace', 'henry']
+      const name = names[Math.floor(Math.random() * names.length)]
+      return `${name}-${Math.floor(Math.random() * 10000)}`
+    },
+    
+    // 生成随机价格
+    generatePrice() {
+      return (Math.random() * 500 + 10).toFixed(2)
+    },
+    
+    // 生成随机描述
+    generateDescription() {
+      const descriptions = [
+        '回收电子产品',
+        '二手家电处理',
+        '旧家具回收',
+        '废金属回收',
+        '包装材料回收',
+        '废纸回收',
+        '塑料回收',
+        '玻璃回收'
+      ]
+      return descriptions[Math.floor(Math.random() * descriptions.length)]
+    },
+    
+    async handleCreateOrder() {
+      const request = {
+        userId: this.generateUserId(),
+        price: parseFloat(this.generatePrice()),
+        description: this.generateDescription()
+      }
+      
+      try {
+        const response = await orderAPI.create(request)
+        this.operationCount++
+        
+        if (response.data.code === 0) {
+          this.latestOrder = response.data.data
+          this.successCount++
+          this.message = { 
+            type: 'success', 
+            text: `✅ 订单创建成功! ID: ${response.data.data.orderId}` 
+          }
+        } else {
+          this.errorCount++
+          this.message = { type: 'error', text: `❌ ${response.data.msg}` }
+        }
+      } catch (error) {
+        this.errorCount++
+        this.message = { type: 'error', text: `❌ 错误: ${error.message}` }
+      }
+      
+      setTimeout(() => { this.message = null }, 3000)
+    },
+    
+    async handlePayOrder() {
+      // 先创建订单
+      try {
+        const createRequest = {
+          userId: this.generateUserId(),
+          price: parseFloat(this.generatePrice()),
+          description: this.generateDescription()
+        }
+        
+        const createResponse = await orderAPI.create(createRequest)
+        if (createResponse.data.code !== 0) {
+          throw new Error(createResponse.data.msg)
+        }
+        
+        const orderId = createResponse.data.data.orderId
+        this.latestOrder = createResponse.data.data
+        this.operationCount++
+        this.successCount++
+        
+        // 再支付订单
+        const paymentRequest = {
+          orderId: orderId,
+          amount: parseFloat(createResponse.data.data.price),
+          paymentMethod: ['CARD', 'WECHAT', 'ALIPAY'][Math.floor(Math.random() * 3)]
+        }
+        
+        const payResponse = await paymentAPI.pay(paymentRequest)
+        if (payResponse.data.code === 0) {
+          this.operationCount++
+          this.successCount++
+          this.message = { 
+            type: 'success', 
+            text: `✅ 订单创建+支付成功! 订单ID: ${orderId}` 
+          }
+        } else {
+          this.operationCount++
+          this.errorCount++
+          this.message = { 
+            type: 'error', 
+            text: `⚠️ 订单创建成功但支付失败,请稍后重试` 
+          }
+        }
+      } catch (error) {
+        this.operationCount++
+        this.errorCount++
+        this.message = { type: 'error', text: `❌ ${error.message}` }
+      }
+      
+      setTimeout(() => { this.message = null }, 3000)
+    },
+    
+    async handleBatchCreate() {
+      const startTime = Date.now()
+      this.message = { type: 'info', text: '⏳ 正在批量生成10条订单...' }
+      
+      let successCount = 0
+      let errorCount = 0
+      
+      for (let i = 0; i < 10; i++) {
+        try {
+          const request = {
+            userId: this.generateUserId(),
+            price: parseFloat(this.generatePrice()),
+            description: this.generateDescription()
+          }
+          
+          const response = await orderAPI.create(request)
+          this.operationCount++
+          
+          if (response.data.code === 0) {
+            this.latestOrder = response.data.data
+            successCount++
+            this.successCount++
+          } else {
+            errorCount++
+            this.errorCount++
+          }
+          
+          // 避免请求过快,每个请求间隔100ms
+          await new Promise(resolve => setTimeout(resolve, 100))
+        } catch (error) {
+          errorCount++
+          this.errorCount++
+        }
+      }
+      
+      const duration = Date.now() - startTime
+      this.message = {
+        type: successCount === 10 ? 'success' : 'warning',
+        text: `✅ 批量生成完成! 成功: ${successCount}, 失败: ${errorCount}, 耗时: ${duration}ms`
+      }
+      
+      setTimeout(() => { this.message = null }, 5000)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.order-container {
+  padding: 20px;
+  max-width: 900px;
+  margin: 20px auto;
+}
+
+h2 {
+  color: #333;
+  border-bottom: 2px solid #409EFF;
+  padding-bottom: 10px;
+  margin-bottom: 20px;
+}
+
+h3 {
+  color: #555;
+  margin-top: 20px;
+  margin-bottom: 15px;
+}
+
+/* 快速操作区域 */
+.quick-action-section {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 30px;
+  border-radius: 12px;
+  margin: 20px 0;
+  box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
+}
+
+.action-group h3 {
+  color: white;
+  margin-top: 0;
+  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.button-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 15px;
+  margin: 20px 0;
+}
+
+.btn {
+  padding: 12px 20px;
+  border: none;
+  border-radius: 6px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.3s;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+}
+
+.btn-large {
+  padding: 20px;
+  min-height: 120px;
+  justify-content: center;
+  font-weight: 600;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+.btn-large:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
+}
+
+.btn-icon {
+  font-size: 32px;
+}
+
+.btn-text {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.btn-desc {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+.btn-primary {
+  background-color: #409EFF;
+  color: white;
+}
+
+.btn-primary:hover {
+  background-color: #66b1ff;
+}
+
+.btn-success {
+  background-color: #67C26A;
+  color: white;
+}
+
+.btn-success:hover {
+  background-color: #85CE61;
+}
+
+.btn-info {
+  background-color: #909399;
+  color: white;
+}
+
+.btn-info:hover {
+  background-color: #A6A9AD;
+}
+
+.message {
+  display: block;
+  margin-top: 15px;
+  padding: 12px 16px;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  animation: slideIn 0.3s ease-out;
+}
+
+.message.success {
+  background-color: #f0f9ff;
+  color: #67C26A;
+  border: 1px solid #67C26A;
+}
+
+.message.error {
+  background-color: #fef0f0;
+  color: #F56C6C;
+  border: 1px solid #F56C6C;
+}
+
+.message.warning {
+  background-color: #fdf6ec;
+  color: #E6A23C;
+  border: 1px solid #E6A23C;
+}
+
+.message.info {
+  background-color: #f0f2f5;
+  color: #909399;
+  border: 1px solid #909399;
+}
+
+@keyframes slideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 结果展示区域 */
+.result-section {
+  background: #f5f7fa;
+  padding: 20px;
+  border-radius: 8px;
+  margin-top: 20px;
+  border-left: 4px solid #409EFF;
+}
+
+.order-info {
+  background: white;
+  padding: 16px;
+  border-radius: 6px;
+  line-height: 1.8;
+}
+
+.order-info p {
+  margin: 8px 0;
+  display: flex;
+  justify-content: space-between;
+}
+
+.order-info code {
+  background: #f0f0f0;
+  padding: 4px 8px;
+  border-radius: 3px;
+  font-family: 'Courier New', monospace;
+  color: #d63384;
+}
+
+.status-badge {
+  display: inline-block;
+  padding: 4px 12px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: bold;
+  text-transform: uppercase;
+}
+
+.status-badge.created {
+  background-color: #e7f3ff;
+  color: #0050b3;
+}
+
+.status-badge.paid {
+  background-color: #f6ffed;
+  color: #135200;
+}
+
+.status-badge.cancelled {
+  background-color: #fff1f0;
+  color: #92400e;
+}
+
+/* 统计区域 */
+.stats-section {
+  background: white;
+  padding: 20px;
+  border-radius: 8px;
+  margin-top: 20px;
+  border: 1px solid #dcdfe6;
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: 15px;
+  margin-top: 15px;
+}
+
+.stat-item {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20px;
+  border-radius: 8px;
+  text-align: center;
+  color: white;
+  box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
+}
+
+.stat-number {
+  font-size: 32px;
+  font-weight: bold;
+  margin-bottom: 8px;
+}
+
+.stat-label {
+  font-size: 14px;
+  opacity: 0.9;
+}
+</style>

+ 559 - 0
shop-recycle-web/src/components/PaymentComponent.vue

@@ -0,0 +1,559 @@
+<template>
+  <div class="payment-container">
+    <h2>💰 支付管理 (一键生成)</h2>
+    
+    <div class="quick-action-section">
+      <div class="action-group">
+        <h3>快速操作 - 自动生成随机数据</h3>
+        <div class="button-grid">
+          <button @click="handleCreateAndPay" class="btn btn-success btn-large">
+            <div class="btn-icon">💳</div>
+            <div class="btn-text">创建+支付</div>
+            <div class="btn-desc">创建订单并支付</div>
+          </button>
+          
+          <button @click="handleBatchPay" class="btn btn-primary btn-large">
+            <div class="btn-icon">📊</div>
+            <div class="btn-text">批量支付</div>
+            <div class="btn-desc">批量创建并支付10个</div>
+          </button>
+          
+          <button @click="handlePaymentFailures" class="btn btn-warning btn-large">
+            <div class="btn-icon">⚠️</div>
+            <div class="btn-text">测试失败场景</div>
+            <div class="btn-desc">测试支付失败重试</div>
+          </button>
+        </div>
+        <span v-if="message" :class="['message', message.type]">{{ message.text }}</span>
+      </div>
+    </div>
+
+    <div class="result-section" v-if="latestPayment">
+      <h3>最新支付记录</h3>
+      <div class="payment-info">
+        <p><strong>支付ID:</strong> <code>{{ latestPayment.paymentId }}</code></p>
+        <p><strong>订单ID:</strong> <code>{{ latestPayment.orderId }}</code></p>
+        <p><strong>金额:</strong> ¥{{ latestPayment.amount }}</p>
+        <p><strong>状态:</strong> <span :class="['status-badge', latestPayment.status.toLowerCase()]">{{ latestPayment.status }}</span></p>
+        <p><strong>支付方式:</strong> <span class="method-badge">{{ latestPayment.paymentMethod }}</span></p>
+      </div>
+    </div>
+
+    <div class="stats-section" v-if="operationCount > 0">
+      <h3>支付统计</h3>
+      <div class="stats-grid">
+        <div class="stat-item">
+          <div class="stat-number">{{ operationCount }}</div>
+          <div class="stat-label">总操作数</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-number">{{ successCount }}</div>
+          <div class="stat-label">成功</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-number">{{ failureCount }}</div>
+          <div class="stat-label">失败</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-number">{{ totalAmount }}</div>
+          <div class="stat-label">总金额 (¥)</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { orderAPI, paymentAPI } from '../api/index.js'
+
+export default {
+  name: 'PaymentComponent',
+  data() {
+    return {
+      message: null,
+      latestPayment: null,
+      operationCount: 0,
+      successCount: 0,
+      failureCount: 0,
+      totalAmount: 0
+    }
+  },
+  methods: {
+    // 生成随机用户ID
+    generateUserId() {
+      const names = ['alice', 'bob', 'charlie', 'david', 'emma', 'frank', 'grace', 'henry']
+      const name = names[Math.floor(Math.random() * names.length)]
+      return `${name}-${Math.floor(Math.random() * 10000)}`
+    },
+    
+    // 生成随机价格
+    generatePrice() {
+      return (Math.random() * 500 + 10).toFixed(2)
+    },
+    
+    // 生成随机描述
+    generateDescription() {
+      const descriptions = ['回收电子产品', '二手家电处理', '旧家具回收', '废金属回收', '包装材料回收']
+      return descriptions[Math.floor(Math.random() * descriptions.length)]
+    },
+    
+    // 生成随机支付方式
+    generatePaymentMethod() {
+      return ['CARD', 'WECHAT', 'ALIPAY'][Math.floor(Math.random() * 3)]
+    },
+    
+    async handleCreateAndPay() {
+      try {
+        // 创建订单
+        const createRequest = {
+          userId: this.generateUserId(),
+          price: parseFloat(this.generatePrice()),
+          description: this.generateDescription()
+        }
+        
+        const createResponse = await orderAPI.create(createRequest)
+        if (createResponse.data.code !== 0) {
+          throw new Error(createResponse.data.msg)
+        }
+        
+        const orderId = createResponse.data.data.orderId
+        const amount = parseFloat(createResponse.data.data.price)
+        
+        // 支付订单
+        const paymentRequest = {
+          orderId: orderId,
+          amount: amount,
+          paymentMethod: this.generatePaymentMethod()
+        }
+        
+        const payResponse = await paymentAPI.pay(paymentRequest)
+        this.operationCount++
+        
+        if (payResponse.data.code === 0) {
+          this.latestPayment = payResponse.data.data
+          this.successCount++
+          this.totalAmount += amount
+          this.message = { 
+            type: 'success', 
+            text: `✅ 支付成功! 支付ID: ${payResponse.data.data.paymentId}` 
+          }
+        } else {
+          this.failureCount++
+          this.message = { 
+            type: 'error', 
+            text: `❌ 支付失败: ${payResponse.data.msg}` 
+          }
+        }
+      } catch (error) {
+        this.operationCount++
+        this.failureCount++
+        this.message = { type: 'error', text: `❌ ${error.message}` }
+      }
+      
+      setTimeout(() => { this.message = null }, 3000)
+    },
+    
+    async handleBatchPay() {
+      const startTime = Date.now()
+      this.message = { type: 'info', text: '⏳ 正在批量创建并支付10个订单...' }
+      
+      let successCount = 0
+      let failureCount = 0
+      let totalAmount = 0
+      
+      for (let i = 0; i < 10; i++) {
+        try {
+          // 创建订单
+          const createRequest = {
+            userId: this.generateUserId(),
+            price: parseFloat(this.generatePrice()),
+            description: this.generateDescription()
+          }
+          
+          const createResponse = await orderAPI.create(createRequest)
+          if (createResponse.data.code !== 0) {
+            failureCount++
+            this.failureCount++
+            this.operationCount++
+            continue
+          }
+          
+          const orderId = createResponse.data.data.orderId
+          const amount = parseFloat(createResponse.data.data.price)
+          
+          // 支付订单
+          const paymentRequest = {
+            orderId: orderId,
+            amount: amount,
+            paymentMethod: this.generatePaymentMethod()
+          }
+          
+          const payResponse = await paymentAPI.pay(paymentRequest)
+          this.operationCount++
+          
+          if (payResponse.data.code === 0) {
+            this.latestPayment = payResponse.data.data
+            successCount++
+            this.successCount++
+            totalAmount += amount
+          } else {
+            failureCount++
+            this.failureCount++
+          }
+          
+          // 避免请求过快,每个请求间隔150ms
+          await new Promise(resolve => setTimeout(resolve, 150))
+        } catch (error) {
+          failureCount++
+          this.failureCount++
+          this.operationCount++
+        }
+      }
+      
+      const duration = Date.now() - startTime
+      this.totalAmount += totalAmount
+      this.message = {
+        type: successCount > 0 ? 'success' : 'error',
+        text: `✅ 批量支付完成! 成功: ${successCount}, 失败: ${failureCount}, 总金额: ¥${totalAmount.toFixed(2)}, 耗时: ${duration}ms`
+      }
+      
+      setTimeout(() => { this.message = null }, 5000)
+    },
+    
+    async handlePaymentFailures() {
+      const startTime = Date.now()
+      this.message = { type: 'info', text: '⏳ 测试支付失败重试...' }
+      
+      let finalSuccess = false
+      let retryCount = 0
+      const maxRetries = 5
+      
+      try {
+        // 创建订单
+        const createRequest = {
+          userId: this.generateUserId(),
+          price: parseFloat(this.generatePrice()),
+          description: '测试失败重试'
+        }
+        
+        const createResponse = await orderAPI.create(createRequest)
+        if (createResponse.data.code !== 0) {
+          throw new Error(createResponse.data.msg)
+        }
+        
+        const orderId = createResponse.data.data.orderId
+        const amount = parseFloat(createResponse.data.data.price)
+        
+        // 重试支付,直到成功或达到最大重试次数
+        while (retryCount < maxRetries && !finalSuccess) {
+          try {
+            const paymentRequest = {
+              orderId: orderId,
+              amount: amount,
+              paymentMethod: this.generatePaymentMethod()
+            }
+            
+            const payResponse = await paymentAPI.pay(paymentRequest)
+            this.operationCount++
+            retryCount++
+            
+            if (payResponse.data.code === 0) {
+              this.latestPayment = payResponse.data.data
+              this.successCount++
+              this.totalAmount += amount
+              finalSuccess = true
+            } else {
+              this.failureCount++
+            }
+            
+            await new Promise(resolve => setTimeout(resolve, 300))
+          } catch (error) {
+            this.failureCount++
+            this.operationCount++
+            retryCount++
+          }
+        }
+        
+        const duration = Date.now() - startTime
+        if (finalSuccess) {
+          this.message = {
+            type: 'success',
+            text: `✅ 第${retryCount}次重试成功! 耗时: ${duration}ms`
+          }
+        } else {
+          this.message = {
+            type: 'error',
+            text: `❌ 连续失败${maxRetries}次,放弃重试。耗时: ${duration}ms`
+          }
+        }
+      } catch (error) {
+        this.operationCount++
+        this.failureCount++
+        this.message = { type: 'error', text: `❌ ${error.message}` }
+      }
+      
+      setTimeout(() => { this.message = null }, 5000)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.payment-container {
+  padding: 20px;
+  max-width: 900px;
+  margin: 20px auto;
+}
+
+h2 {
+  color: #333;
+  border-bottom: 2px solid #67C26A;
+  padding-bottom: 10px;
+  margin-bottom: 20px;
+}
+
+h3 {
+  color: #555;
+  margin-top: 20px;
+  margin-bottom: 15px;
+}
+
+/* 快速操作区域 */
+.quick-action-section {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 30px;
+  border-radius: 12px;
+  margin: 20px 0;
+  box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
+}
+
+.action-group h3 {
+  color: white;
+  margin-top: 0;
+  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.button-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 15px;
+  margin: 20px 0;
+}
+
+.btn {
+  padding: 12px 20px;
+  border: none;
+  border-radius: 6px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.3s;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+}
+
+.btn-large {
+  padding: 20px;
+  min-height: 120px;
+  justify-content: center;
+  font-weight: 600;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+.btn-large:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
+}
+
+.btn-icon {
+  font-size: 32px;
+}
+
+.btn-text {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.btn-desc {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+.btn-primary {
+  background-color: #409EFF;
+  color: white;
+}
+
+.btn-primary:hover {
+  background-color: #66b1ff;
+}
+
+.btn-success {
+  background-color: #67C26A;
+  color: white;
+}
+
+.btn-success:hover {
+  background-color: #85CE61;
+}
+
+.btn-warning {
+  background-color: #E6A23C;
+  color: white;
+}
+
+.btn-warning:hover {
+  background-color: #ebb563;
+}
+
+.message {
+  display: block;
+  margin-top: 15px;
+  padding: 12px 16px;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  animation: slideIn 0.3s ease-out;
+}
+
+.message.success {
+  background-color: #f0f9ff;
+  color: #67C26A;
+  border: 1px solid #67C26A;
+}
+
+.message.error {
+  background-color: #fef0f0;
+  color: #F56C6C;
+  border: 1px solid #F56C6C;
+}
+
+.message.warning {
+  background-color: #fdf6ec;
+  color: #E6A23C;
+  border: 1px solid #E6A23C;
+}
+
+.message.info {
+  background-color: #f0f2f5;
+  color: #909399;
+  border: 1px solid #909399;
+}
+
+@keyframes slideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 结果展示区域 */
+.result-section {
+  background: #f5f7fa;
+  padding: 20px;
+  border-radius: 8px;
+  margin-top: 20px;
+  border-left: 4px solid #67C26A;
+}
+
+.payment-info {
+  background: white;
+  padding: 16px;
+  border-radius: 6px;
+  line-height: 1.8;
+}
+
+.payment-info p {
+  margin: 8px 0;
+  display: flex;
+  justify-content: space-between;
+}
+
+.payment-info code {
+  background: #f0f0f0;
+  padding: 4px 8px;
+  border-radius: 3px;
+  font-family: 'Courier New', monospace;
+  color: #d63384;
+}
+
+.status-badge {
+  display: inline-block;
+  padding: 4px 12px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: bold;
+  text-transform: uppercase;
+}
+
+.status-badge.pending {
+  background-color: #fff7e6;
+  color: #b37200;
+}
+
+.status-badge.success {
+  background-color: #f6ffed;
+  color: #135200;
+}
+
+.status-badge.failed {
+  background-color: #fff1f0;
+  color: #92400e;
+}
+
+.status-badge.refunded {
+  background-color: #e6f7ff;
+  color: #003a8c;
+}
+
+.method-badge {
+  display: inline-block;
+  padding: 3px 10px;
+  background-color: #e6f7ff;
+  color: #0050b3;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: bold;
+}
+
+/* 统计区域 */
+.stats-section {
+  background: white;
+  padding: 20px;
+  border-radius: 8px;
+  margin-top: 20px;
+  border: 1px solid #dcdfe6;
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: 15px;
+  margin-top: 15px;
+}
+
+.stat-item {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20px;
+  border-radius: 8px;
+  text-align: center;
+  color: white;
+  box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
+}
+
+.stat-number {
+  font-size: 32px;
+  font-weight: bold;
+  margin-bottom: 8px;
+}
+
+.stat-label {
+  font-size: 14px;
+  opacity: 0.9;
+}
+</style>

+ 5 - 0
shop-recycle-web/src/main.js

@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+const app = createApp(App)
+app.mount('#app')

+ 15 - 0
shop-recycle-web/vite.config.js

@@ -0,0 +1,15 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+  plugins: [vue()],
+  server: {
+    port: 5173,
+    proxy: {
+      '/api': {
+        target: 'http://localhost:8080',
+        changeOrigin: true
+      }
+    }
+  }
+})

+ 108 - 0
start.ps1

@@ -0,0 +1,108 @@
+# Quick Start Script for Spring Cloud Log Demo System (PowerShell)
+
+Write-Host "================================" -ForegroundColor Cyan
+Write-Host "Spring Cloud Log Demo - Quick Start" -ForegroundColor Cyan
+Write-Host "================================" -ForegroundColor Cyan
+Write-Host ""
+
+# Check prerequisites
+Write-Host "[1/5] Checking prerequisites..." -ForegroundColor Yellow
+$errorCount = 0
+
+if (-not (Get-Command java -ErrorAction SilentlyContinue)) {
+    Write-Host "ERROR: Java is not installed" -ForegroundColor Red
+    $errorCount++
+} else {
+    $javaVersion = java -version 2>&1 | Select-Object -First 1
+    Write-Host "✓ Java: $javaVersion" -ForegroundColor Green
+}
+
+if (-not (Get-Command mvn -ErrorAction SilentlyContinue)) {
+    Write-Host "ERROR: Maven is not installed" -ForegroundColor Red
+    $errorCount++
+} else {
+    $mvnVersion = mvn -v 2>&1 | Select-Object -First 1
+    Write-Host "✓ Maven: $mvnVersion" -ForegroundColor Green
+}
+
+if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
+    Write-Host "ERROR: Node.js is not installed" -ForegroundColor Red
+    $errorCount++
+} else {
+    $nodeVersion = node -v
+    Write-Host "✓ Node: $nodeVersion" -ForegroundColor Green
+}
+
+if ($errorCount -gt 0) {
+    Write-Host "Please install missing prerequisites" -ForegroundColor Red
+    exit 1
+}
+
+Write-Host ""
+
+# Build backend
+Write-Host "[2/5] Building backend services..." -ForegroundColor Yellow
+mvn clean install -DskipTests | Out-Null
+Write-Host "✓ Backend build completed" -ForegroundColor Green
+Write-Host ""
+
+# Start services
+Write-Host "[3/5] Starting backend services..." -ForegroundColor Yellow
+
+Write-Host "  - Gateway (port 8080)" -ForegroundColor White
+Push-Location shop-recycle-gateway
+Start-Process mvn -ArgumentList "spring-boot:run" -WindowStyle Minimized
+Write-Host "    Started" -ForegroundColor Green
+Pop-Location
+
+Start-Sleep -Seconds 3
+
+Write-Host "  - Order Service (port 8081)" -ForegroundColor White
+Push-Location shop-recycle-order-service
+Start-Process mvn -ArgumentList "spring-boot:run" -WindowStyle Minimized
+Write-Host "    Started" -ForegroundColor Green
+Pop-Location
+
+Write-Host "  - Payment Service (port 8082)" -ForegroundColor White
+Push-Location shop-recycle-payment-service
+Start-Process mvn -ArgumentList "spring-boot:run" -WindowStyle Minimized
+Write-Host "    Started" -ForegroundColor Green
+Pop-Location
+
+Start-Sleep -Seconds 5
+
+Write-Host "✓ All backend services started" -ForegroundColor Green
+Write-Host ""
+
+# Frontend setup
+Write-Host "[4/5] Installing frontend dependencies..." -ForegroundColor Yellow
+Push-Location shop-recycle-web
+npm install | Out-Null
+Write-Host "✓ Frontend dependencies installed" -ForegroundColor Green
+Pop-Location
+Write-Host ""
+
+Write-Host "[5/5] Starting frontend development server..." -ForegroundColor Yellow
+Push-Location shop-recycle-web
+Write-Host "Frontend will open in a new terminal..." -ForegroundColor Cyan
+Start-Process cmd -ArgumentList "/c npm run dev"
+Pop-Location
+Write-Host ""
+
+Write-Host "================================" -ForegroundColor Green
+Write-Host "✓ All services are running!" -ForegroundColor Green
+Write-Host "================================" -ForegroundColor Green
+Write-Host ""
+Write-Host "🌐 Frontend:        http://localhost:5173" -ForegroundColor Cyan
+Write-Host "🔌 API Gateway:     http://localhost:8080" -ForegroundColor Cyan
+Write-Host "📦 Order Service:   http://localhost:8081/api" -ForegroundColor Cyan
+Write-Host "💰 Payment Service: http://localhost:8082/api" -ForegroundColor Cyan
+Write-Host ""
+Write-Host "📋 Check logs in the terminal windows that opened" -ForegroundColor Yellow
+Write-Host ""
+Write-Host "💡 Tips:" -ForegroundColor Yellow
+Write-Host "  1. Open http://localhost:5173 in your browser" -ForegroundColor White
+Write-Host "  2. Create an order and complete payment" -ForegroundColor White
+Write-Host "  3. Watch the structured JSON logs in backend terminals" -ForegroundColor White
+Write-Host "  4. Observe traceId, uri_group, event_class, duration fields" -ForegroundColor White
+Write-Host ""

+ 94 - 0
start.sh

@@ -0,0 +1,94 @@
+#!/bin/bash
+# Quick Start Script for Spring Cloud Log Demo System
+
+set -e
+
+echo "================================"
+echo "Spring Cloud Log Demo - Quick Start"
+echo "================================"
+echo ""
+
+# Check prerequisites
+echo "[1/5] Checking prerequisites..."
+if ! command -v java &> /dev/null; then
+    echo "ERROR: Java is not installed"
+    exit 1
+fi
+
+if ! command -v mvn &> /dev/null; then
+    echo "ERROR: Maven is not installed"
+    exit 1
+fi
+
+if ! command -v node &> /dev/null; then
+    echo "ERROR: Node.js is not installed"
+    exit 1
+fi
+
+echo "✓ Java version: $(java -version 2>&1 | head -1)"
+echo "✓ Maven version: $(mvn -v | head -1)"
+echo "✓ Node version: $(node -v)"
+echo ""
+
+# Build backend
+echo "[2/5] Building backend services..."
+mvn clean install -DskipTests > /dev/null
+echo "✓ Backend build completed"
+echo ""
+
+# Start services in background
+echo "[3/5] Starting backend services..."
+echo "  - Gateway (port 8080)"
+cd shop-recycle-gateway
+mvn spring-boot:run > /tmp/gateway.log 2>&1 &
+GATEWAY_PID=$!
+echo "    Started with PID $GATEWAY_PID"
+
+sleep 3
+
+echo "  - Order Service (port 8081)"
+cd ../shop-recycle-order-service
+mvn spring-boot:run > /tmp/order.log 2>&1 &
+ORDER_PID=$!
+echo "    Started with PID $ORDER_PID"
+
+echo "  - Payment Service (port 8082)"
+cd ../shop-recycle-payment-service
+mvn spring-boot:run > /tmp/payment.log 2>&1 &
+PAYMENT_PID=$!
+echo "    Started with PID $PAYMENT_PID"
+
+sleep 5
+
+echo "✓ All backend services started"
+echo ""
+
+# Start frontend
+echo "[4/5] Installing frontend dependencies..."
+cd ../shop-recycle-web
+npm install > /dev/null 2>&1
+echo "✓ Frontend dependencies installed"
+echo ""
+
+echo "[5/5] Starting frontend development server..."
+npm run dev &
+FRONTEND_PID=$!
+echo "✓ Frontend started with PID $FRONTEND_PID"
+echo ""
+
+echo "================================"
+echo "✓ All services are running!"
+echo "================================"
+echo ""
+echo "🌐 Frontend:        http://localhost:5173"
+echo "🔌 API Gateway:     http://localhost:8080"
+echo "📦 Order Service:   http://localhost:8081/api"
+echo "💰 Payment Service: http://localhost:8082/api"
+echo ""
+echo "📋 Backend logs:"
+echo "  - Gateway: tail -f /tmp/gateway.log"
+echo "  - Order:   tail -f /tmp/order.log"
+echo "  - Payment: tail -f /tmp/payment.log"
+echo ""
+echo "🛑 To stop all services, run: pkill -f maven; kill $FRONTEND_PID"
+echo ""