Browse Source

企业黑名单同步

yaodaoseng 2 weeks ago
parent
commit
7ddcafcec6

+ 34 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/BlacklistSyncService.java

@@ -0,0 +1,34 @@
+package com.tzld.piaoquan.risk.control.service.sync;
+
+/**
+ * 黑名单同步服务接口
+ * 
+ * 提供企业微信黑名单数据同步功能
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+public interface BlacklistSyncService {
+    
+    /**
+     * 智能同步检查
+     * 
+     * 遍历所有企业,基于时间间隔和随机概率进行智能同步决策
+     */
+    void intelligentSyncCheck();
+    
+    /**
+     * 强制同步指定企业
+     * 
+     * @param corpId 企业ID
+     * @return true表示同步成功,false表示同步失败
+     */
+    boolean forceSyncCorp(Long corpId);
+    
+    /**
+     * 获取同步统计信息
+     * 
+     * @return 同步统计信息
+     */
+    String getSyncStatistics();
+}

+ 209 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/cache/RedisSyncTimeCache.java

@@ -0,0 +1,209 @@
+package com.tzld.piaoquan.risk.control.service.sync.cache;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Redis同步时间缓存服务
+ * 
+ * 管理每个企业的最后同步时间
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+@Component
+public class RedisSyncTimeCache {
+    
+    private static final Logger log = LoggerFactory.getLogger(RedisSyncTimeCache.class);
+    
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+    
+    private static final String TIME_PREFIX = "blacklist_sync_time:";
+    
+    /**
+     * 获取企业最后同步时间
+     * 
+     * @param corpId 企业ID
+     * @return 最后同步时间戳,如果不存在返回null
+     */
+    public Long getLastSyncTime(Long corpId) {
+        if (corpId == null) {
+            log.warn("获取同步时间时企业ID为空");
+            return null;
+        }
+        
+        String key = TIME_PREFIX + corpId;
+        
+        try {
+            String timeStr = redisTemplate.opsForValue().get(key);
+            
+            if (timeStr != null) {
+                try {
+                    Long syncTime = Long.valueOf(timeStr);
+                    log.debug("获取企业{}同步时间: {}", corpId, syncTime);
+                    return syncTime;
+                } catch (NumberFormatException e) {
+                    log.warn("企业{}的同步时间格式错误: {}", corpId, timeStr);
+                    // 删除格式错误的数据
+                    redisTemplate.delete(key);
+                    return null;
+                }
+            }
+            
+            log.debug("企业{}未找到同步时间记录", corpId);
+            return null;
+            
+        } catch (Exception e) {
+            log.error("获取企业{}同步时间异常", corpId, e);
+            return null;
+        }
+    }
+    
+    /**
+     * 更新企业最后同步时间
+     * 
+     * @param corpId 企业ID
+     */
+    public void updateLastSyncTime(Long corpId) {
+        updateLastSyncTime(corpId, System.currentTimeMillis());
+    }
+    
+    /**
+     * 更新企业最后同步时间
+     * 
+     * @param corpId 企业ID
+     * @param syncTime 同步时间戳
+     */
+    public void updateLastSyncTime(Long corpId, Long syncTime) {
+        if (corpId == null) {
+            log.warn("更新同步时间时企业ID为空");
+            return;
+        }
+        
+        if (syncTime == null) {
+            log.warn("更新企业{}同步时间时时间戳为空", corpId);
+            return;
+        }
+        
+        String key = TIME_PREFIX + corpId;
+        String currentTime = String.valueOf(syncTime);
+        
+        try {
+            redisTemplate.opsForValue().set(key, currentTime);
+            log.info("更新企业{}同步时间: {}", corpId, syncTime);
+        } catch (Exception e) {
+            log.error("更新企业{}同步时间异常", corpId, e);
+        }
+    }
+    
+    /**
+     * 删除企业同步时间记录
+     * 
+     * @param corpId 企业ID
+     */
+    public void removeLastSyncTime(Long corpId) {
+        if (corpId == null) {
+            log.warn("删除同步时间时企业ID为空");
+            return;
+        }
+        
+        String key = TIME_PREFIX + corpId;
+        
+        try {
+            Boolean result = redisTemplate.delete(key);
+            if (Boolean.TRUE.equals(result)) {
+                log.info("删除企业{}同步时间记录成功", corpId);
+            } else {
+                log.info("企业{}同步时间记录不存在", corpId);
+            }
+        } catch (Exception e) {
+            log.error("删除企业{}同步时间记录异常", corpId, e);
+        }
+    }
+    
+    /**
+     * 获取所有企业的同步时间信息
+     * 
+     * @return 企业ID和同步时间的映射
+     */
+    public Map<Long, Long> getAllSyncTimes() {
+        Map<Long, Long> result = new HashMap<>();
+        
+        try {
+            Set<String> keys = redisTemplate.keys(TIME_PREFIX + "*");
+            
+            if (keys != null && !keys.isEmpty()) {
+                log.debug("找到{}个企业同步时间记录", keys.size());
+                
+                for (String key : keys) {
+                    try {
+                        String corpIdStr = key.substring(TIME_PREFIX.length());
+                        Long corpId = Long.valueOf(corpIdStr);
+                        Long syncTime = getLastSyncTime(corpId);
+                        
+                        if (syncTime != null) {
+                            result.put(corpId, syncTime);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("解析企业ID失败: {}", key);
+                    }
+                }
+            } else {
+                log.debug("未找到任何企业同步时间记录");
+            }
+            
+        } catch (Exception e) {
+            log.error("获取所有企业同步时间异常", e);
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 批量更新企业同步时间
+     * 
+     * @param syncTimes 企业ID和同步时间的映射
+     */
+    public void batchUpdateSyncTimes(Map<Long, Long> syncTimes) {
+        if (syncTimes == null || syncTimes.isEmpty()) {
+            log.debug("批量更新同步时间:数据为空");
+            return;
+        }
+        
+        try {
+            for (Map.Entry<Long, Long> entry : syncTimes.entrySet()) {
+                updateLastSyncTime(entry.getKey(), entry.getValue());
+            }
+            log.info("批量更新{}个企业同步时间完成", syncTimes.size());
+        } catch (Exception e) {
+            log.error("批量更新企业同步时间异常", e);
+        }
+    }
+    
+    /**
+     * 清理所有同步时间记录
+     */
+    public void clearAllSyncTimes() {
+        try {
+            Set<String> keys = redisTemplate.keys(TIME_PREFIX + "*");
+            
+            if (keys != null && !keys.isEmpty()) {
+                Long deletedCount = redisTemplate.delete(keys);
+                log.info("清理同步时间记录完成,删除{}条记录", deletedCount);
+            } else {
+                log.info("没有找到需要清理的同步时间记录");
+            }
+            
+        } catch (Exception e) {
+            log.error("清理同步时间记录异常", e);
+        }
+    }
+}

+ 141 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/config/ApolloConfigService.java

@@ -0,0 +1,141 @@
+package com.tzld.piaoquan.risk.control.service.sync.config;
+
+import com.alibaba.fastjson.JSON;
+import com.ctrip.framework.apollo.Config;
+import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * Apollo配置服务
+ *
+ * 从Apollo配置中心读取同步参数配置
+ *
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+@Component
+public class ApolloConfigService {
+
+    private static final Logger log = LoggerFactory.getLogger(ApolloConfigService.class);
+
+    @ApolloConfig
+    private Config config;
+
+    /**
+     * 配置项Key
+     */
+    private static final String SYNC_CONFIG_KEY = "blacklist.sync.config";
+
+    /**
+     * 默认配置JSON字符串
+     */
+    private static final String DEFAULT_CONFIG_JSON =
+        "{\"minIntervalHours\":2,\"maxIntervalHours\":6,\"syncProbability\":0.333}";
+
+    /**
+     * 获取同步配置参数(JSON格式)
+     *
+     * @return 同步配置对象
+     */
+    public SyncConfig getSyncConfig() {
+        try {
+            String configJson = config.getProperty(SYNC_CONFIG_KEY, DEFAULT_CONFIG_JSON);
+            log.debug("读取Apollo配置: {}", configJson);
+
+            SyncConfig syncConfig = JSON.parseObject(configJson, SyncConfig.class);
+
+            if (syncConfig == null) {
+                log.warn("解析同步配置为空,使用默认配置");
+                return getDefaultSyncConfig();
+            }
+
+            if (!syncConfig.isValid()) {
+                log.warn("同步配置参数无效: {}, 使用默认配置", syncConfig);
+                return getDefaultSyncConfig();
+            }
+
+            log.info("成功读取同步配置: {}", syncConfig.getDescription());
+            return syncConfig;
+
+        } catch (Exception e) {
+            log.error("解析同步配置失败,使用默认配置", e);
+            return getDefaultSyncConfig();
+        }
+    }
+
+    /**
+     * 获取默认同步配置
+     *
+     * @return 默认配置对象
+     */
+    private SyncConfig getDefaultSyncConfig() {
+        SyncConfig config = new SyncConfig();
+        config.setMinIntervalHours(2);
+        config.setMaxIntervalHours(6);
+        config.setSyncProbability(0.333);
+
+        log.info("使用默认同步配置: {}", config.getDescription());
+        return config;
+    }
+
+    /**
+     * 获取配置的JSON字符串(用于调试)
+     *
+     * @return 配置JSON字符串
+     */
+    public String getSyncConfigJson() {
+        return config.getProperty(SYNC_CONFIG_KEY, DEFAULT_CONFIG_JSON);
+    }
+
+    /**
+     * 检查配置是否存在
+     *
+     * @return true表示配置存在,false表示使用默认配置
+     */
+    public boolean hasCustomConfig() {
+        try {
+            String configValue = config.getProperty(SYNC_CONFIG_KEY, null);
+            return configValue != null && !DEFAULT_CONFIG_JSON.equals(configValue);
+        } catch (Exception e) {
+            log.error("检查配置存在性异常", e);
+            return false;
+        }
+    }
+
+    /**
+     * 验证配置格式是否正确
+     *
+     * @return true表示配置格式正确,false表示格式错误
+     */
+    public boolean validateConfig() {
+        try {
+            SyncConfig config = getSyncConfig();
+            return config.isValid();
+        } catch (Exception e) {
+            log.error("验证配置格式异常", e);
+            return false;
+        }
+    }
+
+    /**
+     * 获取配置详细信息(用于监控和调试)
+     *
+     * @return 配置详细信息
+     */
+    public String getConfigInfo() {
+        try {
+            SyncConfig syncConfig = getSyncConfig();
+            return String.format(
+                "配置来源: %s, 配置内容: %s, 配置状态: %s",
+                hasCustomConfig() ? "Apollo" : "默认",
+                syncConfig.toString(),
+                syncConfig.isValid() ? "有效" : "无效"
+            );
+        } catch (Exception e) {
+            log.error("获取配置信息异常", e);
+            return "配置信息获取失败: " + e.getMessage();
+        }
+    }
+}

+ 143 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/config/SyncConfig.java

@@ -0,0 +1,143 @@
+package com.tzld.piaoquan.risk.control.service.sync.config;
+
+/**
+ * 同步配置实体类
+ * 
+ * 用于存储从Apollo读取的同步参数配置
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+public class SyncConfig {
+    
+    /**
+     * 最小同步间隔(小时)
+     * 小于此时间间隔的企业不会被同步
+     */
+    private int minIntervalHours = 2;
+    
+    /**
+     * 最大同步间隔(小时)
+     * 大于此时间间隔的企业必须被同步
+     */
+    private int maxIntervalHours = 6;
+    
+    /**
+     * 同步概率(0-1之间)
+     * 在最小和最大间隔之间的企业,按此概率决定是否同步
+     */
+    private double syncProbability = 0.333; // 三分之一概率
+    
+    /**
+     * 默认构造函数
+     */
+    public SyncConfig() {
+    }
+    
+    /**
+     * 带参数构造函数
+     * 
+     * @param minIntervalHours 最小间隔小时数
+     * @param maxIntervalHours 最大间隔小时数
+     * @param syncProbability 同步概率
+     */
+    public SyncConfig(int minIntervalHours, int maxIntervalHours, double syncProbability) {
+        this.minIntervalHours = minIntervalHours;
+        this.maxIntervalHours = maxIntervalHours;
+        this.syncProbability = syncProbability;
+    }
+    
+    public int getMinIntervalHours() {
+        return minIntervalHours;
+    }
+    
+    public void setMinIntervalHours(int minIntervalHours) {
+        this.minIntervalHours = minIntervalHours;
+    }
+    
+    public int getMaxIntervalHours() {
+        return maxIntervalHours;
+    }
+    
+    public void setMaxIntervalHours(int maxIntervalHours) {
+        this.maxIntervalHours = maxIntervalHours;
+    }
+    
+    public double getSyncProbability() {
+        return syncProbability;
+    }
+    
+    public void setSyncProbability(double syncProbability) {
+        this.syncProbability = syncProbability;
+    }
+    
+    /**
+     * 获取最小间隔毫秒数
+     * 
+     * @return 最小间隔毫秒数
+     */
+    public long getMinIntervalMillis() {
+        return minIntervalHours * 3600L * 1000L;
+    }
+    
+    /**
+     * 获取最大间隔毫秒数
+     * 
+     * @return 最大间隔毫秒数
+     */
+    public long getMaxIntervalMillis() {
+        return maxIntervalHours * 3600L * 1000L;
+    }
+    
+    /**
+     * 验证配置参数是否有效
+     * 
+     * @return true表示配置有效,false表示配置无效
+     */
+    public boolean isValid() {
+        return minIntervalHours > 0 
+            && maxIntervalHours > minIntervalHours 
+            && syncProbability >= 0 
+            && syncProbability <= 1;
+    }
+    
+    /**
+     * 获取配置的描述信息
+     * 
+     * @return 配置描述
+     */
+    public String getDescription() {
+        return String.format("最小间隔: %d小时, 最大间隔: %d小时, 同步概率: %.1f%%", 
+                minIntervalHours, maxIntervalHours, syncProbability * 100);
+    }
+    
+    @Override
+    public String toString() {
+        return String.format("SyncConfig{minInterval=%dh, maxInterval=%dh, probability=%.3f}", 
+                minIntervalHours, maxIntervalHours, syncProbability);
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        
+        SyncConfig that = (SyncConfig) obj;
+        return minIntervalHours == that.minIntervalHours
+            && maxIntervalHours == that.maxIntervalHours
+            && Double.compare(that.syncProbability, syncProbability) == 0;
+    }
+    
+    @Override
+    public int hashCode() {
+        int result = minIntervalHours;
+        result = 31 * result + maxIntervalHours;
+        long temp = Double.doubleToLongBits(syncProbability);
+        result = 31 * result + (int) (temp ^ (temp >>> 32));
+        return result;
+    }
+}

+ 58 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/data/BlacklistDataService.java

@@ -0,0 +1,58 @@
+package com.tzld.piaoquan.risk.control.service.sync.data;
+
+import com.tzld.piaoquan.risk.control.model.po.QywxCorpBlacklistUser;
+
+import java.util.List;
+
+/**
+ * 黑名单数据访问服务接口
+ * 
+ * 提供黑名单相关的数据库操作
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+public interface BlacklistDataService {
+    
+    /**
+     * 获取所有企业ID(去重)
+     * 
+     * @return 企业ID列表
+     */
+    List<Long> getAllCorpIds();
+    
+    /**
+     * 获取企业的登录用户UUID
+     * 
+     * @param corpId 企业ID
+     * @return 登录用户UUID,如果不存在返回null
+     */
+    String getLoginUserUuid(Long corpId);
+    
+    /**
+     * 清理企业黑名单数据
+     * 
+     * @param corpId 企业ID
+     * @return 删除的记录数
+     */
+    int clearCorpBlacklist(Long corpId);
+    
+    /**
+     * 批量插入黑名单数据
+     * 
+     * @param users 黑名单用户列表
+     * @return 插入的记录数
+     */
+    int batchInsertBlacklist(List<QywxCorpBlacklistUser> users);
+    
+    /**
+     * 根据企业ID查询黑名单用户数量
+     * 
+     * @param corpId 企业ID
+     * @return 黑名单用户数量
+     */
+    int getCorpBlacklistCount(Long corpId);
+
+
+
+}

+ 173 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/data/impl/BlacklistDataServiceImpl.java

@@ -0,0 +1,173 @@
+package com.tzld.piaoquan.risk.control.service.sync.data.impl;
+
+import com.tzld.piaoquan.risk.control.dao.mapper.QywxCorpBlacklistUserMapper;
+import com.tzld.piaoquan.risk.control.dao.mapper.UserBaseMapper;
+import com.tzld.piaoquan.risk.control.model.po.*;
+import com.tzld.piaoquan.risk.control.service.qywx.Constant;
+import com.tzld.piaoquan.risk.control.service.sync.data.BlacklistDataService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 黑名单数据访问服务实现类
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+@Service
+public class BlacklistDataServiceImpl implements BlacklistDataService {
+    
+    private static final Logger log = LoggerFactory.getLogger(BlacklistDataServiceImpl.class);
+    
+    @Autowired
+    private UserBaseMapper userBaseMapper;
+    
+    @Autowired
+    private QywxCorpBlacklistUserMapper corpBlacklistUserMapper;
+    
+    @Override
+    public List<Long> getAllCorpIds() {
+        try {
+            UserBaseExample example = new UserBaseExample();
+            // 只查询有效的企业ID(不为空且大于0)
+            example.createCriteria().andCorpIdIsNotNull();
+            
+            List<UserBase> users = userBaseMapper.selectByExample(example);
+            
+            // 去重企业ID
+            Set<Long> corpIdSet = new HashSet<>();
+            for (UserBase user : users) {
+                if (user.getCorpId() != null && user.getCorpId() > 0) {
+                    corpIdSet.add(user.getCorpId());
+                }
+            }
+            
+            List<Long> corpIds = new ArrayList<>(corpIdSet);
+            log.info("获取到{}个去重后的企业ID", corpIds.size());
+            
+            return corpIds;
+            
+        } catch (Exception e) {
+            log.error("获取所有企业ID异常", e);
+            return new ArrayList<>();
+        }
+    }
+    
+    @Override
+    public String getLoginUserUuid(Long corpId) {
+        if (corpId == null) {
+            log.warn("获取登录用户UUID时企业ID为空");
+            return null;
+        }
+        
+        try {
+            UserBaseExample example = new UserBaseExample();
+            example.createCriteria()
+                .andCorpIdEqualTo(corpId)
+                .andLoginStatusEqualTo(Constant.LOGIN_STATUS_LOGIN);
+            
+            List<UserBase> users = userBaseMapper.selectByExample(example);
+            
+            if (users.isEmpty()) {
+                log.warn("企业{}未找到登录用户", corpId);
+                return null;
+            }
+            
+            UserBase user = users.get(0);
+            String uuid = user.getUuid();
+            
+            log.debug("企业{}找到登录用户UUID: {}", corpId, uuid);
+            return uuid;
+            
+        } catch (Exception e) {
+            log.error("企业{}获取登录用户UUID异常", corpId, e);
+            return null;
+        }
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int clearCorpBlacklist(Long corpId) {
+        if (corpId == null) {
+            log.warn("清理企业黑名单时企业ID为空");
+            return 0;
+        }
+        
+        try {
+            QywxCorpBlacklistUserExample example = new QywxCorpBlacklistUserExample();
+            example.createCriteria().andCorpIdEqualTo(corpId);
+            
+            int deletedCount = corpBlacklistUserMapper.deleteByExample(example);
+            
+            log.info("企业{}清理黑名单完成,删除{}条记录", corpId, deletedCount);
+            return deletedCount;
+            
+        } catch (Exception e) {
+            log.error("企业{}清理黑名单异常", corpId, e);
+            throw new RuntimeException("清理企业黑名单失败", e);
+        }
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int batchInsertBlacklist(List<QywxCorpBlacklistUser> users) {
+        if (users == null || users.isEmpty()) {
+            log.debug("批量插入黑名单:数据为空");
+            return 0;
+        }
+        
+        try {
+            int insertedCount = 0;
+            
+            for (QywxCorpBlacklistUser user : users) {
+                if (user != null && user.getVid() != null && user.getCorpId() != null) {
+                    int result = corpBlacklistUserMapper.insertSelective(user);
+                    if (result > 0) {
+                        insertedCount++;
+                    }
+                } else {
+                    log.warn("跳过无效的黑名单用户记录: {}", user);
+                }
+            }
+            
+            log.info("批量插入黑名单完成,成功插入{}条记录", insertedCount);
+            return insertedCount;
+            
+        } catch (Exception e) {
+            log.error("批量插入黑名单异常", e);
+            throw new RuntimeException("批量插入黑名单失败", e);
+        }
+    }
+    
+    @Override
+    public int getCorpBlacklistCount(Long corpId) {
+        if (corpId == null) {
+            log.warn("获取企业黑名单数量时企业ID为空");
+            return 0;
+        }
+        
+        try {
+            QywxCorpBlacklistUserExample example = new QywxCorpBlacklistUserExample();
+            example.createCriteria().andCorpIdEqualTo(corpId);
+            
+            long count = corpBlacklistUserMapper.countByExample(example);
+            
+            log.debug("企业{}黑名单用户数量: {}", corpId, count);
+            return (int) count;
+            
+        } catch (Exception e) {
+            log.error("企业{}获取黑名单数量异常", corpId, e);
+            return 0;
+        }
+    }
+
+
+}

+ 249 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/impl/BlacklistSyncServiceImpl.java

@@ -0,0 +1,249 @@
+package com.tzld.piaoquan.risk.control.service.sync.impl;
+
+import com.tzld.piaoquan.risk.control.model.po.QywxCorpBlacklistUser;
+import com.tzld.piaoquan.risk.control.model.qywx.RoomBlacklistResponse;
+import com.tzld.piaoquan.risk.control.service.impl.RiskUserOperateService;
+import com.tzld.piaoquan.risk.control.service.sync.BlacklistSyncService;
+import com.tzld.piaoquan.risk.control.service.sync.data.BlacklistDataService;
+import com.tzld.piaoquan.risk.control.service.sync.lock.RedisDistributedLockService;
+import com.tzld.piaoquan.risk.control.service.sync.strategy.SyncDecision;
+import com.tzld.piaoquan.risk.control.service.sync.strategy.SyncTimeStrategy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 黑名单同步服务实现类
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+@Service
+public class BlacklistSyncServiceImpl implements BlacklistSyncService {
+    
+    private static final Logger log = LoggerFactory.getLogger(BlacklistSyncServiceImpl.class);
+    
+    @Autowired
+    private SyncTimeStrategy syncTimeStrategy;
+    
+    @Autowired
+    private BlacklistDataService blacklistDataService;
+    
+    @Autowired
+    private RiskUserOperateService riskUserOperateService;
+    
+    @Autowired
+    private RedisDistributedLockService lockService;
+    
+    @Override
+    public void intelligentSyncCheck() {
+        // 1. 获取所有企业ID
+        List<Long> corpIds = blacklistDataService.getAllCorpIds();
+        log.info("开始智能同步检查,共{}个企业", corpIds.size());
+        
+        int syncCount = 0;
+        int skipCount = 0;
+        int lockFailedCount = 0;
+        long startTime = System.currentTimeMillis();
+        
+        // 2. 遍历企业ID,进行智能决策
+        for (Long corpId : corpIds) {
+            try {
+                // 3. 判断是否需要同步
+                SyncDecision decision = syncTimeStrategy.shouldSync(corpId);
+                
+                log.info("企业{}同步决策: {}, 原因: {}", corpId, 
+                        decision.isShouldSync() ? "执行同步" : "跳过同步", 
+                        decision.getReason());
+                
+                if (decision.isShouldSync()) {
+                    // 4. 尝试获取企业级分布式锁
+                    String lockKey = "corp_sync_" + corpId;
+                    
+                    if (lockService.tryLock(lockKey, 300)) { // 锁定5分钟
+                        try {
+                            log.info("企业{}获取分布式锁成功,开始执行同步", corpId);
+                            
+                            // 5. 执行同步
+                            boolean success = executeSyncForCorp(corpId);
+                            if (success) {
+                                // 6. 更新同步时间
+                                syncTimeStrategy.updateLastSyncTime(corpId);
+                                syncCount++;
+                                log.info("企业{}同步成功", corpId);
+                            } else {
+                                log.error("企业{}同步失败", corpId);
+                            }
+                            
+                        } finally {
+                            // 7. 释放企业级锁
+                            lockService.releaseLock(lockKey);
+                            log.debug("企业{}释放分布式锁", corpId);
+                        }
+                    } else {
+                        lockFailedCount++;
+                        log.info("企业{}获取分布式锁失败,其他实例正在同步该企业", corpId);
+                    }
+                } else {
+                    skipCount++;
+                }
+                
+            } catch (Exception e) {
+                log.error("企业{}同步检查异常", corpId, e);
+            }
+        }
+        
+        long endTime = System.currentTimeMillis();
+        long duration = endTime - startTime;
+        
+        log.info("智能同步检查完成,耗时{}ms,执行同步: {}个企业,跳过同步: {}个企业,锁冲突: {}个企业", 
+                duration, syncCount, skipCount, lockFailedCount);
+    }
+    
+    @Override
+    public boolean forceSyncCorp(Long corpId) {
+        if (corpId == null) {
+            log.warn("强制同步企业时企业ID为空");
+            return false;
+        }
+        
+        log.info("开始强制同步企业: {}", corpId);
+        
+        // 尝试获取企业级分布式锁
+        String lockKey = "corp_sync_" + corpId;
+        
+        if (lockService.tryLock(lockKey, 300)) { // 锁定5分钟
+            try {
+                log.info("企业{}获取分布式锁成功,开始强制同步", corpId);
+                
+                boolean success = executeSyncForCorp(corpId);
+                if (success) {
+                    // 更新同步时间
+                    syncTimeStrategy.updateLastSyncTime(corpId);
+                    log.info("企业{}强制同步成功", corpId);
+                    return true;
+                } else {
+                    log.error("企业{}强制同步失败", corpId);
+                    return false;
+                }
+                
+            } finally {
+                lockService.releaseLock(lockKey);
+                log.debug("企业{}释放分布式锁", corpId);
+            }
+        } else {
+            log.warn("企业{}获取分布式锁失败,其他实例正在同步该企业", corpId);
+            return false;
+        }
+    }
+    
+    /**
+     * 执行企业同步
+     * 
+     * @param corpId 企业ID
+     * @return true表示同步成功,false表示同步失败
+     */
+    private boolean executeSyncForCorp(Long corpId) {
+        try {
+            // 获取登录用户UUID
+            String uuid = blacklistDataService.getLoginUserUuid(corpId);
+            if (uuid == null) {
+                log.warn("企业{}未找到登录用户", corpId);
+                return false;
+            }
+            
+            log.info("企业{}开始同步,使用UUID: {}", corpId, uuid);
+            
+            // 调用API获取黑名单数据
+            RoomBlacklistResponse response = riskUserOperateService.getRoomBlacklist(uuid);
+            
+            if (response != null && response.getErrcode() == 0) {
+                // 清理旧数据
+                int deletedCount = blacklistDataService.clearCorpBlacklist(corpId);
+                log.debug("企业{}清理旧黑名单数据完成,删除{}条记录", corpId, deletedCount);
+                
+                // 插入新数据
+                if (response.getData() != null && response.getData().getList() != null) {
+                    List<QywxCorpBlacklistUser> users = convertToCorpBlacklistUsers(
+                            response.getData().getList(), corpId);
+                    
+                    if (!users.isEmpty()) {
+                        int insertedCount = blacklistDataService.batchInsertBlacklist(users);
+                        log.info("企业{}同步完成,更新{}条黑名单记录", corpId, insertedCount);
+                    } else {
+                        log.info("企业{}同步完成,黑名单为空", corpId);
+                    }
+                } else {
+                    log.info("企业{}同步完成,API返回数据为空", corpId);
+                }
+                
+                return true;
+            } else {
+                log.error("企业{}API调用失败,错误码: {}, 错误信息: {}", 
+                        corpId, response != null ? response.getErrcode() : -1,
+                        response != null ? response.getErrmsg() : "未知错误");
+                return false;
+            }
+            
+        } catch (Exception e) {
+            log.error("企业{}同步执行异常", corpId, e);
+            return false;
+        }
+    }
+    
+    /**
+     * 转换API响应数据为数据库实体
+     * 
+     * @param items API响应的黑名单项列表
+     * @param corpId 企业ID
+     * @return 数据库实体列表
+     */
+    private List<QywxCorpBlacklistUser> convertToCorpBlacklistUsers(
+            List<RoomBlacklistResponse.BlacklistItem> items, Long corpId) {
+        
+        List<QywxCorpBlacklistUser> users = new ArrayList<>();
+        Date now = new Date();
+        
+        for (RoomBlacklistResponse.BlacklistItem item : items) {
+            if (item.getBlacklist_vid() != null) {
+                QywxCorpBlacklistUser user = new QywxCorpBlacklistUser();
+                user.setVid(item.getBlacklist_vid());
+                user.setCorpId(corpId);
+                // 判断时间戳长度,如果不是13位才补齐
+                long timestamp = item.getCreate_time();
+                if (String.valueOf(timestamp).length() == 10) {
+                    timestamp = timestamp * 1000; // 10位时间戳转13位
+                }
+                user.setBlackTime(new Date(timestamp));
+                user.setCreateTime(now);
+                users.add(user);
+            }
+        }
+        
+        return users;
+    }
+    
+    @Override
+    public String getSyncStatistics() {
+        try {
+            List<Long> corpIds = blacklistDataService.getAllCorpIds();
+            int totalCorps = corpIds.size();
+            int totalBlacklistUsers = 0;
+            
+            for (Long corpId : corpIds) {
+                totalBlacklistUsers += blacklistDataService.getCorpBlacklistCount(corpId);
+            }
+            
+            return String.format("同步统计: 企业总数=%d, 黑名单用户总数=%d", totalCorps, totalBlacklistUsers);
+            
+        } catch (Exception e) {
+            log.error("获取同步统计信息异常", e);
+            return "同步统计信息获取失败: " + e.getMessage();
+        }
+    }
+}

+ 74 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/job/BlacklistSyncJobHandler.java

@@ -0,0 +1,74 @@
+package com.tzld.piaoquan.risk.control.service.sync.job;
+
+import com.tzld.piaoquan.risk.control.service.sync.BlacklistSyncService;
+import com.tzld.piaoquan.risk.control.service.sync.config.ApolloConfigService;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 黑名单同步XXL-JOB任务处理器
+ * 
+ * 每234秒执行一次智能同步检查
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+@Component
+public class BlacklistSyncJobHandler {
+    
+    private static final Logger log = LoggerFactory.getLogger(BlacklistSyncJobHandler.class);
+    
+    @Autowired
+    private BlacklistSyncService blacklistSyncService;
+    
+    @Autowired
+    private ApolloConfigService apolloConfigService;
+    
+    /**
+     * 黑名单同步任务
+     * 
+     * XXL-JOB任务执行方法,每234秒执行一次
+     */
+    @XxlJob("blacklistSyncJob")
+    public void execute() throws Exception {
+        log.info("开始执行黑名单同步任务检查");
+        
+        try {
+            // 记录任务开始时间
+            long startTime = System.currentTimeMillis();
+            
+            // 记录配置信息
+            String configInfo = apolloConfigService.getConfigInfo();
+            log.info("当前配置信息: {}", configInfo);
+            log.info("XXL-JOB任务开始执行,配置信息: {}", configInfo);
+            
+            // 执行智能同步检查
+            blacklistSyncService.intelligentSyncCheck();
+            
+            // 记录任务结束时间和耗时
+            long endTime = System.currentTimeMillis();
+            long duration = endTime - startTime;
+            
+            // 获取同步统计信息
+            String statistics = blacklistSyncService.getSyncStatistics();
+            
+            log.info("黑名单同步任务检查完成,耗时: {}ms", duration);
+            log.info("同步统计信息: {}", statistics);
+            
+            log.info("XXL-JOB任务执行完成,耗时: {}ms,统计信息: {}", duration, statistics);
+            
+        } catch (Exception e) {
+            String errorMsg = "黑名单同步任务执行失败: " + e.getMessage();
+            log.info(errorMsg);
+            log.error("XXL-JOB任务执行异常", e);
+            
+            // 重新抛出异常,让XXL-JOB记录任务失败
+            throw e;
+        }
+    }
+    
+
+}

+ 150 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/lock/RedisDistributedLockService.java

@@ -0,0 +1,150 @@
+package com.tzld.piaoquan.risk.control.service.sync.lock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Redis分布式锁服务
+ * 
+ * 使用Redis SETNX实现企业级分布式锁
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+@Component
+public class RedisDistributedLockService {
+    
+    private static final Logger log = LoggerFactory.getLogger(RedisDistributedLockService.class);
+
+    @Resource(name = "redisTemplate")
+    private RedisTemplate<String, String> redisTemplate;
+    
+    private static final String LOCK_PREFIX = "blacklist_sync_lock:";
+    private static final int DEFAULT_EXPIRE_TIME = 300; // 5分钟
+    
+    /**
+     * 锁值存储,用于释放锁时验证
+     */
+    private final ThreadLocal<String> lockValueHolder = new ThreadLocal<>();
+    
+    /**
+     * 尝试获取分布式锁
+     * 
+     * @param lockKey 锁的key
+     * @param expireTime 过期时间(秒)
+     * @return true表示获取成功,false表示获取失败
+     */
+    public boolean tryLock(String lockKey, int expireTime) {
+        String key = LOCK_PREFIX + lockKey;
+        String value = UUID.randomUUID().toString();
+        
+        try {
+            // 使用SET命令的NX和EX参数实现原子操作
+            Boolean result = redisTemplate.opsForValue()
+                .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
+                
+            if (Boolean.TRUE.equals(result)) {
+                // 将锁值存储到ThreadLocal,用于释放锁时验证
+                lockValueHolder.set(value);
+                log.info("成功获取分布式锁: {}, 过期时间: {}秒", key, expireTime);
+                return true;
+            } else {
+                log.debug("获取分布式锁失败: {}", key);
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("获取分布式锁异常: {}", key, e);
+            return false;
+        }
+    }
+    
+    /**
+     * 尝试获取分布式锁(使用默认过期时间)
+     * 
+     * @param lockKey 锁的key
+     * @return true表示获取成功,false表示获取失败
+     */
+    public boolean tryLock(String lockKey) {
+        return tryLock(lockKey, DEFAULT_EXPIRE_TIME);
+    }
+    
+    /**
+     * 释放分布式锁
+     * 
+     * @param lockKey 锁的key
+     */
+    public void releaseLock(String lockKey) {
+        String key = LOCK_PREFIX + lockKey;
+        String value = lockValueHolder.get();
+        
+        if (value != null) {
+            try {
+                // 使用Lua脚本确保原子性:只有锁的持有者才能释放锁
+                String luaScript = 
+                    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
+                    "return redis.call('del', KEYS[1]) " +
+                    "else return 0 end";
+                    
+                Long result = redisTemplate.execute(
+                    new DefaultRedisScript<>(luaScript, Long.class),
+                    Collections.singletonList(key),
+                    value
+                );
+                
+                if (result != null && result == 1) {
+                    log.info("成功释放分布式锁: {}", key);
+                } else {
+                    log.warn("释放分布式锁失败,可能已过期: {}", key);
+                }
+                
+            } catch (Exception e) {
+                log.error("释放分布式锁异常: {}", key, e);
+            } finally {
+                lockValueHolder.remove();
+            }
+        } else {
+            log.warn("尝试释放未持有的锁: {}", key);
+        }
+    }
+    
+    /**
+     * 检查锁是否存在
+     * 
+     * @param lockKey 锁的key
+     * @return true表示锁存在,false表示锁不存在
+     */
+    public boolean isLocked(String lockKey) {
+        String key = LOCK_PREFIX + lockKey;
+        try {
+            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
+        } catch (Exception e) {
+            log.error("检查锁状态异常: {}", key, e);
+            return false;
+        }
+    }
+    
+    /**
+     * 获取锁的剩余过期时间
+     * 
+     * @param lockKey 锁的key
+     * @return 剩余过期时间(秒),-1表示永不过期,-2表示key不存在
+     */
+    public long getLockTtl(String lockKey) {
+        String key = LOCK_PREFIX + lockKey;
+        try {
+            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            log.error("获取锁过期时间异常: {}", key, e);
+            return -2;
+        }
+    }
+}

+ 154 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/strategy/SyncDecision.java

@@ -0,0 +1,154 @@
+package com.tzld.piaoquan.risk.control.service.sync.strategy;
+
+/**
+ * 同步决策实体类
+ * 
+ * 封装同步决策的结果和原因
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+public class SyncDecision {
+    
+    /**
+     * 是否应该同步
+     */
+    private boolean shouldSync;
+    
+    /**
+     * 决策原因
+     */
+    private String reason;
+    
+    /**
+     * 时间差(毫秒)
+     */
+    private long timeDiff;
+    
+    /**
+     * 私有构造函数
+     * 
+     * @param shouldSync 是否应该同步
+     * @param reason 决策原因
+     * @param timeDiff 时间差(毫秒)
+     */
+    private SyncDecision(boolean shouldSync, String reason, long timeDiff) {
+        this.shouldSync = shouldSync;
+        this.reason = reason;
+        this.timeDiff = timeDiff;
+    }
+    
+    /**
+     * 创建同步决策(执行同步)
+     * 
+     * @param reason 决策原因
+     * @param timeDiff 时间差(毫秒)
+     * @return 同步决策对象
+     */
+    public static SyncDecision sync(String reason, long timeDiff) {
+        return new SyncDecision(true, reason, timeDiff);
+    }
+    
+    /**
+     * 创建跳过决策(跳过同步)
+     * 
+     * @param reason 决策原因
+     * @param timeDiff 时间差(毫秒)
+     * @return 同步决策对象
+     */
+    public static SyncDecision skip(String reason, long timeDiff) {
+        return new SyncDecision(false, reason, timeDiff);
+    }
+    
+    /**
+     * 创建首次同步决策
+     * 
+     * @param reason 决策原因
+     * @return 同步决策对象
+     */
+    public static SyncDecision firstSync(String reason) {
+        return new SyncDecision(true, reason, 0);
+    }
+    
+    public boolean isShouldSync() {
+        return shouldSync;
+    }
+    
+    public String getReason() {
+        return reason;
+    }
+    
+    public long getTimeDiff() {
+        return timeDiff;
+    }
+    
+    /**
+     * 获取时间差(小时)
+     * 
+     * @return 时间差小时数
+     */
+    public double getTimeDiffHours() {
+        return timeDiff / (3600.0 * 1000);
+    }
+    
+    /**
+     * 获取时间差(分钟)
+     * 
+     * @return 时间差分钟数
+     */
+    public double getTimeDiffMinutes() {
+        return timeDiff / (60.0 * 1000);
+    }
+    
+    /**
+     * 获取决策类型描述
+     * 
+     * @return 决策类型
+     */
+    public String getDecisionType() {
+        return shouldSync ? "执行同步" : "跳过同步";
+    }
+    
+    /**
+     * 获取详细的决策信息
+     * 
+     * @return 详细决策信息
+     */
+    public String getDetailedInfo() {
+        if (timeDiff == 0) {
+            return String.format("决策: %s, 原因: %s", getDecisionType(), reason);
+        } else {
+            return String.format("决策: %s, 原因: %s, 时间差: %.1f小时", 
+                    getDecisionType(), reason, getTimeDiffHours());
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return String.format("SyncDecision{shouldSync=%s, reason='%s', timeDiff=%dms}", 
+                shouldSync, reason, timeDiff);
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        
+        SyncDecision that = (SyncDecision) obj;
+        return shouldSync == that.shouldSync
+            && timeDiff == that.timeDiff
+            && (reason != null ? reason.equals(that.reason) : that.reason == null);
+    }
+    
+    @Override
+    public int hashCode() {
+        int result = (shouldSync ? 1 : 0);
+        result = 31 * result + (reason != null ? reason.hashCode() : 0);
+        result = 31 * result + (int) (timeDiff ^ (timeDiff >>> 32));
+        return result;
+    }
+}

+ 189 - 0
risk-control-core/src/main/java/com/tzld/piaoquan/risk/control/service/sync/strategy/SyncTimeStrategy.java

@@ -0,0 +1,189 @@
+package com.tzld.piaoquan.risk.control.service.sync.strategy;
+
+import com.tzld.piaoquan.risk.control.service.sync.cache.RedisSyncTimeCache;
+import com.tzld.piaoquan.risk.control.service.sync.config.ApolloConfigService;
+import com.tzld.piaoquan.risk.control.service.sync.config.SyncConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 同步时间策略
+ * 
+ * 基于时间间隔和随机概率的智能同步决策
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+@Component
+public class SyncTimeStrategy {
+    
+    private static final Logger log = LoggerFactory.getLogger(SyncTimeStrategy.class);
+    
+    @Autowired
+    private RedisSyncTimeCache syncTimeCache;
+    
+    @Autowired
+    private ApolloConfigService apolloConfigService;
+    
+    /**
+     * 判断企业是否需要同步
+     * 
+     * @param corpId 企业ID
+     * @return 同步决策结果
+     */
+    public SyncDecision shouldSync(Long corpId) {
+        if (corpId == null) {
+            log.warn("企业ID为空,跳过同步决策");
+            return SyncDecision.skip("企业ID为空", 0);
+        }
+        
+        try {
+            // 获取配置参数
+            SyncConfig config = apolloConfigService.getSyncConfig();
+            log.debug("企业{}使用同步配置: {}", corpId, config);
+            
+            // 获取上次同步时间
+            Long lastSyncTime = syncTimeCache.getLastSyncTime(corpId);
+            long currentTime = System.currentTimeMillis();
+            
+            if (lastSyncTime == null) {
+                // 首次同步,直接执行
+                log.info("企业{}首次同步", corpId);
+                return SyncDecision.firstSync("首次同步");
+            }
+            
+            long timeDiff = currentTime - lastSyncTime;
+            long minInterval = config.getMinIntervalMillis();
+            long maxInterval = config.getMaxIntervalMillis();
+            
+            // 转换为小时便于日志显示
+            double timeDiffHours = timeDiff / (3600.0 * 1000);
+            
+            log.debug("企业{}时间差分析: 当前间隔={}小时, 最小间隔={}小时, 最大间隔={}小时", 
+                    corpId, timeDiffHours, config.getMinIntervalHours(), config.getMaxIntervalHours());
+            
+            if (timeDiff < minInterval) {
+                // 小于最小间隔,不同步
+                String reason = String.format("未达到最小间隔时间(%.1f小时 < %d小时)", 
+                        timeDiffHours, config.getMinIntervalHours());
+                return SyncDecision.skip(reason, timeDiff);
+                
+            } else if (timeDiff > maxInterval) {
+                // 大于最大间隔,必须同步
+                String reason = String.format("超过最大间隔时间(%.1f小时 > %d小时)", 
+                        timeDiffHours, config.getMaxIntervalHours());
+                return SyncDecision.sync(reason, timeDiff);
+                
+            } else {
+                // 在区间内,随机决策
+                double random = Math.random();
+                
+                log.debug("企业{}随机决策: 随机数={:.3f}, 概率阈值={:.3f}", 
+                        corpId, random, config.getSyncProbability());
+                
+                if (random < config.getSyncProbability()) {
+                    String reason = String.format("随机概率命中(%.3f < %.3f, 间隔%.1f小时)", 
+                            random, config.getSyncProbability(), timeDiffHours);
+                    return SyncDecision.sync(reason, timeDiff);
+                } else {
+                    String reason = String.format("随机概率未命中(%.3f >= %.3f, 间隔%.1f小时)", 
+                            random, config.getSyncProbability(), timeDiffHours);
+                    return SyncDecision.skip(reason, timeDiff);
+                }
+            }
+            
+        } catch (Exception e) {
+            log.error("企业{}同步决策异常", corpId, e);
+            return SyncDecision.skip("决策异常: " + e.getMessage(), 0);
+        }
+    }
+    
+    /**
+     * 更新企业最后同步时间
+     * 
+     * @param corpId 企业ID
+     */
+    public void updateLastSyncTime(Long corpId) {
+        if (corpId == null) {
+            log.warn("更新同步时间时企业ID为空");
+            return;
+        }
+        
+        try {
+            syncTimeCache.updateLastSyncTime(corpId);
+            log.debug("企业{}同步时间已更新", corpId);
+        } catch (Exception e) {
+            log.error("企业{}更新同步时间异常", corpId, e);
+        }
+    }
+    
+    /**
+     * 获取企业上次同步时间
+     * 
+     * @param corpId 企业ID
+     * @return 上次同步时间戳,如果不存在返回null
+     */
+    public Long getLastSyncTime(Long corpId) {
+        if (corpId == null) {
+            log.warn("获取同步时间时企业ID为空");
+            return null;
+        }
+        
+        try {
+            return syncTimeCache.getLastSyncTime(corpId);
+        } catch (Exception e) {
+            log.error("企业{}获取同步时间异常", corpId, e);
+            return null;
+        }
+    }
+    
+    /**
+     * 删除企业同步时间记录
+     * 
+     * @param corpId 企业ID
+     */
+    public void removeLastSyncTime(Long corpId) {
+        if (corpId == null) {
+            log.warn("删除同步时间时企业ID为空");
+            return;
+        }
+        
+        try {
+            syncTimeCache.removeLastSyncTime(corpId);
+            log.info("企业{}同步时间记录已删除", corpId);
+        } catch (Exception e) {
+            log.error("企业{}删除同步时间异常", corpId, e);
+        }
+    }
+    
+    /**
+     * 获取企业同步状态信息
+     * 
+     * @param corpId 企业ID
+     * @return 同步状态信息
+     */
+    public String getSyncStatusInfo(Long corpId) {
+        if (corpId == null) {
+            return "企业ID为空";
+        }
+        
+        try {
+            Long lastSyncTime = getLastSyncTime(corpId);
+            if (lastSyncTime == null) {
+                return "未同步过";
+            }
+            
+            long timeDiff = System.currentTimeMillis() - lastSyncTime;
+            double timeDiffHours = timeDiff / (3600.0 * 1000);
+            
+            return String.format("上次同步: %.1f小时前",
+                    timeDiffHours);
+                    
+        } catch (Exception e) {
+            log.error("企业{}获取同步状态信息异常", corpId, e);
+            return "状态信息获取失败: " + e.getMessage();
+        }
+    }
+}

+ 203 - 0
risk-control-server/src/main/java/com/tzld/piaoquan/risk/control/controller/BlacklistSyncController.java

@@ -0,0 +1,203 @@
+package com.tzld.piaoquan.risk.control.controller;
+
+import com.tzld.piaoquan.risk.control.common.base.CommonResponse;
+import com.tzld.piaoquan.risk.control.service.sync.BlacklistSyncService;
+import com.tzld.piaoquan.risk.control.service.sync.config.ApolloConfigService;
+import com.tzld.piaoquan.risk.control.service.sync.strategy.SyncTimeStrategy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 黑名单同步管理控制器
+ * 
+ * 提供同步任务的手动触发、监控和调试功能
+ * 
+ * @author 风控系统开发团队
+ * @since 1.0.0
+ */
+@RestController
+@RequestMapping("/sync")
+public class BlacklistSyncController {
+    
+    private static final Logger log = LoggerFactory.getLogger(BlacklistSyncController.class);
+    
+    @Autowired
+    private BlacklistSyncService blacklistSyncService;
+    
+    @Autowired
+    private ApolloConfigService apolloConfigService;
+    
+    @Autowired
+    private SyncTimeStrategy syncTimeStrategy;
+    
+    /**
+     * 手动触发智能同步检查
+     * 
+     * @return 执行结果
+     */
+    @PostMapping("/trigger")
+    public CommonResponse<String> triggerSync() {
+        try {
+            log.info("手动触发智能同步检查");
+            
+            long startTime = System.currentTimeMillis();
+            blacklistSyncService.intelligentSyncCheck();
+            long duration = System.currentTimeMillis() - startTime;
+            
+            String result = String.format("智能同步检查完成,耗时: %dms", duration);
+            log.info("手动触发同步完成: {}", result);
+            
+            return CommonResponse.success(result);
+            
+        } catch (Exception e) {
+            log.error("手动触发同步失败", e);
+            return CommonResponse.create(-1, "同步失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 强制同步指定企业
+     * 
+     * @param corpId 企业ID
+     * @return 执行结果
+     */
+    @PostMapping("/force/{corpId}")
+    public CommonResponse<String> forceSync(@PathVariable Long corpId) {
+        try {
+            log.info("手动强制同步企业: {}", corpId);
+            
+            if (corpId == null || corpId <= 0) {
+                return CommonResponse.create(-1, "企业ID无效");
+            }
+            
+            long startTime = System.currentTimeMillis();
+            boolean success = blacklistSyncService.forceSyncCorp(corpId);
+            long duration = System.currentTimeMillis() - startTime;
+            
+            if (success) {
+                String result = String.format("企业%d强制同步成功,耗时: %dms", corpId, duration);
+                log.info("手动强制同步成功: {}", result);
+                return CommonResponse.success(result);
+            } else {
+                String result = String.format("企业%d强制同步失败", corpId);
+                log.warn("手动强制同步失败: {}", result);
+                return CommonResponse.create(-1, result);
+            }
+            
+        } catch (Exception e) {
+            log.error("手动强制同步企业{}失败", corpId, e);
+            return CommonResponse.create(-1, "强制同步失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 获取同步统计信息
+     * 
+     * @return 统计信息
+     */
+    @GetMapping("/statistics")
+    public CommonResponse<String> getStatistics() {
+        try {
+            String statistics = blacklistSyncService.getSyncStatistics();
+            log.debug("获取同步统计信息: {}", statistics);
+            return CommonResponse.success(statistics);
+            
+        } catch (Exception e) {
+            log.error("获取同步统计信息失败", e);
+            return CommonResponse.create(-1, "获取统计信息失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 获取配置信息
+     * 
+     * @return 配置信息
+     */
+    @GetMapping("/config")
+    public CommonResponse<String> getConfig() {
+        try {
+            String configInfo = apolloConfigService.getConfigInfo();
+            log.debug("获取配置信息: {}", configInfo);
+            return CommonResponse.success(configInfo);
+            
+        } catch (Exception e) {
+            log.error("获取配置信息失败", e);
+            return CommonResponse.create(-1, "获取配置信息失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 获取企业同步状态
+     * 
+     * @param corpId 企业ID
+     * @return 同步状态信息
+     */
+    @GetMapping("/status/{corpId}")
+    public CommonResponse<String> getCorpSyncStatus(@PathVariable Long corpId) {
+        try {
+            if (corpId == null || corpId <= 0) {
+                return CommonResponse.create(-1, "企业ID无效");
+            }
+            
+            String statusInfo = syncTimeStrategy.getSyncStatusInfo(corpId);
+            log.debug("企业{}同步状态: {}", corpId, statusInfo);
+            return CommonResponse.success(statusInfo);
+            
+        } catch (Exception e) {
+            log.error("获取企业{}同步状态失败", corpId, e);
+            return CommonResponse.create(-1, "获取同步状态失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 删除企业同步时间记录
+     * 
+     * @param corpId 企业ID
+     * @return 执行结果
+     */
+    @DeleteMapping("/time/{corpId}")
+    public CommonResponse<String> removeCorpSyncTime(@PathVariable Long corpId) {
+        try {
+            if (corpId == null || corpId <= 0) {
+                return CommonResponse.create(-1, "企业ID无效");
+            }
+            
+            log.info("删除企业{}同步时间记录", corpId);
+            syncTimeStrategy.removeLastSyncTime(corpId);
+            
+            String result = String.format("企业%d同步时间记录已删除", corpId);
+            return CommonResponse.success(result);
+            
+        } catch (Exception e) {
+            log.error("删除企业{}同步时间记录失败", corpId, e);
+            return CommonResponse.create(-1, "删除同步时间记录失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 健康检查
+     * 
+     * @return 系统状态
+     */
+    @GetMapping("/health")
+    public CommonResponse<String> healthCheck() {
+        try {
+            // 检查配置是否正常
+            boolean configValid = apolloConfigService.validateConfig();
+            
+            // 获取基本统计信息
+            String statistics = blacklistSyncService.getSyncStatistics();
+            
+            String healthInfo = String.format("系统状态: 正常, 配置状态: %s, %s", 
+                    configValid ? "有效" : "无效", statistics);
+            
+            return CommonResponse.success(healthInfo);
+            
+        } catch (Exception e) {
+            log.error("健康检查失败", e);
+            return CommonResponse.create(-1, "系统异常: " + e.getMessage());
+        }
+    }
+}