<think>我们将构建一个前后端分离的异步数据同步方案:
前端:React(使用axios进行HTTP请求)
后端:Spring Boot(使用@Async实现异步处理)
整体流程:
1. 前端调用后端同步接口
2. 后端立即返回任务ID(表示已接受请求,任务进入处理队列)
3. 前端轮询任务状态直到完成
4. 后端异步执行同步任务并更新状态
后端实现步骤:
a. 创建任务模型和状态存储(使用内存或数据库,这里使用ConcurrentHashMap模拟)
b. 使用Spring的@Async实现异步方法
c. 提供两个接口:启动同步(返回任务ID)、查询任务状态
前端实现步骤:
a. 调用启动接口获取任务ID
b. 使用setInterval轮询状态接口
c. 根据状态更新UI(轮询直到任务完成)
下面分别给出前后端代码:
### 后端代码 (Spring Boot)
1. 添加依赖(pom.xml):
```xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 使用H2作为内存数据库,简化示例 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
```
2. 启用异步支持(主类上添加注解):
```java
@SpringBootApplication
@EnableAsync
public class AsyncDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
}
}
```
3. 创建任务状态模型和存储服务:
```java
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Service;
@Service
public class TaskService {
// 存储任务状态,实际生产环境可用数据库或Redis
private Map<String, String> taskStatusMap = new ConcurrentHashMap<>();
public void createTask(String taskId) {
taskStatusMap.put(taskId, "PENDING");
}
public void updateTaskStatus(String taskId, String status) {
taskStatusMap.put(taskId, status);
}
public String getTaskStatus(String taskId) {
return taskStatusMap.getOrDefault(taskId, "NOT_FOUND");
}
}
```
4. 控制器和异步服务:
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/api/sync")
public class SyncController {
@Autowired
private TaskService taskService;
@Autowired
private SyncService syncService;
// 启动同步
@PostMapping("/start")
public SyncResponse startSync() {
String taskId = UUID.randomUUID().toString();
taskService.createTask(taskId);
// 异步调用同步方法
syncService.performSync(taskId);
return new SyncResponse(taskId);
}
// 查询任务状态
@GetMapping("/status/{taskId}")
public TaskStatusResponse getTaskStatus(@PathVariable String taskId) {
String status = taskService.getTaskStatus(taskId);
return new TaskStatusResponse(taskId, status);
}
}
// 异步服务
@Service
public class SyncService {
@Autowired
private TaskService taskService;
@Async
public void performSync(String taskId) {
try {
taskService.updateTaskStatus(taskId, "RUNNING");
// 模拟耗时操作(例如:同步数据)
Thread.sleep(5000); // 5秒
// 实际同步逻辑...
taskService.updateTaskStatus(taskId, "COMPLETED");
} catch (InterruptedException e) {
taskService.updateTaskStatus(taskId, "FAILED");
e.printStackTrace();
}
}
}
// 响应模型
public class SyncResponse {
private String taskId;
public SyncResponse(String taskId) {
this.taskId = taskId;
}
// getter and setter
}
public class TaskStatusResponse {
private String taskId;
private String status;
public TaskStatusResponse(String taskId, String status) {
this.taskId = taskId;
this.status = status;
}
// getters and setters
}
```
### 前端代码 (React)
1. 安装axios: `npm install axios`
2. 组件示例:
```jsx
import React, { useState } from 'react';
import axios from 'axios';
const SyncComponent = () => {
const [taskId, setTaskId] = useState(null);
const [status, setStatus] = useState('');
const [isLoading, setIsLoading] = useState(false);
const startSync = async () => {
setIsLoading(true);
try {
const response = await axios.post('http://localhost:8080/api/sync/start');
const newTaskId = response.data.taskId;
setTaskId(newTaskId);
setStatus('pending');
// 开始轮询状态
const intervalId = setInterval(async () => {
try {
const statusResponse = await axios.get(`http://localhost:8080/api/sync/status/${newTaskId}`);
const currentStatus = statusResponse.data.status;
setStatus(currentStatus);
// 如果任务完成或失败,清除轮询
if (currentStatus === 'COMPLETED' || currentStatus === 'FAILED') {
clearInterval(intervalId);
setIsLoading(false);
}
} catch (error) {
console.error('Error checking status:', error);
clearInterval(intervalId);
setIsLoading(false);
}
}, 1000); // 每秒轮询一次
} catch (error) {
console.error('Error starting sync:', error);
setIsLoading(false);
}
};
return (
<div>
<button onClick={startSync} disabled={isLoading}>
{isLoading ? 'Syncing...' : 'Start Data Sync'}
</button>
{taskId && (
<div>
<p>Task ID: {taskId}</p>
<p>Status: {status}</p>
</div>
)}
</div>
);
};
export default SyncComponent;
```
### 解释
**后端部分**:
- `@EnableAsync`:启用Spring的异步执行能力。
- `@Async`:标记方法为异步执行,Spring会将其放入线程池中运行。
- 任务状态存储:使用内存中的`ConcurrentHashMap`存储任务状态(实际生产环境应使用数据库或Redis)。
- 接口设计:
- `POST /api/sync/start`:创建任务并立即返回任务ID,后台异步执行同步。
- `GET /api/sync/status/{taskId}`:根据任务ID查询状态。
**前端部分**:
- 使用React的`useState`管理状态(任务ID、状态、加载状态)。
- 点击按钮触发`startSync`函数:
- 发送POST请求启动同步,获取任务ID。
- 使用`setInterval`每秒轮询任务状态。
- 当状态变为`COMPLETED`或`FAILED`时停止轮询。
### 注意事项
1. **线程池配置**:默认情况下,Spring使用一个简单的线程池。生产环境需自定义线程池:
```java
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.initialize();
return executor;
}
}
```
2. **状态存储**:示例使用内存存储,重启后状态丢失。生产环境应使用数据库或Redis。
3. **轮询优化**:前端轮询可能造成服务端压力,可考虑WebSocket推送状态更新。
### 扩展建议
- 添加任务超时处理
- 记录任务详细日志
- 实现任务取消功能
- 添加身份验证和权限控制
§§相关问题§§:
1. 如何防止前端频繁轮询给后端造成压力?
2. 除了轮询,还有哪些技术可以实现异步任务状态通知?
3. 在Spring Boot中如何配置多个不同的异步线程池?
4. 如果同步任务需要传递大量参数,后端接口应该如何设计?</think>下面我将详细讲解如何在 React 前端和 Spring Boot 后端之间实现异步数据同步方案,提供完整的代码实现和原理说明。
### 整体架构设计
```mermaid
sequenceDiagram
participant F as React前端
participant B as Spring Boot后端
F->>B: 1. 发起同步请求
B-->>F: 2. 立即返回任务ID
B->>B: 3. 异步执行同步任务
F->>B: 4. 定期轮询任务状态
B-->>F: 5. 返回任务状态
loop 直到任务完成
F->>B: 继续轮询
B-->>F: 返回最新状态
end
B-->>F: 6. 返回最终结果
```
### 后端实现 (Spring Boot)
#### 1. 添加依赖 (pom.xml)
```xml
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 数据库 (内存数据库) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Async 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-async</artifactId>
</dependency>
</dependencies>
```
#### 2. 启用异步支持 (主应用类)
```java
@SpringBootApplication
@EnableAsync
public class DataSyncApplication {
public static void main(String[] args) {
SpringApplication.run(DataSyncApplication.class, args);
}
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.initialize();
return executor;
}
}
```
#### 3. 实体类和存储库
```java
@Entity
public class SyncTask {
@Id
private String taskId;
private String status; // PENDING, PROCESSING, COMPLETED, FAILED
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private String result;
// 构造函数、getter和setter省略
}
public interface SyncTaskRepository extends JpaRepository<SyncTask, String> {
}
```
#### 4. 异步服务实现
```java
@Service
public class DataSyncService {
@Autowired
private SyncTaskRepository taskRepository;
@Async
public CompletableFuture<String> performDataSync(String taskId) {
// 更新任务状态为处理中
updateTaskStatus(taskId, "PROCESSING");
try {
// 模拟耗时操作(实际替换为真实同步逻辑)
Thread.sleep(5000);
// 模拟同步结果
String result = "{\"source\": \"CRM\", \"target\": \"ERP\", " +
"\"recordsSynced\": 150, \"timestamp\": \"" +
LocalDateTime.now() + "\"}";
// 更新任务状态为完成
updateTaskStatus(taskId, "COMPLETED", result);
return CompletableFuture.completedFuture(result);
} catch (InterruptedException e) {
updateTaskStatus(taskId, "FAILED", "同步失败: " + e.getMessage());
return CompletableFuture.failedFuture(e);
}
}
private void updateTaskStatus(String taskId, String status) {
updateTaskStatus(taskId, status, null);
}
private void updateTaskStatus(String taskId, String status, String result) {
SyncTask task = taskRepository.findById(taskId)
.orElseThrow(() -> new RuntimeException("任务不存在"));
task.setStatus(status);
task.setUpdatedAt(LocalDateTime.now());
if (result != null) {
task.setResult(result);
}
taskRepository.save(task);
}
}
```
#### 5. 控制器实现
```java
@RestController
@RequestMapping("/api/sync")
public class SyncController {
@Autowired
private DataSyncService dataSyncService;
@Autowired
private SyncTaskRepository taskRepository;
@PostMapping("/start")
public ResponseEntity<Map<String, String>> startSync() {
// 创建新任务
String taskId = UUID.randomUUID().toString();
SyncTask task = new SyncTask();
task.setTaskId(taskId);
task.setStatus("PENDING");
task.setCreatedAt(LocalDateTime.now());
taskRepository.save(task);
// 异步启动同步任务
dataSyncService.performDataSync(taskId);
return ResponseEntity.ok(Map.of(
"taskId", taskId,
"statusUrl", "/api/sync/status/" + taskId
));
}
@GetMapping("/status/{taskId}")
public ResponseEntity<Map<String, Object>> getSyncStatus(
@PathVariable String taskId) {
return taskRepository.findById(taskId)
.map(task -> ResponseEntity.ok(Map.of(
"taskId", task.getTaskId(),
"status", task.getStatus(),
"createdAt", task.getCreatedAt(),
"updatedAt", task.getUpdatedAt(),
"result", task.getResult()
)))
.orElse(ResponseEntity.notFound().build());
}
}
```
### 前端实现 (React)
#### 1. 同步服务模块 (src/services/syncService.js)
```javascript
import axios from 'axios';
const API_BASE_URL = 'http://localhost:8080/api/sync';
export const startSync = async () => {
try {
const response = await axios.post(`${API_BASE_URL}/start`);
return response.data;
} catch (error) {
console.error('启动同步失败:', error);
throw error;
}
};
export const checkSyncStatus = async (taskId) => {
try {
const response = await axios.get(`${API_BASE_URL}/status/${taskId}`);
return response.data;
} catch (error) {
console.error('检查状态失败:', error);
throw error;
}
};
```
#### 2. 同步组件实现 (src/components/SyncComponent.js)
```jsx
import React, { useState } from 'react';
import { startSync, checkSyncStatus } from '../services/syncService';
const SyncComponent = () => {
const [taskId, setTaskId] = useState(null);
const [status, setStatus] = useState('idle');
const [syncResult, setSyncResult] = useState(null);
const [error, setError] = useState(null);
const [isPolling, setIsPolling] = useState(false);
const [pollingInterval, setPollingInterval] = useState(null);
const handleStartSync = async () => {
try {
setStatus('starting');
setError(null);
setSyncResult(null);
const response = await startSync();
setTaskId(response.taskId);
setStatus('pending');
// 开始轮询状态
startPolling(response.taskId);
} catch (err) {
setError('启动同步失败');
setStatus('error');
}
};
const startPolling = (taskId) => {
setIsPolling(true);
const interval = setInterval(async () => {
try {
const statusResponse = await checkSyncStatus(taskId);
setStatus(statusResponse.status.toLowerCase());
if (statusResponse.result) {
setSyncResult(JSON.parse(statusResponse.result));
}
// 如果任务完成或失败,停止轮询
if (['completed', 'failed'].includes(statusResponse.status.toLowerCase())) {
clearInterval(interval);
setIsPolling(false);
}
} catch (err) {
console.error('轮询错误:', err);
clearInterval(interval);
setIsPolling(false);
setError('获取状态失败');
setStatus('error');
}
}, 2000); // 每2秒轮询一次
setPollingInterval(interval);
};
const getStatusText = () => {
switch (status) {
case 'idle': return '准备就绪';
case 'starting': return '正在启动同步...';
case 'pending': return '等待处理...';
case 'processing': return '同步进行中...';
case 'completed': return '同步完成!';
case 'failed': return '同步失败';
case 'error': return '发生错误';
default: return '未知状态';
}
};
return (
<div className="sync-container">
<h2>数据同步</h2>
{!taskId && (
<button
onClick={handleStartSync}
disabled={status === 'starting'}
>
{status === 'starting' ? '启动中...' : '开始同步'}
</button>
)}
{taskId && (
<div className="task-info">
<p>任务ID: {taskId}</p>
<p>状态: <span className={`status-${status}`}>{getStatusText()}</span></p>
{isPolling && <p>正在检查状态...</p>}
{status === 'completed' && syncResult && (
<div className="sync-result">
<h3>同步结果</h3>
<p>源系统: {syncResult.source}</p>
<p>目标系统: {syncResult.target}</p>
<p>同步记录数: {syncResult.recordsSynced}</p>
<p>完成时间: {syncResult.timestamp}</p>
</div>
)}
{status === 'failed' && (
<div className="error-message">
<p>同步失败,请重试或联系管理员</p>
</div>
)}
</div>
)}
{error && <div className="error">{error}</div>}
</div>
);
};
export default SyncComponent;
```
### 关键实现点解释
1. **后端异步处理**:
- 使用 `@EnableAsync` 开启异步支持
- 自定义线程池配置避免资源耗尽
- `@Async` 注解标记异步方法
- `CompletableFuture` 处理异步结果
2. **任务状态管理**:
- 使用数据库持久化任务状态
- 任务生命周期:PENDING → PROCESSING → COMPLETED/FAILED
- 每个状态变更都记录时间戳
3. **前端交互流程**:
- 启动同步后立即返回任务ID
- 使用轮询机制检查任务状态
- 状态更新时实时反馈给用户
- 提供完整的错误处理
4. **轮询策略优化**:
- 初始轮询间隔2秒
- 长时间运行任务可动态调整间隔
- 任务完成后自动停止轮询
### 生产环境增强建议
1. **任务超时处理**:
```java
@Async
@Timeout(value = 30, unit = TimeUnit.MINUTES)
public CompletableFuture<String> performDataSync(String taskId) {
// ...
}
```
2. **任务取消支持**:
```java
@PostMapping("/cancel/{taskId}")
public ResponseEntity<?> cancelSync(@PathVariable String taskId) {
// 实现任务取消逻辑
}
```
3. **WebSocket推送**:
- 使用STOMP协议替代轮询
- 任务状态变更时主动推送
- 减少不必要的HTTP请求
4. **分布式任务队列**:
- 集成RabbitMQ或Kafka
- 实现任务持久化和重试
- 支持大规模任务处理