分布式高并发Springcloud系统下的数据图同步断点续传方案【订单/商品/用户等】

1. 核心需求复述

完全解耦、可复用的数据同步断点续传方案核心目标是让断点续传的通用逻辑(分布式锁、断点读写、批次控制、重试、幂等)与具体业务逻辑(订单/商品/用户数据同步)分离,新增任意业务数据同步时,仅需实现差异化的查询和同步逻辑,无需重复开发通用的断点续传框架。

2. 通用解耦版断点续传完整方案

2.1 整体架构设计

采用「接口抽象 + 模板方法模式 + 策略模式」实现解耦:

  • 抽象层 :定义SyncDataSource(数据源)和SyncHandler(同步处理器)接口,封装业务差异化逻辑;
  • 模板层GenericSyncTemplate封装所有通用断点续传逻辑(锁、断点、重试、批次),依赖抽象接口而非具体业务;
  • 业务层 :订单/商品等业务实现抽象接口,Spring自动收集所有实现类,通过bizCode动态路由;
css 复制代码
flowchart TB
    A[控制器] -->|传入bizCode| B[GenericSyncTemplate(通用模板)]
    B -->|1. 分布式锁| C[Redisson]
    B -->|2. 断点读写| D[BreakPointManager]
    B -->|3. 任务状态| E[SyncTaskService]
    B -->|4. 路由到业务实现| F{bizCode匹配}
    F -->|order_sync| G[OrderSyncDataSource + OrderSyncHandler]
    F -->|product_sync| H[ProductSyncDataSource + ProductSyncHandler]
    F -->|user_sync| I[UserSyncDataSource + UserSyncHandler]
    G/H/I -->|实现| J[SyncDataSource(查数据)+ SyncHandler(同步数据)]

2.2 完整可运行代码

2.2.1 环境依赖(pom.xml)
xml 复制代码
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>sync-resume-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sync-resume-generic</name>
    <description>通用解耦版数据同步断点续传</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.8</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- SpringCloud核心 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2021.0.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- SpringBoot核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- 分布式锁/Redis -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.23.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- 数据库 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>

        <!-- 工具类 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
2.2.2 核心配置(application.yml)
yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: sync-resume-generic
  # Nacos注册中心
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  # 数据库配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/sync_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
  # Redis配置
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      password:
      database: 0

# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.sync.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# 同步通用配置
sync:
  batch-size: 1000        # 默认批次大小
  lock-prefix: sync_lock  # 分布式锁前缀
  break-point-prefix: sync_break_point_ # 断点前缀
  retry-count: 3          # 默认重试次数
2.2.3 核心抽象接口
通用数据源接口(SyncDataSource)
java 复制代码
package com.example.sync.core;

import java.util.List;

/**
 * 通用同步数据源接口(定义"查什么数据"的差异化逻辑)
 * @param <T> 业务数据类型(如OrderDO、ProductDO)
 */
public interface SyncDataSource<T> {
    /**
     * 按断点查询待同步数据
     * @param lastBreakPoint 最后同步的断点(ID)
     * @param batchSize 批次大小
     * @return 待同步数据列表
     */
    List<T> selectWaitSyncData(Long lastBreakPoint, Integer batchSize);

    /**
     * 批量更新同步状态(幂等保障)
     * @param dataIds 业务数据ID列表
     * @param syncStatus 同步状态(0-未同步,1-已同步)
     */
    void batchUpdateSyncStatus(List<Long> dataIds, Integer syncStatus);

    /**
     * 获取单条数据的ID(统一断点标识)
     * @param data 业务数据
     * @return 数据ID
     */
    Long getDataId(T data);

    /**
     * 获取业务编码(与SyncHandler一致)
     * @return 业务编码(如order_sync)
     */
    String getBizCode();
}
通用同步处理器接口(SyncHandler)
java 复制代码
package com.example.sync.core;

import java.util.List;

/**
 * 通用同步处理器接口(定义"怎么同步数据"的差异化逻辑)
 * @param <T> 业务数据类型(如OrderDO、ProductDO)
 */
public interface SyncHandler<T> {
    /**
     * 同步数据到目标服务/数据库
     * @param dataList 待同步数据列表
     */
    void syncToTarget(List<T> dataList);

    /**
     * 获取业务编码(唯一标识,如order_sync、product_sync)
     * @return 业务编码
     */
    String getBizCode();
}
通用同步上下文(SyncContext)
arduino 复制代码
package com.example.sync.core;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * 通用同步上下文(封装任务参数,避免方法参数过多)
 */
@Data
@Accessors(chain = true)
public class SyncContext {
    /** 业务编码(核心:路由到具体业务实现) */
    private String bizCode;
    /** 批次大小(默认取配置) */
    private Integer batchSize;
    /** 重试次数(默认取配置) */
    private Integer retryCount;
    /** 分布式锁前缀(默认取配置) */
    private String lockPrefix;
    /** 断点前缀(默认取配置) */
    private String breakPointPrefix;
}
2.2.4 通用模板类(核心封装)
ini 复制代码
package com.example.sync.core;

import com.example.sync.service.SyncTaskService;
import com.example.sync.util.BreakPointManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 通用断点续传同步模板类(封装所有通用逻辑,业务无需关心)
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class GenericSyncTemplate {
    // 通用配置
    @Value("${sync.batch-size}")
    private Integer defaultBatchSize;
    @Value("${sync.lock-prefix}")
    private String defaultLockPrefix;
    @Value("${sync.break-point-prefix}")
    private String defaultBreakPointPrefix;
    @Value("${sync.retry-count}")
    private Integer defaultRetryCount;

    // 通用组件
    private final BreakPointManager breakPointManager;
    private final SyncTaskService syncTaskService;
    private final RedissonClient redissonClient;

    // Spring自动收集所有业务实现(key=bean名称,需手动适配bizCode)
    private final Map<String, SyncDataSource<?>> syncDataSourceMap;
    private final Map<String, SyncHandler<?>> syncHandlerMap;

    /**
     * 通用同步方法(启动/恢复断点续传)
     * @param context 同步上下文
     */
    @SuppressWarnings("unchecked")
    public void startOrResumeSync(SyncContext context) {
        // 1. 补全上下文默认值
        String bizCode = context.getBizCode();
        Integer batchSize = context.getBatchSize() == null ? defaultBatchSize : context.getBatchSize();
        Integer retryCount = context.getRetryCount() == null ? defaultRetryCount : context.getRetryCount();
        String lockPrefix = context.getLockPrefix() == null ? defaultLockPrefix : context.getLockPrefix();

        // 2. 查找业务实现(按bizCode匹配)
        SyncDataSource<?> dataSource = findDataSourceByBizCode(bizCode);
        SyncHandler<?> handler = findHandlerByBizCode(bizCode);
        if (dataSource == null || handler == null) {
            throw new RuntimeException("未找到业务[" + bizCode + "]的同步实现类");
        }

        // 3. 分布式锁:避免高并发下多节点执行同一任务
        String lockKey = lockPrefix + "_" + bizCode;
        RLock lock = redissonClient.getLock(lockKey);
        try {
            boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
            if (!locked) {
                log.warn("业务[{}]获取分布式锁失败,任务已在执行", bizCode);
                return;
            }

            // 4. 初始化断点(Redis → MySQL)
            Long lastBreakPoint = breakPointManager.getBreakPoint(bizCode);
            if (lastBreakPoint == 0) {
                lastBreakPoint = syncTaskService.getSyncTaskByCode(bizCode).getLastBreakPoint();
            }
            log.info("业务[{}]开始同步,断点位置:{}", bizCode, lastBreakPoint);

            // 5. 更新任务状态为"同步中"
            syncTaskService.updateTaskStatus(bizCode, 1);

            // 6. 批次同步主逻辑
            int retry = 0;
            while (true) {
                try {
                    // 6.1 查询待同步数据(调用业务数据源实现)
                    List<?> dataList = dataSource.selectWaitSyncData(lastBreakPoint, batchSize);
                    if (dataList.isEmpty()) {
                        log.info("业务[{}]同步完成,最终断点:{}", bizCode, lastBreakPoint);
                        // 同步完成:清理断点+更新任务状态
                        breakPointManager.clearBreakPoint(bizCode);
                        syncTaskService.updateTaskBreakPoint(bizCode, lastBreakPoint);
                        syncTaskService.updateTaskStatus(bizCode, 2);
                        break;
                    }

                    // 6.2 同步数据到目标(调用业务处理器实现)
                    handler.syncToTarget(dataList);

                    // 6.3 更新同步状态(幂等保障)
                    List<Long> dataIds = (List<Long>) dataList.stream()
                            .map(data -> dataSource.getDataId((Object) data))
                            .toList();
                    dataSource.batchUpdateSyncStatus(dataIds, 1);

                    // 6.4 更新断点(Redis实时保存)
                    lastBreakPoint = dataSource.getDataId(dataList.get(dataList.size() - 1));
                    breakPointManager.updateBreakPoint(bizCode, lastBreakPoint);
                    log.info("业务[{}]批次同步完成,更新断点:{}", bizCode, lastBreakPoint);

                    // 重置重试次数
                    retry = 0;
                } catch (Exception e) {
                    retry++;
                    log.error("业务[{}]同步失败,断点:{},重试次数:{}", bizCode, lastBreakPoint, retry, e);
                    if (retry >= retryCount) {
                        // 重试超限:标记任务异常
                        syncTaskService.updateTaskStatus(bizCode, 3);
                        throw new RuntimeException("业务[" + bizCode + "]同步失败,重试次数超限", e);
                    }
                    // 短暂休眠后重试
                    TimeUnit.SECONDS.sleep(1);
                }
            }
        } catch (Exception e) {
            log.error("业务[{}]同步异常", bizCode, e);
            syncTaskService.updateTaskStatus(bizCode, 3);
        } finally {
            // 释放分布式锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 按bizCode查找数据源实现
     */
    private SyncDataSource<?> findDataSourceByBizCode(String bizCode) {
        return syncDataSourceMap.values().stream()
                .filter(dataSource -> bizCode.equals(dataSource.getBizCode()))
                .findFirst()
                .orElse(null);
    }

    /**
     * 按bizCode查找处理器实现
     */
    private SyncHandler<?> findHandlerByBizCode(String bizCode) {
        return syncHandlerMap.values().stream()
                .filter(handler -> bizCode.equals(handler.getBizCode()))
                .findFirst()
                .orElse(null);
    }
}
2.2.5 通用工具类
断点管理工具(BreakPointManager)
typescript 复制代码
package com.example.sync.util;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * 通用断点管理工具(Redis存储实时断点)
 */
@Component
@RequiredArgsConstructor
public class BreakPointManager {
    private final StringRedisTemplate redisTemplate;

    @Value("${sync.break-point-prefix}")
    private String breakPointPrefix;

    /**
     * 获取业务断点
     */
    public Long getBreakPoint(String bizCode) {
        String key = breakPointPrefix + bizCode;
        String value = redisTemplate.opsForValue().get(key);
        return StringUtils.hasText(value) ? Long.parseLong(value) : 0L;
    }

    /**
     * 更新业务断点
     */
    public void updateBreakPoint(String bizCode, Long breakPoint) {
        String key = breakPointPrefix + bizCode;
        redisTemplate.opsForValue().set(key, breakPoint.toString());
    }

    /**
     * 清理业务断点
     */
    public void clearBreakPoint(String bizCode) {
        String key = breakPointPrefix + bizCode;
        redisTemplate.delete(key);
    }
}
幂等注解(Idempotent)
java 复制代码
package com.example.sync.annotation;

import java.lang.annotation.*;

/**
 * 通用幂等注解(避免重复同步)
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {
    /** 幂等Key前缀 */
    String prefix() default "sync_idempotent_";
    /** 过期时间(秒) */
    long expireTime() default 300;
}
幂等切面(IdempotentAspect)
java 复制代码
package com.example.sync.aspect;

import com.example.sync.annotation.Idempotent;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.UUID;

/**
 * 通用幂等切面(基于Redis)
 */
@Aspect
@Component
@RequiredArgsConstructor
public class IdempotentAspect {
    private final StringRedisTemplate redisTemplate;

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);

        // 生成幂等Key:前缀 + 方法名 + 第一个参数(业务数据ID)
        String key = idempotent.prefix() + method.getName() + "_" + joinPoint.getArgs()[0];
        String value = UUID.randomUUID().toString();

        // SETNX实现幂等(不存在则设置)
        Boolean success = redisTemplate.opsForValue().setIfAbsent(
                key, value, idempotent.expireTime(), java.util.concurrent.TimeUnit.SECONDS
        );
        if (Boolean.FALSE.equals(success)) {
            throw new RuntimeException("当前数据正在同步,请勿重复操作");
        }

        try {
            return joinPoint.proceed();
        } finally {
            // 正常执行完成后删除Key
            redisTemplate.delete(key);
        }
    }
}
2.2.6 基础实体与服务
同步任务实体(SyncTask)
kotlin 复制代码
package com.example.sync.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * 同步任务配置(持久化到MySQL)
 */
@Data
@TableName("sync_task")
public class SyncTask {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String taskName;       // 任务名称
    private String taskCode;       // 任务编码(与bizCode一致)
    private String source;         // 数据源
    private String target;         // 目标源
    private Long lastBreakPoint;   // 最终断点
    private Integer status;        // 状态:0-未开始 1-同步中 2-已完成 3-异常
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
同步任务服务(SyncTaskService)
java 复制代码
package com.example.sync.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.sync.entity.SyncTask;
import com.example.sync.mapper.SyncTaskMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 同步任务配置服务(通用,所有业务复用)
 */
@Service
@RequiredArgsConstructor
public class SyncTaskService {
    private final SyncTaskMapper syncTaskMapper;

    /**
     * 按业务编码查询任务
     */
    public SyncTask getSyncTaskByCode(String bizCode) {
        return syncTaskMapper.selectOne(new LambdaQueryWrapper<SyncTask>()
                .eq(SyncTask::getTaskCode, bizCode));
    }

    /**
     * 更新任务断点
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateTaskBreakPoint(String bizCode, Long breakPoint) {
        SyncTask task = getSyncTaskByCode(bizCode);
        if (task == null) {
            throw new RuntimeException("同步任务不存在:" + bizCode);
        }
        task.setLastBreakPoint(breakPoint);
        task.setUpdateTime(LocalDateTime.now());
        syncTaskMapper.updateById(task);
    }

    /**
     * 更新任务状态
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateTaskStatus(String bizCode, Integer status) {
        SyncTask task = getSyncTaskByCode(bizCode);
        if (task == null) {
            throw new RuntimeException("同步任务不存在:" + bizCode);
        }
        task.setStatus(status);
        task.setUpdateTime(LocalDateTime.now());
        syncTaskMapper.updateById(task);
    }
}
同步任务Mapper(SyncTaskMapper)
java 复制代码
package com.example.sync.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.sync.entity.SyncTask;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SyncTaskMapper extends BaseMapper<SyncTask> {
}
2.2.7 业务实现示例(订单+商品)
订单实体(OrderDO)
java 复制代码
package com.example.sync.entity;

import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单业务实体
 */
@Data
public class OrderDO {
    private Long id;             // 主键(断点标识)
    private String orderNo;      // 订单号
    private BigDecimal amount;   // 金额
    private LocalDateTime createTime;
    private Integer syncStatus;  // 同步状态:0-未同步 1-已同步
}
订单数据源实现(OrderSyncDataSource)
kotlin 复制代码
package com.example.sync.biz.order;

import com.example.sync.core.SyncDataSource;
import com.example.sync.entity.OrderDO;
import com.example.sync.mapper.OrderMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 订单同步数据源(实现通用接口,仅写订单相关查询逻辑)
 */
@Component
@RequiredArgsConstructor
public class OrderSyncDataSource implements SyncDataSource<OrderDO> {
    private final OrderMapper orderMapper;

    @Override
    public List<OrderDO> selectWaitSyncData(Long lastBreakPoint, Integer batchSize) {
        // 订单业务的查询逻辑:按断点ID查询待同步订单
        return orderMapper.selectWaitSyncOrder(lastBreakPoint, batchSize);
    }

    @Override
    public void batchUpdateSyncStatus(List<Long> dataIds, Integer syncStatus) {
        // 订单业务的状态更新逻辑
        orderMapper.batchUpdateSyncStatus(dataIds, syncStatus);
    }

    @Override
    public Long getDataId(OrderDO data) {
        // 订单的断点标识是ID
        return data.getId();
    }

    @Override
    public String getBizCode() {
        // 订单业务编码
        return "order_sync";
    }
}
订单同步处理器(OrderSyncHandler)
java 复制代码
package com.example.sync.biz.order;

import com.example.sync.annotation.Idempotent;
import com.example.sync.core.SyncHandler;
import com.example.sync.entity.OrderDO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 订单同步处理器(实现通用接口,仅写订单同步逻辑)
 */
@Slf4j
@Component
public class OrderSyncHandler implements SyncHandler<OrderDO> {
    @Override
    @Idempotent(prefix = "order_sync_", expireTime = 300)
    public void syncToTarget(List<OrderDO> dataList) {
        // 订单业务的同步逻辑:调用Feign接口同步到仓储服务(示例)
        log.info("同步{}条订单数据到仓储服务:{}", dataList.size(), 
                dataList.stream().map(OrderDO::getOrderNo).toList());
        // 实际代码:orderFeignClient.syncOrderData(dataList);
    }

    @Override
    public String getBizCode() {
        return "order_sync";
    }
}
订单Mapper(OrderMapper)
less 复制代码
package com.example.sync.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.sync.entity.OrderDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface OrderMapper extends BaseMapper<OrderDO> {
    List<OrderDO> selectWaitSyncOrder(@Param("lastId") Long lastId, @Param("batchSize") Integer batchSize);
    int batchUpdateSyncStatus(@Param("ids") List<Long> ids, @Param("syncStatus") Integer syncStatus);
}
订单Mapper XML(OrderMapper.xml)
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.sync.mapper.OrderMapper">
    <select id="selectWaitSyncOrder" resultType="com.example.sync.entity.OrderDO">
        SELECT id, order_no, amount, create_time, sync_status
        FROM order_table
        WHERE id > #{lastId} AND sync_status = 0
        ORDER BY id ASC LIMIT #{batchSize}
    </select>

    <update id="batchUpdateSyncStatus">
        UPDATE order_table SET sync_status = #{syncStatus}
        WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </update>
</mapper>
商品实体(ProductDO)
java 复制代码
package com.example.sync.entity;

import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 商品业务实体
 */
@Data
public class ProductDO {
    private Long id;             // 主键(断点标识)
    private String productNo;    // 商品编码
    private String productName;  // 商品名称
    private BigDecimal price;    // 价格
    private LocalDateTime createTime;
    private Integer syncStatus;  // 同步状态:0-未同步 1-已同步
}
商品数据源实现(ProductSyncDataSource)
kotlin 复制代码
package com.example.sync.biz.product;

import com.example.sync.core.SyncDataSource;
import com.example.sync.entity.ProductDO;
import com.example.sync.mapper.ProductMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 商品同步数据源(实现通用接口,仅写商品相关查询逻辑)
 */
@Component
@RequiredArgsConstructor
public class ProductSyncDataSource implements SyncDataSource<ProductDO> {
    private final ProductMapper productMapper;

    @Override
    public List<ProductDO> selectWaitSyncData(Long lastBreakPoint, Integer batchSize) {
        // 商品业务的查询逻辑
        return productMapper.selectWaitSyncProduct(lastBreakPoint, batchSize);
    }

    @Override
    public void batchUpdateSyncStatus(List<Long> dataIds, Integer syncStatus) {
        // 商品业务的状态更新逻辑
        productMapper.batchUpdateSyncStatus(dataIds, syncStatus);
    }

    @Override
    public Long getDataId(ProductDO data) {
        return data.getId();
    }

    @Override
    public String getBizCode() {
        // 商品业务编码
        return "product_sync";
    }
}
商品同步处理器(ProductSyncHandler)
java 复制代码
package com.example.sync.biz.product;

import com.example.sync.annotation.Idempotent;
import com.example.sync.core.SyncHandler;
import com.example.sync.entity.ProductDO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 商品同步处理器(实现通用接口,仅写商品同步逻辑)
 */
@Slf4j
@Component
public class ProductSyncHandler implements SyncHandler<ProductDO> {
    @Override
    @Idempotent(prefix = "product_sync_", expireTime = 300)
    public void syncToTarget(List<ProductDO> dataList) {
        // 商品业务的同步逻辑:调用Feign接口同步到商城服务(示例)
        log.info("同步{}条商品数据到商城服务:{}", dataList.size(),
                dataList.stream().map(ProductDO::getProductNo).toList());
        // 实际代码:productFeignClient.syncProductData(dataList);
    }

    @Override
    public String getBizCode() {
        return "product_sync";
    }
}
商品Mapper(ProductMapper)
less 复制代码
package com.example.sync.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.sync.entity.ProductDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface ProductMapper extends BaseMapper<ProductDO> {
    List<ProductDO> selectWaitSyncProduct(@Param("lastId") Long lastId, @Param("batchSize") Integer batchSize);
    int batchUpdateSyncStatus(@Param("ids") List<Long> ids, @Param("syncStatus") Integer syncStatus);
}
商品Mapper XML(ProductMapper.xml)
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.sync.mapper.ProductMapper">
    <select id="selectWaitSyncProduct" resultType="com.example.sync.entity.ProductDO">
        SELECT id, product_no, product_name, price, create_time, sync_status
        FROM product_table
        WHERE id > #{lastId} AND sync_status = 0
        ORDER BY id ASC LIMIT #{batchSize}
    </select>

    <update id="batchUpdateSyncStatus">
        UPDATE product_table SET sync_status = #{syncStatus}
        WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </update>
</mapper>
2.2.8 控制器(通用接口)
kotlin 复制代码
package com.example.sync.controller;

import com.example.sync.core.GenericSyncTemplate;
import com.example.sync.core.SyncContext;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 通用同步控制器(所有业务复用同一个接口)
 */
@RestController
@RequestMapping("/sync")
@RequiredArgsConstructor
public class SyncController {
    private final GenericSyncTemplate genericSyncTemplate;

    /**
     * 启动/恢复任意业务的同步任务
     * @param bizCode 业务编码(order_sync/product_sync)
     * @return 响应信息
     */
    @GetMapping("/start/{bizCode}")
    public String startSync(@PathVariable String bizCode) {
        try {
            // 构建同步上下文(仅需传入业务编码,其他用默认配置)
            SyncContext context = new SyncContext().setBizCode(bizCode);
            genericSyncTemplate.startOrResumeSync(context);
            return "业务[" + bizCode + "]同步任务启动/恢复成功";
        } catch (Exception e) {
            return "业务[" + bizCode + "]同步任务启动/恢复失败:" + e.getMessage();
        }
    }
}
2.2.9 启动类
kotlin 复制代码
package com.example.sync;

import org.mybatis.spring.annotation.MapperScan;
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 // Nacos注册
@EnableFeignClients    // OpenFeign
@MapperScan("com.example.sync.mapper") // MyBatis-Plus扫描
public class SyncResumeGenericApplication {
    public static void main(String[] args) {
        SpringApplication.run(SyncResumeGenericApplication.class, args);
    }
}

2.3 测试验证

  1. 前置准备

    1. 创建MySQL数据库sync_db,执行以下建表语句:

      sql 复制代码
      -- 同步任务表
      CREATE TABLE sync_task (
          id BIGINT AUTO_INCREMENT PRIMARY KEY,
          task_name VARCHAR(100) NOT NULL,
          task_code VARCHAR(50) NOT NULL UNIQUE,
          source VARCHAR(200) NOT NULL,
          target VARCHAR(200) NOT NULL,
          last_break_point BIGINT DEFAULT 0,
          status TINYINT DEFAULT 0 COMMENT '0-未开始 1-同步中 2-已完成 3-异常',
          create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
          update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
      );
      -- 订单表
      CREATE TABLE order_table (
          id BIGINT AUTO_INCREMENT PRIMARY KEY,
          order_no VARCHAR(50) NOT NULL,
          amount DECIMAL(10,2) NOT NULL,
          create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
          sync_status TINYINT DEFAULT 0 COMMENT '0-未同步 1-已同步'
      );
      -- 商品表
      CREATE TABLE product_table (
          id BIGINT AUTO_INCREMENT PRIMARY KEY,
          product_no VARCHAR(50) NOT NULL,
          product_name VARCHAR(100) NOT NULL,
          price DECIMAL(10,2) NOT NULL,
          create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
          sync_status TINYINT DEFAULT 0 COMMENT '0-未同步 1-已同步'
      );
    2. 插入测试数据:

      sql 复制代码
      -- 订单同步任务
      INSERT INTO sync_task (task_name, task_code, source, target) 
      VALUES ('订单数据同步', 'order_sync', 'order_table', 'warehouse_order');
      -- 商品同步任务
      INSERT INTO sync_task (task_name, task_code, source, target) 
      VALUES ('商品数据同步', 'product_sync', 'product_table', 'mall_product');
      -- 测试订单数据
      INSERT INTO order_table (order_no, amount) VALUES ('ORDER001', 100.00), ('ORDER002', 200.00), ('ORDER003', 300.00);
      -- 测试商品数据
      INSERT INTO product_table (product_no, product_name, price) VALUES ('PROD001', '手机', 2999.00), ('PROD002', '电脑', 5999.00);
    3. 启动Nacos、Redis、MySQL,确保服务注册成功;

  2. 测试步骤

    1. 调用订单同步接口:http://127.0.0.1:8080/sync/start/order_sync,验证断点续传逻辑;
    2. 调用商品同步接口:http://127.0.0.1:8080/sync/start/product_sync,验证商品数据同步;
    3. 手动中断服务后重启,再次调用接口,验证从断点位置继续同步;

3. 总结

关键点回顾

  1. 解耦核心 :通过SyncDataSourceSyncHandler抽象业务差异化逻辑,GenericSyncTemplate封装通用断点续传逻辑,实现"通用逻辑复用,业务逻辑定制";

  2. 扩展便捷:新增业务(如用户数据同步)时,仅需:

    1. 定义业务实体(UserDO);
    2. 实现SyncDataSource<UserDO>SyncHandler<UserDO>
    3. 配置同步任务(无需修改通用代码);
  3. 核心保障:保留分布式锁(防并发)、幂等(防重复)、断点持久化(Redis+MySQL)、批次同步(防高并发超时)等核心能力,适配分布式高并发场景。

生产扩展建议

  • 动态配置:通过Nacos配置中心动态调整不同业务的批次大小、重试次数;
  • 监控告警:对接Prometheus+Grafana监控同步进度、断点、失败次数,异常时触发告警;
  • 异步执行:通过Spring Task/XXL-Job实现定时同步,避免接口阻塞;
  • 多数据源适配:扩展SyncDataSource支持MySQL/ES/MongoDB等不同数据源。
相关推荐
阿宁又菜又爱玩29 分钟前
Web后端开发入门
java·spring boot·后端·web
LDG_AGI33 分钟前
【推荐系统】深度学习训练框架(十三):模型输入——《特征索引》与《特征向量》的边界
人工智能·pytorch·分布式·深度学习·算法·机器学习
桃花键仙41 分钟前
vLLM-ascend快速上手:从零到一部署Llama2推理服务
后端
桃花键仙42 分钟前
PyTorch模型迁移昇腾平台全流程:ResNet50实战指南
后端
1024肥宅43 分钟前
告别异地登录告警!用 GitHub Self-Hosted Runner 打造“零打扰”全栈自动化部署
前端·后端·github
猪猪拆迁队1 小时前
高性能 Package构建系统设计与实现
前端·后端·node.js
_院长大人_1 小时前
Spring Boot 客户端设计示例:自动刷新 Token 并重试接口调用(Springboot Starter 封装)
java·spring boot·后端
前端fighter1 小时前
全栈项目:闲置二手交易系统(一)
前端·vue.js·后端
疯狂的程序猴1 小时前
Fastlane 结合 开心上架,构建跨平台可发布的 iOS 自动化流水线实践
后端