mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-06-21 07:39:55 +08:00
Merge branch 'dev' into feature-business-minigram
This commit is contained in:
@@ -35,7 +35,9 @@ import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -94,6 +96,27 @@ public class TempController extends BaseController {
|
||||
@Autowired
|
||||
private EBikeSendCommandService eBikeSendCommandService;
|
||||
|
||||
/**
|
||||
* 临时退款用户余额
|
||||
* @param dto
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/tempRefundAmount")
|
||||
public RestApiResponse<?> tempRefundAmount(@RequestBody ApplyRefundDTO dto) {
|
||||
RestApiResponse<?> response = null;
|
||||
try {
|
||||
String mode = pileMerchantInfoService.getDelayModeByWechatAppId(dto.getWechatAppId());
|
||||
// 获取处理逻辑
|
||||
AbstractProgramLogic orderLogic = ProgramLogicFactory.getProgramLogic(mode);
|
||||
orderLogic.refundBalance(dto);
|
||||
response = new RestApiResponse<>();
|
||||
} catch (Exception e) {
|
||||
logger.error("临时退款用户余额 error", e);
|
||||
response = new RestApiResponse<>(e);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 电单车开始充电
|
||||
* http://localhost:8080/temp/tempStartCharging
|
||||
@@ -1122,4 +1145,33 @@ public class TempController extends BaseController {
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入会员余额
|
||||
*/
|
||||
@PostMapping(value = "/batchImportMemberBalance", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public RestApiResponse<?> batchImportMemberBalance(@RequestParam("file") MultipartFile file) {
|
||||
RestApiResponse<?> response;
|
||||
try {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR);
|
||||
}
|
||||
|
||||
ExcelUtil<ImportMemberBalanceDTO> util = new ExcelUtil<>(ImportMemberBalanceDTO.class);
|
||||
List<ImportMemberBalanceDTO> list = util.importExcel(file.getInputStream());
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
throw new BusinessException("00300001", "Excel中未解析到有效数据");
|
||||
}
|
||||
|
||||
logger.info("批量导入会员余额, fileName:{}, totalCount:{}", file.getOriginalFilename(), list.size());
|
||||
response = new RestApiResponse<>(tempService.batchImportMemberBalance(list));
|
||||
} catch (BusinessException e) {
|
||||
logger.warn("批量导入会员余额 warn", e);
|
||||
response = new RestApiResponse<>(e.getCode(), e.getMessage());
|
||||
} catch (Exception e) {
|
||||
logger.error("批量导入会员余额 error", e);
|
||||
response = new RestApiResponse<>("00300002", "批量导入会员余额异常");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,7 +646,7 @@ public class AgentDevService {
|
||||
.mobileNumber(phoneNumber)
|
||||
.requestSource(dto.getRequestSource())
|
||||
.build();
|
||||
return memberService.memberRegisterAndLogin(loginDTO); // 其他一级运营商,微信一键登录
|
||||
return memberService.memberRegisterAndLoginV2(loginDTO); // 其他一级运营商,微信一键登录
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,6 +70,10 @@ import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class MemberService {
|
||||
private static final int MAX_MEMBER_ID_GENERATE_RETRY_TIMES = 20;
|
||||
|
||||
private static final int MAX_MEMBER_REGISTER_RETRY_TIMES = 5;
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
@@ -159,13 +163,23 @@ public class MemberService {
|
||||
public String memberRegisterAndLoginBySMS(MemberRegisterAndLoginDTO dto) {
|
||||
// 校验短信验证码 两种情况不能通过校验,1-验证码错误;2-超时 验证码10分钟有效
|
||||
checkVerificationCode(dto);
|
||||
return memberRegisterAndLogin(dto); // 短信验证码登录
|
||||
return memberRegisterAndLoginV2(dto); // 短信验证码登录
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共登录注册方法
|
||||
*
|
||||
* <p>该方法已废弃,当前会员注册/登录主流程统一走
|
||||
* {@link #memberRegisterAndLoginV2(MemberRegisterAndLoginDTO)}。</p>
|
||||
*
|
||||
* <p>保留该方法的原因:</p>
|
||||
* <p>1. 便于和 V2 做逻辑对照,确认重构前后行为一致。</p>
|
||||
* <p>2. 作为短期回溯参考,降低生产切换风险。</p>
|
||||
* <p>3. 避免在一次重构中直接删除旧实现,影响排查问题时的可读性。</p>
|
||||
*
|
||||
* @return token返给前端
|
||||
*/
|
||||
@Deprecated
|
||||
protected String memberRegisterAndLogin(MemberRegisterAndLoginDTO dto) {
|
||||
String phoneNumber = dto.getMobileNumber();
|
||||
String firstLevelMerchantId = dto.getFirstLevelMerchantId();
|
||||
@@ -277,15 +291,271 @@ public class MemberService {
|
||||
}
|
||||
}
|
||||
|
||||
private String generateNewMemberId() {
|
||||
while (true) {
|
||||
String memberId = IdUtils.getMemberId();
|
||||
// 通过memberId查询是否已经存在
|
||||
MemberVO memberVO = memberBasicInfoService.queryMemberInfoByMemberId(memberId);
|
||||
if (memberVO == null) {
|
||||
return memberId;
|
||||
/**
|
||||
* 公共登录注册方法 V2
|
||||
*
|
||||
* <p>该方法与 {@link #memberRegisterAndLogin(MemberRegisterAndLoginDTO)} 保持相同业务逻辑,
|
||||
* 但将流程拆成更清晰的几个步骤:校验请求、获取锁、查找或创建会员、同步第三方身份、生成 token。</p>
|
||||
*
|
||||
* @param dto 登录/注册入参
|
||||
* @return 服务端生成的会员 token
|
||||
*/
|
||||
protected String memberRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) {
|
||||
String phoneNumber = dto.getMobileNumber();
|
||||
String firstLevelMerchantId = dto.getFirstLevelMerchantId();
|
||||
log.info("公共登录注册方法V2, phoneNumber:{}, firstLevelMerchantId:{}, openId:{}", phoneNumber, firstLevelMerchantId, dto.getOpenId());
|
||||
|
||||
// 1. 先做参数校验,尽早拦截无效请求。
|
||||
validateMemberRegisterAndLoginV2Request(dto);
|
||||
|
||||
// 2. 使用“手机号 + 一级运营商”作为锁粒度,串行化同一会员的注册/登录流程。
|
||||
String lockKey = buildMemberRegisterAndLoginLockKey(phoneNumber, firstLevelMerchantId);
|
||||
String requestId = IdUtils.fastUUID();
|
||||
Boolean isLock = false;
|
||||
try {
|
||||
// 3. 获取锁,防止并发下重复注册或重复创建钱包。
|
||||
isLock = acquireMemberRegisterAndLoginLock(lockKey, requestId, phoneNumber, firstLevelMerchantId);
|
||||
|
||||
// 4. 会员不存在就静默注册,已存在则按请求来源同步 openId / buyerId。
|
||||
MemberBasicInfo memberBasicInfo = findOrCreateMemberForRegisterAndLoginV2(dto);
|
||||
|
||||
// 5. 最终统一生成会员 token 返回前端。
|
||||
return JWTUtils.createMemberToken(memberBasicInfo.getMemberId(), memberBasicInfo.getNickName());
|
||||
} finally {
|
||||
releaseMemberRegisterAndLoginLock(lockKey, requestId, phoneNumber, firstLevelMerchantId, isLock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 V2 登录/注册请求中的关键参数。
|
||||
*
|
||||
* @param dto 登录/注册入参
|
||||
*/
|
||||
private void validateMemberRegisterAndLoginV2Request(MemberRegisterAndLoginDTO dto) {
|
||||
if (StringUtils.isBlank(dto.getMobileNumber())) {
|
||||
throw new BusinessException(ReturnCodeEnum.CODE_GET_MOBILE_NUMBER_BY_CODE_ERROR);
|
||||
}
|
||||
if (StringUtils.isBlank(dto.getFirstLevelMerchantId())) {
|
||||
throw new BusinessException(ReturnCodeEnum.CODE_GET_MERCHANT_ID_BY_APP_ID_ERROR);
|
||||
}
|
||||
if (isWechatLiteRequest(dto) && StringUtils.isBlank(dto.getOpenId())) {
|
||||
throw new BusinessException(ReturnCodeEnum.CODE_OPEN_ID_IS_NULL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成登录/注册流程使用的分布式锁 key。
|
||||
*
|
||||
* @param phoneNumber 手机号
|
||||
* @param firstLevelMerchantId 一级运营商 ID
|
||||
* @return 分布式锁 key
|
||||
*/
|
||||
private String buildMemberRegisterAndLoginLockKey(String phoneNumber, String firstLevelMerchantId) {
|
||||
return CacheConstants.USER_APP_REGISTER + phoneNumber + ":" + firstLevelMerchantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录/注册流程的分布式锁,避免同一会员并发注册。
|
||||
*
|
||||
* @param lockKey 锁 key
|
||||
* @param requestId 当前请求唯一标识
|
||||
* @param phoneNumber 手机号
|
||||
* @param firstLevelMerchantId 一级运营商 ID
|
||||
* @return 是否成功获取锁
|
||||
*/
|
||||
private boolean acquireMemberRegisterAndLoginLock(String lockKey, String requestId, String phoneNumber, String firstLevelMerchantId) {
|
||||
boolean isLock = redisCache.lock(lockKey, requestId, 60);
|
||||
if (!isLock) {
|
||||
log.warn("获取注册锁失败,可能有并发请求正在处理, phoneNumber:{}, merchantId:{}", phoneNumber, firstLevelMerchantId);
|
||||
throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_PROCESSING);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放登录/注册流程的分布式锁。
|
||||
*
|
||||
* @param lockKey 锁 key
|
||||
* @param requestId 当前请求唯一标识
|
||||
* @param phoneNumber 手机号
|
||||
* @param firstLevelMerchantId 一级运营商 ID
|
||||
* @param isLock 是否持有锁
|
||||
*/
|
||||
private void releaseMemberRegisterAndLoginLock(String lockKey, String requestId, String phoneNumber, String firstLevelMerchantId, Boolean isLock) {
|
||||
if (!Boolean.TRUE.equals(isLock)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Object lockValue = redisCache.getCacheObject(lockKey);
|
||||
if (lockValue != null && requestId.equals(lockValue.toString())) {
|
||||
redisCache.unLock(lockKey);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("释放注册锁失败, phoneNumber:{}, merchantId:{}, error:{}", phoneNumber, firstLevelMerchantId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机号和运营商查询会员,不存在则静默注册,存在则同步第三方身份信息。
|
||||
*
|
||||
* @param dto 登录/注册入参
|
||||
* @return 已存在或新创建的会员信息
|
||||
*/
|
||||
private MemberBasicInfo findOrCreateMemberForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) {
|
||||
MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(dto.getMobileNumber(), dto.getFirstLevelMerchantId());
|
||||
if (Objects.isNull(memberBasicInfo)) {
|
||||
return registerMemberForRegisterAndLoginV2(dto);
|
||||
}
|
||||
syncMemberThirdPartyIdentityForRegisterAndLoginV2(memberBasicInfo, dto);
|
||||
return memberBasicInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行静默注册流程,并在唯一索引冲突时回查已创建的会员。
|
||||
*
|
||||
* @param dto 登录/注册入参
|
||||
* @return 注册完成后的会员信息
|
||||
*/
|
||||
private MemberBasicInfo registerMemberForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) {
|
||||
for (int attempt = 1; attempt <= MAX_MEMBER_REGISTER_RETRY_TIMES; attempt++) {
|
||||
MemberBasicInfo memberBasicInfo = buildNewMemberForRegisterAndLoginV2(dto);
|
||||
MemberTransactionDTO memberTransactionDTO = buildMemberTransactionForRegisterAndLoginV2(memberBasicInfo, dto.getFirstLevelMerchantId());
|
||||
try {
|
||||
transactionService.createMember(memberTransactionDTO);
|
||||
return memberBasicInfo;
|
||||
} catch (DuplicateKeyException e) {
|
||||
MemberBasicInfo existedMember = reloadMemberAfterDuplicateKeyForRegisterAndLoginV2(dto, memberBasicInfo.getMemberId(), attempt);
|
||||
if (existedMember != null) {
|
||||
return existedMember;
|
||||
}
|
||||
}
|
||||
}
|
||||
log.error("会员注册重试多次后仍失败, phoneNumber:{}, merchantId:{}", dto.getMobileNumber(), dto.getFirstLevelMerchantId());
|
||||
throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个新的会员基础信息对象。
|
||||
*
|
||||
* @param dto 登录/注册入参
|
||||
* @return 待入库的会员基础信息
|
||||
*/
|
||||
private MemberBasicInfo buildNewMemberForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) {
|
||||
String memberId = generateNewMemberId();
|
||||
MemberBasicInfo memberBasicInfo = new MemberBasicInfo();
|
||||
memberBasicInfo.setStatus(Constants.ONE);
|
||||
memberBasicInfo.setMemberId(memberId);
|
||||
memberBasicInfo.setNickName("会员" + memberId);
|
||||
memberBasicInfo.setMobileNumber(dto.getMobileNumber());
|
||||
memberBasicInfo.setMerchantId(Long.valueOf(dto.getFirstLevelMerchantId()));
|
||||
fillMemberThirdPartyIdentityForRegisterAndLoginV2(memberBasicInfo, dto);
|
||||
return memberBasicInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装会员注册事务对象,必要时附带创建钱包信息。
|
||||
*
|
||||
* @param memberBasicInfo 会员基础信息
|
||||
* @param firstLevelMerchantId 一级运营商 ID
|
||||
* @return 注册事务对象
|
||||
*/
|
||||
private MemberTransactionDTO buildMemberTransactionForRegisterAndLoginV2(MemberBasicInfo memberBasicInfo, String firstLevelMerchantId) {
|
||||
MemberTransactionDTO memberTransactionDTO = new MemberTransactionDTO();
|
||||
memberTransactionDTO.setMemberBasicInfo(memberBasicInfo);
|
||||
if (MerchantUtils.isXiXiaoMerchant(firstLevelMerchantId)) {
|
||||
MemberWalletInfo memberWalletInfo = MemberWalletInfo.builder()
|
||||
.memberId(memberBasicInfo.getMemberId())
|
||||
.merchantId(MerchantUtils.XIXIAO_MERCHANT_ID)
|
||||
.walletCode(memberBasicInfoService.generateWalletCode())
|
||||
.build();
|
||||
memberTransactionDTO.setMemberWalletInfo(memberWalletInfo);
|
||||
}
|
||||
return memberTransactionDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在注册发生唯一索引冲突时,回查已经被并发请求创建好的会员。
|
||||
*
|
||||
* @param dto 登录/注册入参
|
||||
* @return 已存在的会员信息
|
||||
*/
|
||||
private MemberBasicInfo reloadMemberAfterDuplicateKeyForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto, String memberId, int attempt) {
|
||||
log.warn("会员注册时检测到唯一索引冲突,重新查询已存在的会员, phoneNumber:{}, merchantId:{}, candidateMemberId:{}, attempt:{}",
|
||||
dto.getMobileNumber(), dto.getFirstLevelMerchantId(), memberId, attempt);
|
||||
MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(dto.getMobileNumber(), dto.getFirstLevelMerchantId());
|
||||
if (memberBasicInfo == null) {
|
||||
log.warn("唯一索引冲突后未查询到手机号对应会员,准备重新生成memberId重试, phoneNumber:{}, merchantId:{}, candidateMemberId:{}, attempt:{}",
|
||||
dto.getMobileNumber(), dto.getFirstLevelMerchantId(), memberId, attempt);
|
||||
return null;
|
||||
}
|
||||
return memberBasicInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步已有会员的第三方身份标识。
|
||||
*
|
||||
* @param memberBasicInfo 已存在会员
|
||||
* @param dto 登录/注册入参
|
||||
*/
|
||||
private void syncMemberThirdPartyIdentityForRegisterAndLoginV2(MemberBasicInfo memberBasicInfo, MemberRegisterAndLoginDTO dto) {
|
||||
boolean needUpdate = false;
|
||||
if (isWechatLiteRequest(dto) && !StringUtils.equals(memberBasicInfo.getOpenId(), dto.getOpenId())) {
|
||||
memberBasicInfo.setOpenId(dto.getOpenId());
|
||||
needUpdate = true;
|
||||
}
|
||||
if (isAlipayLiteRequest(dto) && !StringUtils.equals(memberBasicInfo.getBuyerId(), dto.getBuyerId())) {
|
||||
memberBasicInfo.setBuyerId(dto.getBuyerId());
|
||||
needUpdate = true;
|
||||
}
|
||||
if (needUpdate) {
|
||||
memberBasicInfoService.updateMemberBasicInfo(memberBasicInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在新建会员时,根据请求来源写入第三方身份标识。
|
||||
*
|
||||
* @param memberBasicInfo 待入库会员
|
||||
* @param dto 登录/注册入参
|
||||
*/
|
||||
private void fillMemberThirdPartyIdentityForRegisterAndLoginV2(MemberBasicInfo memberBasicInfo, MemberRegisterAndLoginDTO dto) {
|
||||
if (isWechatLiteRequest(dto) && StringUtils.isNotBlank(dto.getOpenId())) {
|
||||
memberBasicInfo.setOpenId(dto.getOpenId());
|
||||
}
|
||||
if (isAlipayLiteRequest(dto) && StringUtils.isNotBlank(dto.getBuyerId())) {
|
||||
memberBasicInfo.setBuyerId(dto.getBuyerId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前请求是否来自微信小程序。
|
||||
*
|
||||
* @param dto 登录/注册入参
|
||||
* @return 是否为微信小程序请求
|
||||
*/
|
||||
private boolean isWechatLiteRequest(MemberRegisterAndLoginDTO dto) {
|
||||
return AdapayPayChannelEnum.WX_LITE.getValue().equals(dto.getRequestSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前请求是否来自支付宝小程序。
|
||||
*
|
||||
* @param dto 登录/注册入参
|
||||
* @return 是否为支付宝小程序请求
|
||||
*/
|
||||
private boolean isAlipayLiteRequest(MemberRegisterAndLoginDTO dto) {
|
||||
return AdapayPayChannelEnum.ALIPAY_LITE.getValue().equals(dto.getRequestSource());
|
||||
}
|
||||
|
||||
private String generateNewMemberId() {
|
||||
for (int attempt = 1; attempt <= MAX_MEMBER_ID_GENERATE_RETRY_TIMES; attempt++) {
|
||||
String memberId = IdUtils.getMemberId();
|
||||
if (!memberBasicInfoService.existsByMemberId(memberId)) {
|
||||
return memberId;
|
||||
}
|
||||
log.warn("生成memberId命中已存在记录,准备重试, memberId:{}, attempt:{}", memberId, attempt);
|
||||
}
|
||||
throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,7 +589,7 @@ public class MemberService {
|
||||
.mobileNumber(mobileNumber)
|
||||
.requestSource(dto.getRequestSource())
|
||||
.build();
|
||||
return memberRegisterAndLogin(loginDTO); // 微信小程序一键登录
|
||||
return memberRegisterAndLoginV2(loginDTO); // 微信小程序一键登录
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,7 +639,7 @@ public class MemberService {
|
||||
.mobileNumber(dto.getMobileNumber())
|
||||
.buyerId(dto.getUserId())
|
||||
.build();
|
||||
return memberRegisterAndLogin(loginDTO); // 支付宝小程序一键登录
|
||||
return memberRegisterAndLoginV2(loginDTO); // 支付宝小程序一键登录
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -1553,5 +1558,116 @@ public class TempService {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件中导入会员余额
|
||||
*/
|
||||
public BatchImportMemberBalanceResultDTO batchImportMemberBalance(List<ImportMemberBalanceDTO> list) {
|
||||
BatchImportMemberBalanceResultDTO result = new BatchImportMemberBalanceResultDTO();
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.setTotalCount(list.size());
|
||||
List<ImportMemberBalanceItemResultDTO> itemResults = list.parallelStream()
|
||||
.map(this::importMemberBalanceSafely)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long successCount = itemResults.stream().filter(ImportMemberBalanceItemResultDTO::isSuccess).count();
|
||||
List<ImportMemberBalanceItemResultDTO> 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);
|
||||
String errorMessage = e.getClass().getSimpleName() + ": " + e.getMessage();
|
||||
return ImportMemberBalanceItemResultDTO.fail(phone, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void importMemberBalance(ImportMemberBalanceDTO memberBalanceDTO) {
|
||||
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");
|
||||
}
|
||||
|
||||
// 1. 根据手机号查询万车充会员信息;不存在则静默注册一个属于万车充体系的会员。
|
||||
MemberBasicInfo memberBasicInfo = findOrCreateJsowellMember(phone);
|
||||
logger.info("导入会员余额-会员准备完成, phone:{}, memberId:{}, memberMerchantId:{}, balance:{}",
|
||||
phone, memberBasicInfo.getMemberId(), memberBasicInfo.getMerchantId(), balance);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给指定会员在目标运营商钱包中增加本金余额。
|
||||
*
|
||||
* <p>如果该运营商钱包不存在,现有余额逻辑会自动创建钱包并记录流水。</p>
|
||||
*/
|
||||
private void increaseMemberWalletBalance(String memberId, BigDecimal balance) {
|
||||
logger.info("导入会员余额-开始增加钱包余额, memberId:{}, targetMerchantId:{}, balance:{}",
|
||||
memberId, NANTONG_CHENMING_WALLET_MERCHANT_ID, 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);
|
||||
logger.info("导入会员余额-钱包余额增加完成, memberId:{}, targetMerchantId:{}, balance:{}",
|
||||
memberId, NANTONG_CHENMING_WALLET_MERCHANT_ID, balance);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ package com.jsowell.web.controller.index;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.jsowell.common.core.controller.BaseController;
|
||||
import com.jsowell.common.response.RestApiResponse;
|
||||
import com.jsowell.common.util.StringUtils;
|
||||
import com.jsowell.pile.dto.IndexQueryDTO;
|
||||
import com.jsowell.pile.service.MemberBasicInfoService;
|
||||
import com.jsowell.pile.service.OrderBasicInfoService;
|
||||
import com.jsowell.pile.service.PileBasicInfoService;
|
||||
import com.jsowell.pile.vo.web.IndexGeneralSituationVO;
|
||||
import com.jsowell.pile.vo.web.IndexOrderInfoVO;
|
||||
import com.jsowell.pile.vo.web.IndexPlatformProfitVO;
|
||||
import com.jsowell.pile.service.PileStationInfoService;
|
||||
import com.jsowell.pile.vo.web.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -32,6 +33,12 @@ public class indexController extends BaseController {
|
||||
@Autowired
|
||||
private OrderBasicInfoService orderBasicInfoService;
|
||||
|
||||
@Autowired
|
||||
private MemberBasicInfoService memberBasicInfoService;
|
||||
|
||||
@Autowired
|
||||
private PileStationInfoService pileStationInfoService;
|
||||
|
||||
@PostMapping("/getGeneralSituation")
|
||||
public RestApiResponse<?> getGeneralSituation(@RequestBody IndexQueryDTO dto) {
|
||||
logger.info("首页基础数据查询 param:{}", JSON.toJSONString(dto));
|
||||
@@ -105,4 +112,109 @@ public class indexController extends BaseController {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大数据平台-总览数据
|
||||
*/
|
||||
@PostMapping("/getBigDataOverview")
|
||||
public RestApiResponse<?> getBigDataOverview(@RequestBody(required = false) IndexQueryDTO dto) {
|
||||
if (dto == null) dto = new IndexQueryDTO();
|
||||
logger.info("大数据平台总览数据查询 param:{}", JSON.toJSONString(dto));
|
||||
RestApiResponse<?> response;
|
||||
try {
|
||||
BigDataOverviewVO overviewVO = new BigDataOverviewVO();
|
||||
overviewVO.setTotalUsers(memberBasicInfoService.countTotalMembers());
|
||||
overviewVO.setTotalOrders(orderBasicInfoService.countTotalOrders());
|
||||
IndexGeneralSituationVO generalSituation = pileBasicInfoService.getGeneralSituation(dto);
|
||||
overviewVO.setTotalTransactionAmount(generalSituation.getTotalChargingAmount());
|
||||
overviewVO.setTotalElectricity(generalSituation.getTotalChargingDegree());
|
||||
overviewVO.setTotalPiles(Long.valueOf(generalSituation.getTotalPileQuantity()));
|
||||
overviewVO.setTotalStations(pileStationInfoService.countTotalStations());
|
||||
response = new RestApiResponse<>(overviewVO);
|
||||
} catch (Exception e) {
|
||||
logger.error("大数据平台总览数据查询错误", e);
|
||||
response = new RestApiResponse<>("00200005", "大数据平台总览数据查询错误");
|
||||
}
|
||||
logger.info("大数据平台总览数据查询 result:{}", JSON.toJSONString(response));
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大数据平台-充电站地图数据
|
||||
*/
|
||||
@PostMapping("/getStationMapData")
|
||||
public RestApiResponse<?> getStationMapData(@RequestBody(required = false) IndexQueryDTO dto) {
|
||||
logger.info("大数据平台充电站地图数据查询 param:{}", JSON.toJSONString(dto));
|
||||
RestApiResponse<?> response;
|
||||
try {
|
||||
List<StationMapVO> stationMapData = pileStationInfoService.getStationMapData();
|
||||
response = new RestApiResponse<>(stationMapData);
|
||||
} catch (Exception e) {
|
||||
logger.error("大数据平台充电站地图数据查询错误", e);
|
||||
response = new RestApiResponse<>("00200006", "大数据平台充电站地图数据查询错误");
|
||||
}
|
||||
logger.info("大数据平台充电站地图数据查询 result:{}", JSON.toJSONString(response));
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大数据平台-使用率趋势数据
|
||||
*/
|
||||
@PostMapping("/getUsageTrend")
|
||||
public RestApiResponse<?> getUsageTrend(@RequestBody IndexQueryDTO dto) {
|
||||
logger.info("大数据平台使用率趋势查询 param:{}", JSON.toJSONString(dto));
|
||||
RestApiResponse<?> response;
|
||||
try {
|
||||
List<UsageTrendVO> trendData = orderBasicInfoService.getUsageTrendData(dto);
|
||||
response = new RestApiResponse<>(trendData);
|
||||
} catch (Exception e) {
|
||||
logger.error("大数据平台使用率趋势查询错误", e);
|
||||
response = new RestApiResponse<>("00200007", "大数据平台使用率趋势查询错误");
|
||||
}
|
||||
logger.info("大数据平台使用率趋势查询 result:{}", JSON.toJSONString(response));
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大数据平台-城市设备数量占比(饼图)
|
||||
*/
|
||||
@PostMapping("/getCityDeviceCount")
|
||||
public RestApiResponse<?> getCityDeviceCount() {
|
||||
logger.info("大数据平台城市设备数量查询");
|
||||
RestApiResponse<?> response;
|
||||
try {
|
||||
List<CityDeviceCountVO> data = pileStationInfoService.getCityDeviceCount();
|
||||
response = new RestApiResponse<>(data);
|
||||
} catch (Exception e) {
|
||||
logger.error("大数据平台城市设备数量查询错误", e);
|
||||
response = new RestApiResponse<>("00200008", "城市设备数量查询错误");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 大数据平台-充电时段分布
|
||||
*/
|
||||
@PostMapping("/getTimeDistribution")
|
||||
public RestApiResponse<?> getTimeDistribution(@RequestBody(required = false) IndexQueryDTO dto) {
|
||||
if (dto == null) dto = new IndexQueryDTO();
|
||||
// 默认查询最近30天,避免全表扫描超时
|
||||
if (StringUtils.isEmpty(dto.getStartTime()) || StringUtils.isEmpty(dto.getEndTime())) {
|
||||
java.time.LocalDate endDate = java.time.LocalDate.now();
|
||||
java.time.LocalDate startDate = endDate.minusDays(29);
|
||||
java.time.format.DateTimeFormatter fmt = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
dto.setStartTime(startDate.format(fmt) + " 00:00:00");
|
||||
dto.setEndTime(endDate.format(fmt) + " 23:59:59");
|
||||
}
|
||||
logger.info("大数据平台充电时段分布查询 param:{}", JSON.toJSONString(dto));
|
||||
RestApiResponse<?> response;
|
||||
try {
|
||||
List<TimeDistributionVO> data = orderBasicInfoService.getTimeDistribution(dto);
|
||||
response = new RestApiResponse<>(data);
|
||||
} catch (Exception e) {
|
||||
logger.error("大数据平台充电时段分布查询错误", e);
|
||||
response = new RestApiResponse<>("00200009", "充电时段分布查询错误");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.jsowell.web.controller.pile;
|
||||
|
||||
import com.jsowell.common.annotation.Log;
|
||||
import com.jsowell.common.constant.UserConstants;
|
||||
import com.jsowell.common.core.controller.BaseController;
|
||||
import com.jsowell.common.core.domain.AjaxResult;
|
||||
import com.jsowell.common.core.page.TableDataInfo;
|
||||
import com.jsowell.common.enums.BusinessType;
|
||||
import com.jsowell.common.util.StringUtils;
|
||||
import com.jsowell.common.util.poi.ExcelUtil;
|
||||
import com.jsowell.pile.domain.PileFirmwareInfo;
|
||||
import com.jsowell.pile.service.PileFirmwareInfoService;
|
||||
@@ -59,6 +61,16 @@ public class PileFirmwareInfoController extends BaseController {
|
||||
return AjaxResult.success(pileFirmwareInfoService.selectPileFirmwareInfoById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验固件名称是否唯一
|
||||
*/
|
||||
@PreAuthorize("@ss.hasAnyPermi('pile:firmware:query,pile:firmware:add,pile:firmware:edit')")
|
||||
@GetMapping("/checkNameUnique")
|
||||
public AjaxResult checkNameUnique(PileFirmwareInfo pileFirmwareInfo) {
|
||||
pileFirmwareInfo.setName(StringUtils.trim(pileFirmwareInfo.getName()));
|
||||
return AjaxResult.success("操作成功", pileFirmwareInfoService.checkNameUnique(pileFirmwareInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增充电桩固件信息
|
||||
*/
|
||||
@@ -66,6 +78,10 @@ public class PileFirmwareInfoController extends BaseController {
|
||||
@Log(title = "充电桩固件信息", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody PileFirmwareInfo pileFirmwareInfo) {
|
||||
pileFirmwareInfo.setName(StringUtils.trim(pileFirmwareInfo.getName()));
|
||||
if (UserConstants.NOT_UNIQUE.equals(pileFirmwareInfoService.checkNameUnique(pileFirmwareInfo))) {
|
||||
return AjaxResult.error("新增固件'" + pileFirmwareInfo.getName() + "'失败,固件名称已存在");
|
||||
}
|
||||
return toAjax(pileFirmwareInfoService.insertPileFirmwareInfo(pileFirmwareInfo));
|
||||
}
|
||||
|
||||
@@ -76,6 +92,10 @@ public class PileFirmwareInfoController extends BaseController {
|
||||
@Log(title = "充电桩固件信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody PileFirmwareInfo pileFirmwareInfo) {
|
||||
pileFirmwareInfo.setName(StringUtils.trim(pileFirmwareInfo.getName()));
|
||||
if (UserConstants.NOT_UNIQUE.equals(pileFirmwareInfoService.checkNameUnique(pileFirmwareInfo))) {
|
||||
return AjaxResult.error("修改固件'" + pileFirmwareInfo.getName() + "'失败,固件名称已存在");
|
||||
}
|
||||
return toAjax(pileFirmwareInfoService.updatePileFirmwareInfo(pileFirmwareInfo));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user