diff --git a/jsowell-admin/src/main/java/com/jsowell/service/TempService.java b/jsowell-admin/src/main/java/com/jsowell/service/TempService.java index c70507198..373223840 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/TempService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/TempService.java @@ -21,6 +21,7 @@ import com.jsowell.common.constant.Constants; import com.jsowell.common.constant.RabbitConstants; import com.jsowell.common.core.domain.ykc.TransactionRecordsData; import com.jsowell.common.core.redis.RedisCache; +import com.jsowell.common.enums.MemberWalletEnum; import com.jsowell.common.enums.adapay.AdapayStatusEnum; import com.jsowell.common.enums.adapay.MerchantDelayModeEnum; import com.jsowell.common.enums.ykc.*; @@ -65,6 +66,10 @@ import java.util.stream.Collectors; @Service public class TempService { + private static final String JSOWELL_MEMBER_MERCHANT_ID = "1"; + + private static final String NANTONG_CHENMING_WALLET_MERCHANT_ID = "575"; + private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired @@ -1557,22 +1562,105 @@ public class TempService { /** * 从文件中导入会员余额 */ - public void batchImportMemberBalance(List list) { + public BatchImportMemberBalanceResultDTO batchImportMemberBalance(List list) { + BatchImportMemberBalanceResultDTO result = new BatchImportMemberBalanceResultDTO(); if (CollectionUtils.isEmpty(list)) { - return; + return result; + } + + result.setTotalCount(list.size()); + List itemResults = list.parallelStream() + .map(this::importMemberBalanceSafely) + .collect(Collectors.toList()); + + long successCount = itemResults.stream().filter(ImportMemberBalanceItemResultDTO::isSuccess).count(); + List failedList = itemResults.stream() + .filter(item -> !item.isSuccess()) + .collect(Collectors.toList()); + + result.setSuccessCount((int) successCount); + result.setFailCount(failedList.size()); + result.setFailedList(failedList); + + logger.info("批量导入会员余额完成, totalCount:{}, successCount:{}, failCount:{}", + result.getTotalCount(), result.getSuccessCount(), result.getFailCount()); + return result; + } + + private ImportMemberBalanceItemResultDTO importMemberBalanceSafely(ImportMemberBalanceDTO memberBalanceDTO) { + String phone = memberBalanceDTO != null ? memberBalanceDTO.getPhone() : null; + try { + importMemberBalance(memberBalanceDTO); + return ImportMemberBalanceItemResultDTO.success(phone); + } catch (Exception e) { + logger.error("导入会员余额失败, phone:{}, param:{}", phone, JSON.toJSONString(memberBalanceDTO), e); + return ImportMemberBalanceItemResultDTO.fail(phone, e.getMessage()); } - list.parallelStream().forEach(this::importMemberBalance); } private void importMemberBalance(ImportMemberBalanceDTO memberBalanceDTO) { - // 1. 根据手机号查询万车充会员信息 - MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(memberBalanceDTO.getPhone(), "1"); - // 2. 如果没有万车充会员信息,则自动注册万车充会员,并创建“南通晨鸣中锦置业有限责任公司”运营商钱包 - if (memberBasicInfo == null) { - + if (memberBalanceDTO == null) { + throw new BusinessException("", "导入会员余额失败,参数不能为空"); + } + String phone = memberBalanceDTO.getPhone(); + BigDecimal balance = memberBalanceDTO.getBalance(); + if (StringUtils.isBlank(phone)) { + throw new BusinessException("", "导入会员余额失败,手机号不能为空"); + } + if (balance == null || balance.compareTo(BigDecimal.ZERO) <= 0) { + throw new BusinessException("", "导入会员余额失败,余额必须大于0"); } - // 3. 根据balance添加余额 - } -} + // 1. 根据手机号查询万车充会员信息;不存在则静默注册一个属于万车充体系的会员。 + MemberBasicInfo memberBasicInfo = findOrCreateJsowellMember(phone); + // 2. 给“南通晨鸣中锦置业有限责任公司”运营商钱包增加本金余额。 + increaseMemberWalletBalance(memberBasicInfo.getMemberId(), balance); + + logger.info("导入会员余额成功, phone:{}, memberId:{}, merchantId:{}, balance:{}", + phone, memberBasicInfo.getMemberId(), NANTONG_CHENMING_WALLET_MERCHANT_ID, balance); + } + + /** + * 根据手机号查询万车充会员;若不存在,则自动注册后重新查询。 + */ + private MemberBasicInfo findOrCreateJsowellMember(String phone) { + MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(phone, JSOWELL_MEMBER_MERCHANT_ID); + if (memberBasicInfo != null) { + return memberBasicInfo; + } + + logger.info("导入会员余额时未查询到万车充会员,开始自动注册, phone:{}, merchantId:{}", + phone, JSOWELL_MEMBER_MERCHANT_ID); + MemberRegisterAndLoginDTO loginDTO = MemberRegisterAndLoginDTO.builder() + .mobileNumber(phone) + .firstLevelMerchantId(JSOWELL_MEMBER_MERCHANT_ID) + .build(); + memberService.memberRegisterAndLoginV2(loginDTO); + + memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(phone, JSOWELL_MEMBER_MERCHANT_ID); + if (memberBasicInfo == null) { + logger.error("导入会员余额时自动注册后仍未查询到会员, phone:{}, merchantId:{}", + phone, JSOWELL_MEMBER_MERCHANT_ID); + throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_ERROR); + } + return memberBasicInfo; + } + + /** + * 给指定会员在目标运营商钱包中增加本金余额。 + * + *

如果该运营商钱包不存在,现有余额逻辑会自动创建钱包并记录流水。

+ */ + private void increaseMemberWalletBalance(String memberId, BigDecimal balance) { + UpdateMemberBalanceDTO updateMemberBalanceDTO = UpdateMemberBalanceDTO.builder() + .memberId(memberId) + .type(MemberWalletEnum.TYPE_IN.getValue()) + .subType(MemberWalletEnum.SUBTYPE_TOP_UP.getValue()) + .updatePrincipalBalance(balance) + .targetMerchantId(NANTONG_CHENMING_WALLET_MERCHANT_ID) + .build(); + memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO); + } + +} diff --git a/jsowell-admin/src/test/java/com/jsowell/service/TempServiceImportMemberBalanceTest.java b/jsowell-admin/src/test/java/com/jsowell/service/TempServiceImportMemberBalanceTest.java new file mode 100644 index 000000000..7735fe9a4 --- /dev/null +++ b/jsowell-admin/src/test/java/com/jsowell/service/TempServiceImportMemberBalanceTest.java @@ -0,0 +1,144 @@ +package com.jsowell.service; + +import com.jsowell.pile.dto.BatchImportMemberBalanceResultDTO; +import com.jsowell.common.enums.MemberWalletEnum; +import com.jsowell.pile.domain.MemberBasicInfo; +import com.jsowell.pile.dto.ImportMemberBalanceDTO; +import com.jsowell.pile.dto.ImportMemberBalanceItemResultDTO; +import com.jsowell.pile.dto.MemberRegisterAndLoginDTO; +import com.jsowell.pile.service.MemberBasicInfoService; +import com.jsowell.pile.vo.web.UpdateMemberBalanceDTO; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class TempServiceImportMemberBalanceTest { + + @Test + void importMemberBalance_shouldIncreaseWalletBalance_whenMemberAlreadyExists() throws Exception { + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + MemberService memberService = mock(MemberService.class); + + MemberBasicInfo memberBasicInfo = MemberBasicInfo.builder() + .memberId("M1001") + .mobileNumber("13800000001") + .merchantId(1L) + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13800000001", "1")).thenReturn(memberBasicInfo); + + TempService tempService = newTempService(memberBasicInfoService, memberService); + ImportMemberBalanceDTO dto = new ImportMemberBalanceDTO(); + dto.setPhone("13800000001"); + dto.setBalance(new BigDecimal("88.50")); + + invokeImportMemberBalance(tempService, dto); + + verify(memberService, never()).memberRegisterAndLoginV2(any(MemberRegisterAndLoginDTO.class)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UpdateMemberBalanceDTO.class); + verify(memberBasicInfoService).updateMemberBalance(captor.capture()); + UpdateMemberBalanceDTO updateDto = captor.getValue(); + assertEquals("M1001", updateDto.getMemberId()); + assertEquals(MemberWalletEnum.TYPE_IN.getValue(), updateDto.getType()); + assertEquals(MemberWalletEnum.SUBTYPE_TOP_UP.getValue(), updateDto.getSubType()); + assertEquals(new BigDecimal("88.50"), updateDto.getUpdatePrincipalBalance()); + assertEquals("575", updateDto.getTargetMerchantId()); + } + + @Test + void importMemberBalance_shouldRegisterMemberBeforeIncreaseBalance_whenMemberDoesNotExist() throws Exception { + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + MemberService memberService = mock(MemberService.class); + + MemberBasicInfo memberBasicInfo = MemberBasicInfo.builder() + .memberId("M2002") + .mobileNumber("13800000002") + .merchantId(1L) + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13800000002", "1")).thenReturn(null, memberBasicInfo); + + TempService tempService = newTempService(memberBasicInfoService, memberService); + ImportMemberBalanceDTO dto = new ImportMemberBalanceDTO(); + dto.setPhone("13800000002"); + dto.setBalance(new BigDecimal("100")); + + invokeImportMemberBalance(tempService, dto); + + ArgumentCaptor loginCaptor = ArgumentCaptor.forClass(MemberRegisterAndLoginDTO.class); + verify(memberService).memberRegisterAndLoginV2(loginCaptor.capture()); + MemberRegisterAndLoginDTO loginDTO = loginCaptor.getValue(); + assertEquals("13800000002", loginDTO.getMobileNumber()); + assertEquals("1", loginDTO.getFirstLevelMerchantId()); + + ArgumentCaptor balanceCaptor = ArgumentCaptor.forClass(UpdateMemberBalanceDTO.class); + verify(memberBasicInfoService).updateMemberBalance(balanceCaptor.capture()); + assertEquals("M2002", balanceCaptor.getValue().getMemberId()); + assertEquals("575", balanceCaptor.getValue().getTargetMerchantId()); + assertEquals(new BigDecimal("100"), balanceCaptor.getValue().getUpdatePrincipalBalance()); + } + + @Test + void batchImportMemberBalance_shouldReturnSummary_whenMixedRecords() { + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + MemberService memberService = mock(MemberService.class); + + MemberBasicInfo memberBasicInfo = MemberBasicInfo.builder() + .memberId("M3003") + .mobileNumber("13800000003") + .merchantId(1L) + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13800000003", "1")).thenReturn(memberBasicInfo); + + TempService tempService = newTempService(memberBasicInfoService, memberService); + + ImportMemberBalanceDTO successDto = new ImportMemberBalanceDTO(); + successDto.setPhone("13800000003"); + successDto.setBalance(new BigDecimal("50")); + + ImportMemberBalanceDTO failDto = new ImportMemberBalanceDTO(); + failDto.setPhone(""); + failDto.setBalance(new BigDecimal("20")); + + BatchImportMemberBalanceResultDTO result = tempService.batchImportMemberBalance(Arrays.asList(successDto, failDto)); + + assertNotNull(result); + assertEquals(2, result.getTotalCount()); + assertEquals(1, result.getSuccessCount()); + assertEquals(1, result.getFailCount()); + assertEquals(1, result.getFailedList().size()); + ImportMemberBalanceItemResultDTO failedItem = result.getFailedList().get(0); + assertEquals("", failedItem.getPhone()); + } + + private static TempService newTempService(MemberBasicInfoService memberBasicInfoService, MemberService memberService) { + TempService tempService = new TempService(); + setField(tempService, "memberBasicInfoService", memberBasicInfoService); + setField(tempService, "memberService", memberService); + return tempService; + } + + private static void invokeImportMemberBalance(TempService tempService, ImportMemberBalanceDTO dto) throws Exception { + Method method = TempService.class.getDeclaredMethod("importMemberBalance", ImportMemberBalanceDTO.class); + method.setAccessible(true); + method.invoke(tempService, dto); + } + + private static void setField(Object target, String fieldName, Object value) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatchImportMemberBalanceResultDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatchImportMemberBalanceResultDTO.java new file mode 100644 index 000000000..df7091a9c --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatchImportMemberBalanceResultDTO.java @@ -0,0 +1,14 @@ +package com.jsowell.pile.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class BatchImportMemberBalanceResultDTO { + private int totalCount; + private int successCount; + private int failCount; + private List failedList = new ArrayList<>(); +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceItemResultDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceItemResultDTO.java new file mode 100644 index 000000000..4f887f7cf --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceItemResultDTO.java @@ -0,0 +1,25 @@ +package com.jsowell.pile.dto; + +import lombok.Data; + +@Data +public class ImportMemberBalanceItemResultDTO { + private boolean success; + private String phone; + private String errorMessage; + + public static ImportMemberBalanceItemResultDTO success(String phone) { + ImportMemberBalanceItemResultDTO result = new ImportMemberBalanceItemResultDTO(); + result.setSuccess(true); + result.setPhone(phone); + return result; + } + + public static ImportMemberBalanceItemResultDTO fail(String phone, String errorMessage) { + ImportMemberBalanceItemResultDTO result = new ImportMemberBalanceItemResultDTO(); + result.setSuccess(false); + result.setPhone(phone); + result.setErrorMessage(errorMessage); + return result; + } +}