diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/job/ManualJobController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/job/ManualJobController.java new file mode 100644 index 0000000..0f9e49d --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/job/ManualJobController.java @@ -0,0 +1,47 @@ +package com.agileboot.admin.controller.job; + +import com.agileboot.admin.customize.service.job.SyncAb98UserJob; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 手动执行任务控制器 + * 用于手动触发定时任务,方便调试和即时执行 + * 该接口无需权限验证,路径为 /manual/** 已在SecurityConfig中配置为permitAll + */ +@Tag(name = "手动执行任务API", description = "手动触发定时任务执行") +@RestController +@RequestMapping("/manual/job") +@RequiredArgsConstructor +@Slf4j +public class ManualJobController { + + private final SyncAb98UserJob syncAb98UserJob; + + /** + * 手动同步AB98用户信息 + * 对应定时任务:@Scheduled(cron = "0 0 2 * * ?") + * 从汇邦云平台拉取最新用户信息进行对比更新 + */ + @Operation(summary = "手动同步AB98用户信息", description = "从汇邦云平台同步AB98用户信息到本地数据库") + @PostMapping("/sync-ab98-user") + public ResponseDTO syncAb98UserInfo() { + try { + log.info("开始手动执行AB98用户信息同步任务..."); + syncAb98UserJob.syncAb98UserInfo(); + log.info("手动执行AB98用户信息同步任务完成"); + return ResponseDTO.ok("AB98用户信息同步任务执行成功"); + } catch (Exception e) { + log.error("手动执行AB98用户信息同步任务失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "AB98用户信息同步失败:" + e.getMessage())); + } + } +} \ No newline at end of file diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java index 1255b01..c724546 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java @@ -137,7 +137,7 @@ public class SecurityConfig { .antMatchers("/login", "/register", "/captchaImage", "/api/**", "/file/**").anonymous() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() - .antMatchers("/qywx/**", "/test/**", "/monitor/**", "/getQyUserinfo", "/getConfig").permitAll() + .antMatchers("/manual/**", "/qywx/**", "/test/**", "/monitor/**", "/getQyUserinfo", "/getConfig").permitAll() // TODO this is danger. .antMatchers("/swagger-ui.html").anonymous() .antMatchers("/swagger-resources/**").anonymous() diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/SyncAb98UserJob.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/SyncAb98UserJob.java new file mode 100644 index 0000000..73928c9 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/SyncAb98UserJob.java @@ -0,0 +1,187 @@ +package com.agileboot.admin.customize.service.job; + +import com.agileboot.domain.ab98.api.Ab98ApiUtil; +import com.agileboot.domain.ab98.api.Ab98UserDto; +import com.agileboot.domain.ab98.user.Ab98UserApplicationService; +import com.agileboot.domain.ab98.user.command.UpdateAb98UserCommand; +import com.agileboot.domain.ab98.user.db.Ab98UserEntity; +import com.agileboot.domain.ab98.user.db.Ab98UserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 同步ab98用户信息定时任务 + * 定时从汇邦云平台拉取用户信息,与本地数据库中的用户信息进行对比, + * 如有差异则更新本地用户信息 + * + * @author your-name + * @since 2025-01-01 + */ +@RequiredArgsConstructor +@Component +@Slf4j +public class SyncAb98UserJob { + + private final Ab98UserService ab98UserService; + + private final Ab98UserApplicationService ab98UserApplicationService; + + private static final String APP_NAME = "wxshop"; + private static final String APP_SECRET = "34164e41f0c6694be6bbbba0dc50c14a"; + + /** + * 定时任务:同步ab98用户信息 + * 每天凌晨2点执行,获取所有本地ab98用户信息, + * 从汇邦云平台拉取最新用户信息进行对比更新 + */ + @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 + public void syncAb98UserInfo() { + log.info("开始执行ab98用户信息同步任务..."); + + try { + // 1. 获取所有本地ab98用户(只查询有身份证号的用户) + List localUserList = ab98UserService.selectAll(); + + if (localUserList == null || localUserList.isEmpty()) { + log.info("本地数据库中未找到ab98用户,跳过同步任务."); + return; + } + + log.info("本地数据库共有 {} 个ab98用户需要同步.", localUserList.size()); + + int successCount = 0; + int skipCount = 0; + int errorCount = 0; + + // 2. 遍历每个用户,查询汇邦云平台的最新信息 + for (Ab98UserEntity localUser : localUserList) { + try { + // 跳过没有身份证号的用户(可能是测试数据或异常数据) + if (StringUtils.isBlank(localUser.getIdnum())) { + log.debug("用户[{}]身份证号为空,跳过同步.", localUser.getAb98UserId()); + skipCount++; + continue; + } + + // 从汇邦云平台拉取用户信息 + Ab98UserDto remoteUserDto = Ab98ApiUtil.pullUserInfoByIdnum( + APP_NAME, + APP_SECRET, + localUser.getIdnum() + ); + + // 如果平台没有此用户信息,跳过 + if (remoteUserDto == null) { + log.debug("汇邦云平台未找到身份证号[{}]对应的用户,跳过同步.", localUser.getIdnum()); + skipCount++; + continue; + } + + // 对比并更新用户信息 + boolean isUpdated = compareAndUpdateUser(localUser, remoteUserDto); + if (isUpdated) { + successCount++; + log.info("用户[{}]信息已更新,身份证号: {}", localUser.getAb98UserId(), localUser.getIdnum()); + } else { + skipCount++; + log.debug("用户[{}]信息无变化,跳过更新.", localUser.getAb98UserId()); + } + + } catch (Exception e) { + errorCount++; + log.error("同步用户[{}]信息时发生错误,身份证号: {}", localUser.getAb98UserId(), localUser.getIdnum(), e); + } + } + + log.info("ab98用户信息同步任务执行完成. 总数: {}, 成功: {}, 跳过: {}, 错误: {}", + localUserList.size(), successCount, skipCount, errorCount); + + } catch (Exception globalException) { + // 记录整个定时任务的全局错误 + log.error("执行ab98用户信息同步任务时发生全局错误:", globalException); + } + } + + /** + * 对比并更新用户信息 + * + * @param localUser 本地用户实体 + * @param remoteUserDto 汇邦云平台用户信息 + * @return 是否进行了更新 + */ + private boolean compareAndUpdateUser(Ab98UserEntity localUser, Ab98UserDto remoteUserDto) { + boolean isUpdated = false; + + // 对比需要更新的字段 + UpdateAb98UserCommand updateCommand = new UpdateAb98UserCommand(); + updateCommand.setAb98UserId(localUser.getAb98UserId()); + + // 对比并更新姓名 + if (StringUtils.isNotBlank(remoteUserDto.getRealName()) + && !StringUtils.equals(remoteUserDto.getRealName(), localUser.getName())) { + updateCommand.setName(remoteUserDto.getRealName()); + isUpdated = true; + } + + // 对比并更新手机号 + if (StringUtils.isNotBlank(remoteUserDto.getPhone()) + && !StringUtils.equals(remoteUserDto.getPhone(), localUser.getTel())) { + updateCommand.setTel(remoteUserDto.getPhone()); + isUpdated = true; + } + + // 对比并更新性别 + if (StringUtils.isNotBlank(remoteUserDto.getSex()) + && !StringUtils.equals(remoteUserDto.getSex(), localUser.getSex())) { + updateCommand.setSex(remoteUserDto.getSex()); + isUpdated = true; + } + + // 对比并更新身份证正面 + if (StringUtils.isNotBlank(remoteUserDto.getIdCardFront()) + && !StringUtils.equals(remoteUserDto.getIdCardFront(), localUser.getIdcardFront())) { + updateCommand.setIdcardFront(remoteUserDto.getIdCardFront()); + isUpdated = true; + } + + // 对比并更新身份证背面 + if (StringUtils.isNotBlank(remoteUserDto.getIdCardBack()) + && !StringUtils.equals(remoteUserDto.getIdCardBack(), localUser.getIdcardBack())) { + updateCommand.setIdcardBack(remoteUserDto.getIdCardBack()); + isUpdated = true; + } + + // 对比并更新人脸照片 + if (StringUtils.isNotBlank(remoteUserDto.getFacePicture()) + && !StringUtils.equals(remoteUserDto.getFacePicture(), localUser.getFaceImg())) { + updateCommand.setFaceImg(remoteUserDto.getFacePicture()); + isUpdated = true; + } + + // 对比并更新地址 + if (StringUtils.isNotBlank(remoteUserDto.getIdCardAddress()) + && !StringUtils.equals(remoteUserDto.getIdCardAddress(), localUser.getAddress())) { + updateCommand.setAddress(remoteUserDto.getIdCardAddress()); + isUpdated = true; + } + + // 如果有字段需要更新,则执行更新 + if (isUpdated) { + try { + ab98UserApplicationService.updateUser(updateCommand); + log.debug("用户[{}]信息更新成功", localUser.getAb98UserId()); + } catch (Exception e) { + log.error("用户[{}]信息更新失败", localUser.getAb98UserId(), e); + throw e; // 抛出异常,触发事务回滚 + } + } + + return isUpdated; + } +} diff --git a/agileboot-api/src/test/java/com/agileboot/api/controller/CabinetControllerTest.java b/agileboot-api/src/test/java/com/agileboot/api/controller/CabinetControllerTest.java index db0f9e1..0dfd5fa 100644 --- a/agileboot-api/src/test/java/com/agileboot/api/controller/CabinetControllerTest.java +++ b/agileboot-api/src/test/java/com/agileboot/api/controller/CabinetControllerTest.java @@ -49,7 +49,7 @@ public class CabinetControllerTest { @Test public void testPullUserInfoByIdnum() { - String IdNum = "452526196206215829"; + String IdNum = ""; Ab98UserDto ab98UserDto = Ab98ApiUtil.pullUserInfoByIdnum("wxshop", "34164e41f0c6694be6bbbba0dc50c14a", IdNum); log.info("ab98UserDto:{}", JSONUtil.toJsonStr(ab98UserDto)); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/api/Ab98ApiUtil.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/api/Ab98ApiUtil.java index d580f87..22a0679 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/api/Ab98ApiUtil.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/api/Ab98ApiUtil.java @@ -208,11 +208,6 @@ public class Ab98ApiUtil { return null; } - public static void main(String[] args) { - Ab98UserDto userDto = pullUserInfoByIdnum("wxshop", "34164e41f0c6694be6bbbba0dc50c14a", "450981199505186050"); - log.info("拉取用户信息: {}", JSONUtil.toJsonStr(userDto)); - } - // 基础响应对象 @Data public static class BaseResponse { diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user/Ab98UserApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user/Ab98UserApplicationService.java index bfad31d..5108be9 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user/Ab98UserApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/ab98/user/Ab98UserApplicationService.java @@ -259,7 +259,7 @@ public class Ab98UserApplicationService { addAb98UserCommand.setIdcardFront(ab98UserDto.getIdCardFront()); addAb98UserCommand.setIdcardBack(ab98UserDto.getIdCardBack()); addAb98UserCommand.setFaceImg(ab98UserDto.getFacePicture()); - addAb98UserCommand.setAddress(ab98UserDto.getAddress()); + addAb98UserCommand.setAddress(ab98UserDto.getIdCardAddress()); addAb98UserCommand.setRegistered(true); addAb98UserCommand.initBaseEntity(); return addAb98UserCommand; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/wx/user/WxUserApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/wx/user/WxUserApplicationService.java index b7066b5..ac922cf 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/wx/user/WxUserApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/wx/user/WxUserApplicationService.java @@ -327,7 +327,7 @@ public class WxUserApplicationService { addCommand.setIdcardFront(ab98UserDto.getIdCardFront()); addCommand.setIdcardBack(ab98UserDto.getIdCardBack()); addCommand.setFaceImg(ab98UserDto.getFacePicture()); - addCommand.setAddress(ab98UserDto.getAddress()); + addCommand.setAddress(ab98UserDto.getIdCardAddress()); addCommand.setRegistered(true); addCommand.initBaseEntity(); @@ -345,7 +345,7 @@ public class WxUserApplicationService { updateCommand.setIdcardFront(ab98UserDto.getIdCardFront()); updateCommand.setIdcardBack(ab98UserDto.getIdCardBack()); updateCommand.setFaceImg(ab98UserDto.getFacePicture()); - updateCommand.setAddress(ab98UserDto.getAddress()); + updateCommand.setAddress(ab98UserDto.getIdCardAddress()); updateCommand.setRegistered(true); ab98UserApplicationService.updateUser(updateCommand); ab98UserEntity = existingUser;