# Tài liệu bàn giao dự án SDK_Signning (Service-Proxy)

phác thảo cấu trúc và nội dung chi tiết cho bộ tài liệu

# CHƯƠNG 1: GIỚI THIỆU & BÀI TOÁN NGHIỆP VỤ

<span>Tài liệu này được biên soạn cho dự án </span>**service-proxy**<span> (SDK\_Signning) nhằm mục đích bàn giao cho đội ngũ phát triển và vận hành (Dev/Ops).</span>

# Trang 1: Tổng quan dự án

### 1.1 Định nghĩa

`service-proxy` là một thành phần trung gian (Middleware/API Gateway) được phát triển trên nền tảng **Spring Boot 3.3** và **Java 17**. Dự án này đóng vai trò là "cửa ngõ" (Gateway) duy nhất để các ứng dụng Client (như Mobile App, Web Frontend, hoặc các tích hợp bên thứ ba) tương tác với hệ thống ký số nội bộ và các quy trình nghiệp vụ (BPMN).

### 1.2 Vai trò của Proxy trong hệ thống

Dự án không trực tiếp thực hiện các thuật toán ký số hay lưu trữ cơ sở dữ liệu quy trình. Thay vào đó, nó giải quyết các vấn đề về:

- **Tập trung hóa (Centralization)**: Một đầu mối duy nhất cho mọi yêu cầu ký số.
- **Bảo mật (Security Barrier)**: Ngăn chặn truy cập trực tiếp từ Internet vào các cụm Cluster ký số nội bộ nhạy cảm.
- **Khả năng phục hồi (Resiliency)**: Tự động xử lý lỗi mạng thông qua cơ chế Retry thông minh.

### 1.3 Sơ đồ luồng dữ liệu (Data Flow)

[![image.png](https://docs.lifetex.vn/uploads/images/gallery/2026-04/scaled-1680-/jQSimage.png)](https://docs.lifetex.vn/uploads/images/gallery/2026-04/jQSimage.png)

# Trang 2: Bài toán nghiệp vụ & Giải pháp kỹ thuật

Dự án ra đời nhằm giải quyết 4 thách thức lớn trong quá trình vận hành hệ thống ký số cũ:

### 2.1 Thách thức 1: Phân mảnh backend (Fragmented APIs)

- **Bài toán**: Client phải ghi nhớ và kết nối đồng thời nhiều địa chỉ Server khác nhau (Signing Engine, CRM, BPMN Server). Việc thay đổi IP/Domain của một Server yêu cầu cập nhật lại toàn bộ ứng dụng đầu cuối.
- **Giải pháp**: Proxy cung cấp các Endpoint thống nhất (`/api/sign/**`, `/api/proxy/**`). Các cấu hình Backend được quản lý tập trung trong file `application.yml`.

### 2.2 Thách thức 2: Độ trễ và mất kết nối mạng (Resiliency)

- **Bài toán**: Các thao tác ký PDF là tác vụ nặng, yêu cầu thời gian xử lý và băng thông. Trong môi trường mạng không ổn định, các yêu cầu thường bị thất bại (Timeout).
- **Giải pháp**: Tích hợp `Spring Retry`. Proxy sẽ tự động thử lại (tối đa 3 lần) với khoảng thời gian chờ (Backoff) trước khi trả về lỗi cho Client. Điều này giúp tăng tỷ lệ ký thành công mà người dùng không cần thao tác lại.

### 2.3 Thách thức 3: Bất tương thích tiêu chuẩn Header (Header Mapping)

- **Bài toán**: Ứng dụng Client hiện tại sử dụng Header `Token-Signing` để lưu trữ mã định danh, nhưng các backend chuẩn REST thường yêu cầu Bearer Token trong Header `Authorization`.
- **Giải pháp**: Proxy thực hiện ánh xạ tự động:

```java
if (headers.containsKey("Token-Signing")) {
    headers.set(HttpHeaders.AUTHORIZATION, headers.getFirst("Token-Signing"));
}
```

Quá trình này diễn ra minh bạch, giúp Client không cần sửa mã nguồn cũ.

### 2.4 Thách thức 4: Phân vùng mạng và Bảo vệ hạ tầng

- **Bài toán**: Signing Engine chứa các chứng thư số và khóa mật mã (PKCS#11, HSM), không được phép công khai ra ngoài Internet.
- **Giải pháp**: Proxy được đặt trong phân vùng kiểm soát (DMZ), chỉ mở các cổng API cần thiết. Nó đóng vai trò là "người gác cổng", lọc các Header dư thừa (như `host`, `content-length` của Client) trước khi gửi vào mạng nội bộ để tránh xung đột giao thức.

# CHƯƠNG 2: KIẾN TRÚC KỸ THUẬT

Chương này cung cấp cái nhìn sâu sắc về mặt kỹ thuật, giúp Developer hiểu cách hệ thống vận hành bên trong để có thể bảo trì và mở rộng.

# Trang 1: Công nghệ cốt lõi (Core Stack)

Dự án được xây dựng dựa trên các tiêu chuẩn hiện đại của Java Ecosystem:

1. **Framework chính**: Spring Boot 3.3.0.
2. **Ngôn ngữ**: Java 17 (Cung cấp các tính năng như Record, Text Blocks, Switch Expression).
3. **HTTP Client**: `Spring WebFlux (WebClient)`. Được chọn thay thế cho RestTemplate cũ vì tính linh hoạt và hiệu suất cao hơn.
4. **Resiliency**: `Spring Retry`. Tự động quản lý việc thử lại các yêu cầu thất bại.
5. **Tiện ích**: 
    - `Lombok`: Giảm thiểu mã lặp (Getter, Setter, Constructor).
    - `Logback`: Ghi log chi tiết cho quá trình Proxy.
    - `Maven`: Quản lý phụ thuộc và đóng gói JAR.

# Trang 2: Cấu hình WebClient & Kết nối

Lớp `WebClientConfig` đóng vai trò quan trọng trong việc thiết lập giao tiếp mạng:

### 2.1 Xử lý dung lượng file lớn

Mặc định, WebClient giới hạn bộ nhớ đệm cho dữ liệu nhận về. Vì hệ thống xử lý ký các file PDF dung lượng lớn, cấu hình đã được mở rộng lên **16 MB**:

```java
ExchangeStrategies strategies = ExchangeStrategies.builder()
    .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
    .build();
```

### 2.2 Quản lý Timeout

Hệ thống sử dụng các tham số cấu hình linh hoạt trong `application.yml` cho từng loại backend:

- `connect-timeout-ms`: Thời gian tối đa để thiết lập kết nối (Mặc định 60s cho Signing).
- `read-timeout-ms`: Thời gian chờ Server phản hồi dữ liệu (Mặc định 60s cho Signing).

# Trang 3: Cơ chế Kháng lỗi (Resiliency)

Hệ thống sử dụng cơ chế **Retry-with-Recovery** tại lớp `ExternalApiClient`:

### 3.1 Cơ chế Retry

Sử dụng Annotation `@Retryable` cho các phương thức gọi API:

- `maxAttempts = 3`: Thử lại tối đa 3 lần nếu xảy ra Exception.
- `backoff = @Backoff(delay = 2000)`: Chờ 2 giây giữa mỗi lần thử lại.

### 3.2 Cơ chế Recovery (Hồi phục)

Khi cả 3 lần thử lại đều thất bại, phương thức `@Recover` sẽ được kích hoạt để trả về một phản hồi "an toàn" thay vì làm sập ứng dụng:

```java
@Recover
public ResponseEntity<?> recoverSigning(Exception e) {
    log.warn("Recovering after signing call failed", e);
    return ResponseFactory.error("Signing service error: " + e.getMessage(), ...);
}
```

# Trang 4: Phân tích cấu trúc mã nguồn

Cấu trúc Package được tổ chức theo chuẩn Clean Architecture đơn giản:

<div class="my-4 rounded-lg overflow-x-auto overflow-y-hidden border border-gray-500/20 [&_thead_tr:first-child_th:first-child]:border-t-0 [&_thead_tr:first-child_th:first-child]:border-l-0 [&_thead_tr:first-child_th:last-child]:border-t-0 [&_thead_tr:first-child_th:last-child]:border-r-0 [&_tbody_tr:last-child_td:first-child]:border-b-0 [&_tbody_tr:last-child_td:first-child]:border-l-0 [&_tbody_tr:last-child_td:last-child]:border-b-0 [&_tbody_tr:last-child_td:last-child]:border-r-0 [&_thead_tr:first-child_th]:border-t-0 [&_tbody_tr:last-child_td]:border-b-0 [&_th:first-child]:border-l-0 [&_td:first-child]:border-l-0 [&_th:last-child]:border-r-0 [&_td:last-child]:border-r-0" id="bkmrk-package-tr%C3%A1ch-nhi%E1%BB%87m-"><table class="min-w-full border-separate border-spacing-0"><thead><tr><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Package</th><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Trách nhiệm</th></tr></thead><tbody><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`com.example.serviceproxy.controller`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Tiếp nhận HTTP Request từ Client, thực hiện Mapping Header.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`com.example.serviceproxy.service`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Chứa Interface và Implementation điều phối các yêu cầu Proxy.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`com.example.serviceproxy.client`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">**Trái tim của hệ thống**, thực hiện các cuộc gọi WebClient và logic Retry.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`com.example.serviceproxy.config`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Thiết lập Security, WebClient và các Bean hệ thống.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`com.example.serviceproxy.dto`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Chứa các đối tượng trao đổi dữ liệu (Request/Response) cho cả Signing và BPMN.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`com.example.serviceproxy.response`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Lớp tiện ích để tạo các phản hồi chuẩn (Success/Error).</td></tr></tbody></table>

</div>### Sơ đồ Logic xử lý:

1. `SigningProxyController` nhận request.
2. `SigningProxyServiceImpl` nhận lệnh.
3. `ExternalApiClient` thực thi WebClient (có Retry).
4. Phản hồi được trả ngược lại qua các lớp.

# CHƯƠNG 3: HƯỚNG DẪN VẬN HÀNH & DEV

Chương này được thiết kế như một bản hướng dẫn từng bước (Step-by-step) để cài đặt, cấu hình và khởi chạy hệ thống trong môi trường phát triển và vận hành thực tế.

# Trang 1: Chuẩn bị môi trường

Để chạy dự án `service-proxy`, hệ thống cần cài đặt sẵn các thành phần sau:

1. **Java Development Kit (JDK) 17**: Khuyến nghị sử dụng OpenJDK 17 hoặc Oracle JDK 17. 
    - Kiểm tra bằng lệnh: `java -version`
2. **Apache Maven 3.8+**: Dùng để build mã nguồn. 
    - Kiểm tra bằng lệnh: `mvn -version`
3. **Kết nối mạng**: Đảm bảo Server Proxy có quyền truy cập tới: 
    - Các cụm Signing Cluster (cổng mặc định: 6868).
    - CRM/External API (cổng mặc định: 356).
    - Callback Server (cổng mặc định: 8386).

# Trang 2: Cấu hình hệ thống

Hệ thống hỗ trợ cấu hình linh hoạt thông qua file `application.yml` hoặc các biến môi trường (Environment Variables).

### 2.1 Các tham số cấu hình chính

<div class="my-4 rounded-lg overflow-x-auto overflow-y-hidden border border-gray-500/20 [&_thead_tr:first-child_th:first-child]:border-t-0 [&_thead_tr:first-child_th:first-child]:border-l-0 [&_thead_tr:first-child_th:last-child]:border-t-0 [&_thead_tr:first-child_th:last-child]:border-r-0 [&_tbody_tr:last-child_td:first-child]:border-b-0 [&_tbody_tr:last-child_td:first-child]:border-l-0 [&_tbody_tr:last-child_td:last-child]:border-b-0 [&_tbody_tr:last-child_td:last-child]:border-r-0 [&_thead_tr:first-child_th]:border-t-0 [&_tbody_tr:last-child_td]:border-b-0 [&_th:first-child]:border-l-0 [&_td:first-child]:border-l-0 [&_th:last-child]:border-r-0 [&_td:last-child]:border-r-0" id="bkmrk-tham-s%E1%BB%91-bi%E1%BA%BFn-m%C3%B4i-tr%C6%B0"><table class="min-w-full border-separate border-spacing-0"><thead><tr><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Tham số</th><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Biến môi trường (Env)</th><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Giá trị mặc định</th><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Mô tả</th></tr></thead><tbody><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`server.port`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">`SERVER_PORT`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">`8080`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Port lắng nghe của Proxy</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`external.api.base-url`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">`EXTERNAL_API_BASE_URL`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">`https://administrator.lifetex.vn:356`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">URL dịch vụ CRM/BPMN</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`signing-service.api.base-url`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">`SIGNING_SERVICE_BASE_URL`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">`http://192.168.0.51:6868`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">URL dịch vụ ký số (Engine)</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`callback.server.base-url`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">`CALLBACK_SERVER_BASE_URL`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">`http://192.168.0.185:8386`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">URL nhận callback</td></tr></tbody></table>

</div>### 2.2 Cấu hình Timeout (Quan trọng cho ký số)

Các yêu cầu ký file PDF lớn có thể mất nhiều thời gian, do đó cần chú ý cấu hình:

- `connect-timeout-ms`: 60000 (60 giây).
- `read-timeout-ms`: 60000 (60 giây).

# Trang 3: Build & Deployment

Dự án đã tích hợp sẵn các script tự động hóa cho các hệ điều hành:

### 3.1 Triển khai trên Windows

1. Sử dụng file `run-windows.bat`.
2. Script sẽ tự động: 
    - Kiểm tra sự tồn tại của file JAR trong thư mục `target/`.
    - Nếu chưa có, script sẽ tự chạy `mvn clean package` để build.
    - Sau đó khởi chạy ứng dụng bằng Java và ghi log ra file `app.log`.

### 3.2 Triển khai trên Linux/macOS

1. Cấp quyền thực thi: `chmod +x run-linux.sh`
2. Chạy lệnh: `./run-linux.sh`
3. Script sẽ build project (nếu cần) và khởi chạy ứng dụng dưới dạng tiến trình (Process).

# Trang 4: Logging & Giám sát (Monitoring)

### 4.1 File Log

- **Windows**: Log được ghi song song vào Console và file `app.log` tại thư mục gốc của dự án.
- **Linux**: Log mặc định đẩy ra STDOUT (có thể chuyển hướng bằng `> app.log 2>&1`).

### 4.2 Mức độ Log (Log Levels)

Bạn có thể cấu hình mức độ chi tiết của log trong `application.yml`:

```yaml
logging:
  level:
    org.springframework: INFO
    com.example: DEBUG # Bật DEBUG để xem các URL thực tế proxy đang gọi
```

<p class="callout warning">Khi chạy trong môi trường Production, hãy chuyển `com.example` về mức `INFO` để tránh ghi log quá nhiều, gây đầy ổ cứng.</p>

# CHƯƠNG 4: DANH MỤC API (API REFERENCE)

Tất cả các API dưới đây đều được Proxy chuyển tiếp (Forward) tới các Service Backend tương ứng. Proxy thực hiện việc ánh xạ Header và xử lý lỗi mạng tự động.

# Trang 1: Nhóm API Ký số (Signing Proxy)

<table class="min-w-full border-separate border-spacing-0" id="bkmrk-endpoint-method-m%C3%B4-t"><thead><tr><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Endpoint</th><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Method</th><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Mô tả</th></tr></thead><tbody><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/scan-keywords`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Quét tọa độ từ khóa (keywords) trong file PDF để xác định vị trí ký.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/request-otp`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Gửi yêu cầu lấy mã OTP phục vụ ký số.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/verify-otp`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Xác thực mã OTP trước khi thực hiện ký.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/document`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Ký một tài liệu PDF đơn lẻ (truyền file Multipart).</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/document-json`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Ký tài liệu PDF thông qua dữ liệu JSON (Base64).</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/document-with-image`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Ký tài liệu có chèn hình ảnh chữ ký tươi (Insert Image).</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/document-initial-signature`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Ký nháy tài liệu (Initial Signature) dựa trên tọa độ hoặc từ khóa.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/document-formal-initial-signature`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Ký nháy chính thức (Formal Initial Signature).</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/sign/revoke-p12/{username}`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">DELETE</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Thu hồi chứng thư số P12 của người dùng.</td></tr></tbody></table>

# Trang 2: Nhóm API Quy trình (BPMN Proxy)

<table class="min-w-full border-separate border-spacing-0" id="bkmrk-endpoint-method-m%C3%B4-t"><thead><tr><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Endpoint</th><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Method</th><th class="bg-gray-500/20 px-3 py-2 text-left font-medium leading-snug border border-gray-500/20">Mô tả</th></tr></thead><tbody><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/proxy/start`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Khởi tạo một phiên quy trình nghiệp vụ (Process Start).</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/proxy/tasks/{pid}`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">GET</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Lấy danh sách nhiệm vụ (Tasks) của một Process Instance ID.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/proxy/tasks/{tid}/complete`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Đánh dấu hoàn thành một nhiệm vụ cụ thể.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/proxy/bpmn/find-and-update`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">PUT</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Tìm kiếm và cập nhật thông tin quy trình BPMN.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/proxy/bpmn/find-and-submit-form`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Tìm và gửi dữ liệu Form trong quy trình.</td></tr><tr><td class="px-3 py-2 leading-snug border border-gray-500/20">`/api/proxy/integration-signature/callback`</td><td class="px-3 py-2 leading-snug border border-gray-500/20">POST</td><td class="px-3 py-2 leading-snug border border-gray-500/20">Gửi phản hồi (callback) sau khi ký số hoàn tất.</td></tr></tbody></table>

# CHƯƠNG 5: BẢO TRÌ & MỞ RỘNG

Cách duy trì và phát triển

# Trang 1: Xử lý lỗi & Recovery chuyên sâu

Hệ thống sử dụng `ExternalApiClient` làm trung tâm điều phối. Nếu bạn muốn thay đổi logic xử lý khi Backend gặp lỗi:

1. **Thay đổi số lần thử lại**: Sửa `maxAttempts` trong `@Retryable`.
2. **Sửa logic trả về mặc định**: Cập nhật các phương thức `@Recover`. Ví dụ, bạn có thể gửi cảnh báo qua Telegram/Email nếu tất cả 3 lần thử lại đều thất bại.

# Trang 2: Hướng dẫn thêm Endpoint Proxy mới

Để thêm một đường dẫn Proxy mới vào hệ thống, hãy thực hiện 3 bước:

1. **Bước 1 (Controller)**: Tạo phương thức mới trong `SigningProxyController` hoặc `ExternalProxyController` để đón nhận request.
2. **Bước 2 (Service)**: Định nghĩa interface và triển khai forwarding.
3. **Bước 3 (Client)**: Sử dụng `WebClient` trong `ExternalApiClient` để gọi tới URL backend mới.

### Ví dụ thêm API lấy thông tin Server:

```java
// Trong ExternalApiClient.java
public ResponseEntity<String> callNewApi() {
    return webClient.get()
            .uri(baseUrl + "/api/v1/info")
            .retrieve()
            .toEntity(String.class)
            .block();
}
```

# Trang 3: Check-list bàn giao cho Dev tiếp quản

- Kiểm tra file `application.yml` đã đúng URL của môi trường mới chưa.
- Bật `logging.level.com.example=DEBUG` để kiểm tra các request đầu tiên.
- Đảm bảo cổng Port (8080) không bị chiếm dụng bởi ứng dụng khác.
- Xác nhận Client đã gửi Header `Token-Signing` (cần thiết).