feat(用户同步): 添加手动同步AB98用户信息功能
新增手动同步AB98用户信息接口和定时任务服务 移除Ab98ApiUtil中的main方法测试代码 修正用户地址字段映射为idCardAddress 添加/manual/**路径到安全白名单 更新测试用例中的身份证号参数
This commit is contained in:
parent
a5fc5201aa
commit
60f3595d0d
|
|
@ -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<String> 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -137,7 +137,7 @@ public class SecurityConfig {
|
||||||
.antMatchers("/login", "/register", "/captchaImage", "/api/**", "/file/**").anonymous()
|
.antMatchers("/login", "/register", "/captchaImage", "/api/**", "/file/**").anonymous()
|
||||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
|
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
|
||||||
"/profile/**").permitAll()
|
"/profile/**").permitAll()
|
||||||
.antMatchers("/qywx/**", "/test/**", "/monitor/**", "/getQyUserinfo", "/getConfig").permitAll()
|
.antMatchers("/manual/**", "/qywx/**", "/test/**", "/monitor/**", "/getQyUserinfo", "/getConfig").permitAll()
|
||||||
// TODO this is danger.
|
// TODO this is danger.
|
||||||
.antMatchers("/swagger-ui.html").anonymous()
|
.antMatchers("/swagger-ui.html").anonymous()
|
||||||
.antMatchers("/swagger-resources/**").anonymous()
|
.antMatchers("/swagger-resources/**").anonymous()
|
||||||
|
|
|
||||||
|
|
@ -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<Ab98UserEntity> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ public class CabinetControllerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPullUserInfoByIdnum() {
|
public void testPullUserInfoByIdnum() {
|
||||||
String IdNum = "452526196206215829";
|
String IdNum = "";
|
||||||
Ab98UserDto ab98UserDto = Ab98ApiUtil.pullUserInfoByIdnum("wxshop", "34164e41f0c6694be6bbbba0dc50c14a", IdNum);
|
Ab98UserDto ab98UserDto = Ab98ApiUtil.pullUserInfoByIdnum("wxshop", "34164e41f0c6694be6bbbba0dc50c14a", IdNum);
|
||||||
log.info("ab98UserDto:{}", JSONUtil.toJsonStr(ab98UserDto));
|
log.info("ab98UserDto:{}", JSONUtil.toJsonStr(ab98UserDto));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,11 +208,6 @@ public class Ab98ApiUtil {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
Ab98UserDto userDto = pullUserInfoByIdnum("wxshop", "34164e41f0c6694be6bbbba0dc50c14a", "450981199505186050");
|
|
||||||
log.info("拉取用户信息: {}", JSONUtil.toJsonStr(userDto));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 基础响应对象
|
// 基础响应对象
|
||||||
@Data
|
@Data
|
||||||
public static class BaseResponse {
|
public static class BaseResponse {
|
||||||
|
|
|
||||||
|
|
@ -259,7 +259,7 @@ public class Ab98UserApplicationService {
|
||||||
addAb98UserCommand.setIdcardFront(ab98UserDto.getIdCardFront());
|
addAb98UserCommand.setIdcardFront(ab98UserDto.getIdCardFront());
|
||||||
addAb98UserCommand.setIdcardBack(ab98UserDto.getIdCardBack());
|
addAb98UserCommand.setIdcardBack(ab98UserDto.getIdCardBack());
|
||||||
addAb98UserCommand.setFaceImg(ab98UserDto.getFacePicture());
|
addAb98UserCommand.setFaceImg(ab98UserDto.getFacePicture());
|
||||||
addAb98UserCommand.setAddress(ab98UserDto.getAddress());
|
addAb98UserCommand.setAddress(ab98UserDto.getIdCardAddress());
|
||||||
addAb98UserCommand.setRegistered(true);
|
addAb98UserCommand.setRegistered(true);
|
||||||
addAb98UserCommand.initBaseEntity();
|
addAb98UserCommand.initBaseEntity();
|
||||||
return addAb98UserCommand;
|
return addAb98UserCommand;
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,7 @@ public class WxUserApplicationService {
|
||||||
addCommand.setIdcardFront(ab98UserDto.getIdCardFront());
|
addCommand.setIdcardFront(ab98UserDto.getIdCardFront());
|
||||||
addCommand.setIdcardBack(ab98UserDto.getIdCardBack());
|
addCommand.setIdcardBack(ab98UserDto.getIdCardBack());
|
||||||
addCommand.setFaceImg(ab98UserDto.getFacePicture());
|
addCommand.setFaceImg(ab98UserDto.getFacePicture());
|
||||||
addCommand.setAddress(ab98UserDto.getAddress());
|
addCommand.setAddress(ab98UserDto.getIdCardAddress());
|
||||||
addCommand.setRegistered(true);
|
addCommand.setRegistered(true);
|
||||||
addCommand.initBaseEntity();
|
addCommand.initBaseEntity();
|
||||||
|
|
||||||
|
|
@ -345,7 +345,7 @@ public class WxUserApplicationService {
|
||||||
updateCommand.setIdcardFront(ab98UserDto.getIdCardFront());
|
updateCommand.setIdcardFront(ab98UserDto.getIdCardFront());
|
||||||
updateCommand.setIdcardBack(ab98UserDto.getIdCardBack());
|
updateCommand.setIdcardBack(ab98UserDto.getIdCardBack());
|
||||||
updateCommand.setFaceImg(ab98UserDto.getFacePicture());
|
updateCommand.setFaceImg(ab98UserDto.getFacePicture());
|
||||||
updateCommand.setAddress(ab98UserDto.getAddress());
|
updateCommand.setAddress(ab98UserDto.getIdCardAddress());
|
||||||
updateCommand.setRegistered(true);
|
updateCommand.setRegistered(true);
|
||||||
ab98UserApplicationService.updateUser(updateCommand);
|
ab98UserApplicationService.updateUser(updateCommand);
|
||||||
ab98UserEntity = existingUser;
|
ab98UserEntity = existingUser;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue