|
|
@@ -0,0 +1,1072 @@
|
|
|
+package com.zsElectric.boot.thirdParty.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.util.IdUtil;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.zsElectric.boot.business.mapper.RechargeLevelMapper;
|
|
|
+import com.zsElectric.boot.business.mapper.ThirdPartyEquipmentInfoMapper;
|
|
|
+import com.zsElectric.boot.business.mapper.ThirdPartyInfoMapper;
|
|
|
+import com.zsElectric.boot.business.mapper.ThirdPartyStationInfoMapper;
|
|
|
+import com.zsElectric.boot.business.model.vo.applet.AppletConnectorDetailVO;
|
|
|
+import com.zsElectric.boot.charging.mapper.ThirdPartyConnectorInfoMapper;
|
|
|
+import com.zsElectric.boot.business.mapper.UserAccountMapper;
|
|
|
+import com.zsElectric.boot.business.mapper.UserOrderInfoMapper;
|
|
|
+import com.zsElectric.boot.business.model.entity.RechargeLevel;
|
|
|
+import com.zsElectric.boot.business.model.entity.ThirdPartyInfo;
|
|
|
+import com.zsElectric.boot.business.model.entity.UserAccount;
|
|
|
+import com.zsElectric.boot.business.model.entity.UserInfo;
|
|
|
+import com.zsElectric.boot.business.model.entity.UserOrderInfo;
|
|
|
+import com.zsElectric.boot.business.model.query.StationInfoQuery;
|
|
|
+import com.zsElectric.boot.business.model.vo.AppletStationDetailVO;
|
|
|
+import com.zsElectric.boot.business.model.vo.StationInfoVO;
|
|
|
+import com.zsElectric.boot.charging.entity.ThirdPartyConnectorInfo;
|
|
|
+import com.zsElectric.boot.charging.entity.ThirdPartyEquipmentInfo;
|
|
|
+import com.zsElectric.boot.charging.entity.ThirdPartyStationInfo;
|
|
|
+import com.zsElectric.boot.business.service.UserInfoService;
|
|
|
+import com.zsElectric.boot.business.service.UserAccountService;
|
|
|
+import com.zsElectric.boot.common.constant.SystemConstants;
|
|
|
+import com.zsElectric.boot.common.util.AESCryptoUtils;
|
|
|
+import com.zsElectric.boot.common.util.HmacMD5Util;
|
|
|
+import com.zsElectric.boot.thirdParty.model.*;
|
|
|
+import com.zsElectric.boot.thirdParty.service.ThirdPartyTokenService;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.LocalTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 第三方Token服务实现类
|
|
|
+ *
|
|
|
+ * @author wzq
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class ThirdPartyTokenServiceImpl implements ThirdPartyTokenService {
|
|
|
+
|
|
|
+ private final ThirdPartyInfoMapper thirdPartyInfoMapper;
|
|
|
+ private final RechargeLevelMapper rechargeLevelMapper;
|
|
|
+ private final RedisTemplate<String, Object> redisTemplate;
|
|
|
+ private final UserInfoService userInfoService;
|
|
|
+ private final UserAccountMapper userAccountMapper;
|
|
|
+ private final UserAccountService userAccountService;
|
|
|
+ private final UserOrderInfoMapper userOrderInfoMapper;
|
|
|
+ private final ThirdPartyStationInfoMapper thirdPartyStationInfoMapper;
|
|
|
+ private final ThirdPartyEquipmentInfoMapper thirdPartyEquipmentInfoMapper;
|
|
|
+ private final ThirdPartyConnectorInfoMapper thirdPartyConnectorInfoMapper;
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Redis Token Key 前缀
|
|
|
+ */
|
|
|
+ private static final String TOKEN_KEY_PREFIX = "third_party_access:token:";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Redis OperatorId Token映射 Key 前缀
|
|
|
+ */
|
|
|
+ private static final String OPERATOR_TOKEN_KEY_PREFIX = "third_party_access:operator:";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 默认Token有效期(秒)
|
|
|
+ */
|
|
|
+ private static final int DEFAULT_TOKEN_EXPIRE_SECONDS = 7200;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ThirdPartyResponse queryToken(ThirdPartyRequest request) {
|
|
|
+ try {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (!validateRequestParams(request)) {
|
|
|
+ return buildErrorResponse(4003, "参数不合法,缺少必需的参数", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 通过operatorId查询第三方配置信息
|
|
|
+ ThirdPartyInfo thirdPartyInfo = getThirdPartyInfoByOperatorId(request.getOperatorId());
|
|
|
+ if (thirdPartyInfo == null) {
|
|
|
+ log.warn("运营商不存在, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "运营商不存在", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证签名
|
|
|
+ String signContent = request.getOperatorId() + request.getData() + request.getTimeStamp() + request.getSeq();
|
|
|
+ if (!HmacMD5Util.verify(signContent, thirdPartyInfo.getSigSecret(), request.getSig())) {
|
|
|
+ log.warn("签名验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4001, "签名错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 解密并解析业务参数
|
|
|
+ String decryptedData = AESCryptoUtils.decrypt(
|
|
|
+ request.getData(),
|
|
|
+ thirdPartyInfo.getDataSecret(),
|
|
|
+ thirdPartyInfo.getDataSecretIV()
|
|
|
+ );
|
|
|
+ log.info("解密后的请求数据: {}", decryptedData);
|
|
|
+
|
|
|
+ QueryTokenRequestData tokenRequest = objectMapper.readValue(decryptedData, QueryTokenRequestData.class);
|
|
|
+ if (tokenRequest == null || StrUtil.isBlank(tokenRequest.getOperatorId()) || StrUtil.isBlank(tokenRequest.getOperatorSecret())) {
|
|
|
+ return buildErrorResponse(4004, "请求的业务参数不合法", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 验证运营商ID和密钥
|
|
|
+ if (!thirdPartyInfo.getOperatorId().equals(tokenRequest.getOperatorId())
|
|
|
+ || !thirdPartyInfo.getOperatorSecret().equals(tokenRequest.getOperatorSecret())) {
|
|
|
+ log.warn("运营商ID或密钥错误, operatorId: {}", tokenRequest.getOperatorId());
|
|
|
+ // 返回失败响应数据
|
|
|
+ QueryTokenResponseData failData = QueryTokenResponseData.fail(tokenRequest.getOperatorId(), 1);
|
|
|
+ return buildSuccessResponse(failData, thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 生成或获取已有Token
|
|
|
+ String accessToken = getOrGenerateToken(tokenRequest.getOperatorId());
|
|
|
+ Integer remainingTTL = getRemainingTTL(accessToken);
|
|
|
+
|
|
|
+ // 7. 构建成功响应
|
|
|
+ QueryTokenResponseData responseData = QueryTokenResponseData.success(
|
|
|
+ tokenRequest.getOperatorId(),
|
|
|
+ accessToken,
|
|
|
+ remainingTTL
|
|
|
+ );
|
|
|
+ log.info("生成Token成功, operatorId: {}, token有效期: {}秒", tokenRequest.getOperatorId(), remainingTTL);
|
|
|
+
|
|
|
+ return buildSuccessResponse(responseData, thirdPartyInfo);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理query_token请求异常", e);
|
|
|
+ return buildErrorResponse(500, "系统错误", null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ThirdPartyResponse queryRechargeLevelPage(ThirdPartyRequest request, String authorization) {
|
|
|
+ try {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (!validateRequestParams(request)) {
|
|
|
+ return buildErrorResponse(4003, "参数不合法,缺少必需的参数", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 通过operatorId查询第三方配置信息
|
|
|
+ ThirdPartyInfo thirdPartyInfo = getThirdPartyInfoByOperatorId(request.getOperatorId());
|
|
|
+ if (thirdPartyInfo == null) {
|
|
|
+ log.warn("运营商不存在, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "运营商不存在", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证签名
|
|
|
+ String signContent = request.getOperatorId() + request.getData() + request.getTimeStamp() + request.getSeq();
|
|
|
+ if (!HmacMD5Util.verify(signContent, thirdPartyInfo.getSigSecret(), request.getSig())) {
|
|
|
+ log.warn("签名验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4001, "签名错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 从Header中解析并验证Token
|
|
|
+ String accessToken = parseAccessToken(authorization);
|
|
|
+ if (StrUtil.isBlank(accessToken)) {
|
|
|
+ log.warn("Token为空或格式错误, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+ if (!validateAccessToken(accessToken, request.getOperatorId())) {
|
|
|
+ log.warn("Token验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 解密并解析业务参数
|
|
|
+ String decryptedData = AESCryptoUtils.decrypt(
|
|
|
+ request.getData(),
|
|
|
+ thirdPartyInfo.getDataSecret(),
|
|
|
+ thirdPartyInfo.getDataSecretIV()
|
|
|
+ );
|
|
|
+ log.info("解密后的请求数据: {}", decryptedData);
|
|
|
+
|
|
|
+ RechargeLevelPageRequestData pageRequest = objectMapper.readValue(decryptedData, RechargeLevelPageRequestData.class);
|
|
|
+ if (pageRequest == null) {
|
|
|
+ pageRequest = new RechargeLevelPageRequestData();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 查询充值档位分页数据
|
|
|
+ IPage<RechargeLevel> pageResult = queryRechargeLevelFromDb(pageRequest);
|
|
|
+
|
|
|
+ // 7. 转换为响应数据
|
|
|
+ List<RechargeLevelPageResponseData.RechargeLevelItem> records = pageResult.getRecords().stream()
|
|
|
+ .map(this::convertToItem)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ RechargeLevelPageResponseData responseData = RechargeLevelPageResponseData.of(
|
|
|
+ pageResult.getTotal(),
|
|
|
+ (int) pageResult.getCurrent(),
|
|
|
+ (int) pageResult.getSize(),
|
|
|
+ pageResult.getPages(),
|
|
|
+ records
|
|
|
+ );
|
|
|
+
|
|
|
+ log.info("查询充值档位成功, operatorId: {}, total: {}", request.getOperatorId(), pageResult.getTotal());
|
|
|
+ return buildSuccessResponse(responseData, thirdPartyInfo);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理query_recharge_level_page请求异常", e);
|
|
|
+ return buildErrorResponse(500, "系统错误", null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ThirdPartyResponse queryUserInfo(ThirdPartyRequest request, String authorization) {
|
|
|
+ try {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (!validateRequestParams(request)) {
|
|
|
+ return buildErrorResponse(4003, "参数不合法,缺少必需的参数", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 通过operatorId查询第三方配置信息
|
|
|
+ ThirdPartyInfo thirdPartyInfo = getThirdPartyInfoByOperatorId(request.getOperatorId());
|
|
|
+ if (thirdPartyInfo == null) {
|
|
|
+ log.warn("运营商不存在, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "运营商不存在", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证签名
|
|
|
+ String signContent = request.getOperatorId() + request.getData() + request.getTimeStamp() + request.getSeq();
|
|
|
+ if (!HmacMD5Util.verify(signContent, thirdPartyInfo.getSigSecret(), request.getSig())) {
|
|
|
+ log.warn("签名验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4001, "签名错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 从Header中解析并验证Token
|
|
|
+ String accessToken = parseAccessToken(authorization);
|
|
|
+ if (StrUtil.isBlank(accessToken)) {
|
|
|
+ log.warn("Token为空或格式错误, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+ if (!validateAccessToken(accessToken, request.getOperatorId())) {
|
|
|
+ log.warn("Token验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 解密并解析业务参数
|
|
|
+ String decryptedData = AESCryptoUtils.decrypt(
|
|
|
+ request.getData(),
|
|
|
+ thirdPartyInfo.getDataSecret(),
|
|
|
+ thirdPartyInfo.getDataSecretIV()
|
|
|
+ );
|
|
|
+ log.info("解密后的请求数据: {}", decryptedData);
|
|
|
+
|
|
|
+ QueryUserInfoRequestData userInfoRequest = objectMapper.readValue(decryptedData, QueryUserInfoRequestData.class);
|
|
|
+ if (userInfoRequest == null || StrUtil.isBlank(userInfoRequest.getPhone())) {
|
|
|
+ log.warn("手机号为空, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "请求的业务参数不合法,手机号不能为空", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ String phone = userInfoRequest.getPhone();
|
|
|
+
|
|
|
+ // 6. 根据手机号查询用户信息
|
|
|
+ UserInfo userInfo = userInfoService.getUserInfoByPhone(phone);
|
|
|
+
|
|
|
+ Integer isNewUser = 0;
|
|
|
+ if (userInfo == null) {
|
|
|
+ // 用户不存在,注册新用户
|
|
|
+ log.info("用户不存在,注册新用户, phone: {}", phone);
|
|
|
+ userInfo = userInfoService.registerOrUpdateUserByPhone(phone, null);
|
|
|
+ if (userInfo == null) {
|
|
|
+ log.error("注册用户失败, phone: {}", phone);
|
|
|
+ return buildErrorResponse(500, "注册用户失败", thirdPartyInfo);
|
|
|
+ }
|
|
|
+ isNewUser = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 查询用户账户信息
|
|
|
+ UserAccount userAccount = userAccountMapper.selectOne(
|
|
|
+ Wrappers.<UserAccount>lambdaQuery()
|
|
|
+ .eq(UserAccount::getUserId, userInfo.getId())
|
|
|
+ .eq(UserAccount::getIsDeleted, 0)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 如果账户不存在,创建账户
|
|
|
+ if (userAccount == null) {
|
|
|
+ log.info("用户账户不存在,创建账户, userId: {}", userInfo.getId());
|
|
|
+ userAccount = new UserAccount();
|
|
|
+ userAccount.setUserId(userInfo.getId());
|
|
|
+ userAccount.setBalance(BigDecimal.ZERO);
|
|
|
+ userAccount.setRedeemBalance(BigDecimal.ZERO);
|
|
|
+ userAccount.setIntegral(BigDecimal.ZERO);
|
|
|
+ userAccountMapper.insert(userAccount);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8. 构建响应数据
|
|
|
+ QueryUserInfoResponseData.UserInfoItem userInfoItem = new QueryUserInfoResponseData.UserInfoItem();
|
|
|
+ userInfoItem.setUserId(userInfo.getId());
|
|
|
+ userInfoItem.setNickName(userInfo.getNickName());
|
|
|
+ userInfoItem.setPhone(userInfo.getPhone());
|
|
|
+ userInfoItem.setOpenid(userInfo.getOpenid());
|
|
|
+
|
|
|
+ QueryUserInfoResponseData.AccountInfoItem accountInfoItem = new QueryUserInfoResponseData.AccountInfoItem();
|
|
|
+ accountInfoItem.setAccountId(userAccount.getId());
|
|
|
+ accountInfoItem.setBalance(userAccount.getBalance() != null ? userAccount.getBalance() : BigDecimal.ZERO);
|
|
|
+ accountInfoItem.setRedeemBalance(userAccount.getRedeemBalance() != null ? userAccount.getRedeemBalance() : BigDecimal.ZERO);
|
|
|
+ accountInfoItem.setIntegral(userAccount.getIntegral() != null ? userAccount.getIntegral() : BigDecimal.ZERO);
|
|
|
+
|
|
|
+ QueryUserInfoResponseData responseData = QueryUserInfoResponseData.of(isNewUser, userInfoItem, accountInfoItem);
|
|
|
+
|
|
|
+ log.info("根据手机号查询用户信息成功, operatorId: {}, phone: {}, isNewUser: {}", request.getOperatorId(), phone, isNewUser);
|
|
|
+ return buildSuccessResponse(responseData, thirdPartyInfo);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理query_user_info请求异常", e);
|
|
|
+ return buildErrorResponse(500, "系统错误", null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ThirdPartyResponse chargeOrderPay(ThirdPartyRequest request, String authorization) {
|
|
|
+ try {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (!validateRequestParams(request)) {
|
|
|
+ return buildErrorResponse(4003, "参数不合法,缺少必需的参数", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 通过operatorId查询第三方配置信息
|
|
|
+ ThirdPartyInfo thirdPartyInfo = getThirdPartyInfoByOperatorId(request.getOperatorId());
|
|
|
+ if (thirdPartyInfo == null) {
|
|
|
+ log.warn("运营商不存在, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "运营商不存在", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证签名
|
|
|
+ String signContent = request.getOperatorId() + request.getData() + request.getTimeStamp() + request.getSeq();
|
|
|
+ if (!HmacMD5Util.verify(signContent, thirdPartyInfo.getSigSecret(), request.getSig())) {
|
|
|
+ log.warn("签名验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4001, "签名错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 从Header中解析并验证Token
|
|
|
+ String accessToken = parseAccessToken(authorization);
|
|
|
+ if (StrUtil.isBlank(accessToken)) {
|
|
|
+ log.warn("Token为空或格式错误, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+ if (!validateAccessToken(accessToken, request.getOperatorId())) {
|
|
|
+ log.warn("Token验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 解密并解析业务参数
|
|
|
+ String decryptedData = AESCryptoUtils.decrypt(
|
|
|
+ request.getData(),
|
|
|
+ thirdPartyInfo.getDataSecret(),
|
|
|
+ thirdPartyInfo.getDataSecretIV()
|
|
|
+ );
|
|
|
+ log.info("解密后的请求数据: {}", decryptedData);
|
|
|
+
|
|
|
+ ChangeOrderPayRequestData payRequest = objectMapper.readValue(decryptedData, ChangeOrderPayRequestData.class);
|
|
|
+ if (payRequest == null || StrUtil.isBlank(payRequest.getPhone()) || StrUtil.isBlank(payRequest.getOrderNo())) {
|
|
|
+ log.warn("请求参数不完整, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "请求的业务参数不合法,手机号和订单号不能为空", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ Long userId = payRequest.getUserId();
|
|
|
+ String phone = payRequest.getPhone();
|
|
|
+ //根据userId和phone校验用户
|
|
|
+ UserInfo userInfo = userInfoService.getOne(Wrappers.lambdaQuery(UserInfo.class).eq(UserInfo::getPhone, phone).eq(UserInfo::getId, userId).last("limit 1"));
|
|
|
+ if (ObjectUtil.isNull(userInfo)) {
|
|
|
+ log.warn("用户不存在, userId: {}, phone: {}", userId, phone);
|
|
|
+ ChargeOrderPayResponseData failData = ChargeOrderPayResponseData.fail("用户不存在");
|
|
|
+ return buildSuccessResponse(failData, thirdPartyInfo);
|
|
|
+ }
|
|
|
+ String orderNo = payRequest.getOrderNo();
|
|
|
+ UserAccount userAccount = userAccountService.getOne(Wrappers.lambdaQuery(UserAccount.class).eq(UserAccount::getUserId, userId).last("limit 1"));
|
|
|
+ // 如果账户不存在,创建账户
|
|
|
+ if (ObjectUtil.isNull(userAccount)) {
|
|
|
+ log.info("用户账户不存在,创建账户, userId: {}", userInfo.getId());
|
|
|
+ userAccount = new UserAccount();
|
|
|
+ userAccount.setUserId(userInfo.getId());
|
|
|
+ userAccount.setBalance(BigDecimal.ZERO);
|
|
|
+ userAccount.setRedeemBalance(BigDecimal.ZERO);
|
|
|
+ userAccount.setIntegral(BigDecimal.ZERO);
|
|
|
+ userAccountMapper.insert(userAccount);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 幂等性检查 - 通过Redis检查订单是否已处理
|
|
|
+ String orderKey = "third_party_pay:order:" + orderNo;
|
|
|
+ Boolean isProcessed = redisTemplate.hasKey(orderKey);
|
|
|
+ if (Boolean.TRUE.equals(isProcessed)) {
|
|
|
+ log.warn("订单已处理,请勿重复提交, orderNo: {}", orderNo);
|
|
|
+ ChargeOrderPayResponseData failData = ChargeOrderPayResponseData.fail("订单已处理,请勿重复提交");
|
|
|
+ return buildSuccessResponse(failData, thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 查询充值档位信息(如果有levelId)
|
|
|
+ BigDecimal rechargeMoney;
|
|
|
+ if (payRequest.getLevelId() != null) {
|
|
|
+ RechargeLevel rechargeLevel = rechargeLevelMapper.selectById(payRequest.getLevelId());
|
|
|
+ if (rechargeLevel == null) {
|
|
|
+ log.warn("充值档位不存在, levelId: {}", payRequest.getLevelId());
|
|
|
+ ChargeOrderPayResponseData failData = ChargeOrderPayResponseData.fail("充值档位不存在");
|
|
|
+ return buildSuccessResponse(failData, thirdPartyInfo);
|
|
|
+ }
|
|
|
+ rechargeMoney = rechargeLevel.getMoney();
|
|
|
+ } else if (StrUtil.isNotBlank(payRequest.getTotalMoney())) {
|
|
|
+ // 使用传入的金额
|
|
|
+ rechargeMoney = new BigDecimal(payRequest.getTotalMoney());
|
|
|
+ } else {
|
|
|
+ log.warn("充值金额未指定, operatorId: {}", request.getOperatorId());
|
|
|
+ ChargeOrderPayResponseData failData = ChargeOrderPayResponseData.fail("充值金额未指定");
|
|
|
+ return buildSuccessResponse(failData, thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8. 增加充值订单记录到c_user_order_info表
|
|
|
+ UserOrderInfo orderInfo = new UserOrderInfo();
|
|
|
+ orderInfo.setUserId(userId);
|
|
|
+ orderInfo.setOrderNo(orderNo);
|
|
|
+ orderInfo.setOrderMoney(rechargeMoney);
|
|
|
+ orderInfo.setPayMoney(rechargeMoney);
|
|
|
+ orderInfo.setLastMoney(rechargeMoney);
|
|
|
+ // 解析支付时间
|
|
|
+ if (StrUtil.isNotBlank(payRequest.getPayTime())) {
|
|
|
+ try {
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+ orderInfo.setPayTime(LocalDateTime.parse(payRequest.getPayTime(), formatter));
|
|
|
+ } catch (Exception e) {
|
|
|
+ orderInfo.setPayTime(LocalDateTime.now());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ orderInfo.setPayTime(LocalDateTime.now());
|
|
|
+ }
|
|
|
+ orderInfo.setLevelId(payRequest.getLevelId());
|
|
|
+ orderInfo.setOutTradeNo(orderNo); // 第三方订单号
|
|
|
+ orderInfo.setOrderStatus(2); // 已支付
|
|
|
+ orderInfo.setOrderType(3); // 渠道方
|
|
|
+ orderInfo.setRemark("第三方渠道充值-" + request.getOperatorId());
|
|
|
+ userOrderInfoMapper.insert(orderInfo);
|
|
|
+ log.info("创建充值订单记录成功, orderNo: {}, userId: {}", orderNo, userId);
|
|
|
+
|
|
|
+ // 9. 充值账户余额并记录日志
|
|
|
+ // 使用订单号的Id值作为变更记录ID
|
|
|
+ Long changeId = orderInfo.getId();
|
|
|
+ UserAccount updatedAccount = userAccountService.updateAccountBalanceAndLog(
|
|
|
+ userId,
|
|
|
+ rechargeMoney,
|
|
|
+ SystemConstants.CHANGE_TYPE_ADD,
|
|
|
+ SystemConstants.ACCOUNT_LOG_THIRD_PARTY_PAY_NOTE + "-" + orderNo,
|
|
|
+ changeId
|
|
|
+ );
|
|
|
+
|
|
|
+ BigDecimal balanceAfter = updatedAccount != null ? updatedAccount.getBalance() : userAccount.getBalance().add(rechargeMoney);
|
|
|
+
|
|
|
+ // 10. 构建成功响应
|
|
|
+ ChargeOrderPayResponseData responseData = ChargeOrderPayResponseData.success(
|
|
|
+ userId,
|
|
|
+ rechargeMoney,
|
|
|
+ balanceAfter,
|
|
|
+ orderNo
|
|
|
+ );
|
|
|
+
|
|
|
+ log.info("充电券购买成功, operatorId: {}, phone: {}, orderNo: {}, rechargeMoney: {}, balanceAfter: {}",
|
|
|
+ request.getOperatorId(), phone, orderNo, rechargeMoney, balanceAfter);
|
|
|
+ return buildSuccessResponse(responseData, thirdPartyInfo);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理charge_order_pay请求异常", e);
|
|
|
+ return buildErrorResponse(500, "系统错误", null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Authorization Header中解析Token
|
|
|
+ * 支持格式: "Bearer {token}" 或直接传 "{token}"
|
|
|
+ */
|
|
|
+ private String parseAccessToken(String authorization) {
|
|
|
+ if (StrUtil.isBlank(authorization)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ // 支持 Bearer Token 格式
|
|
|
+ if (authorization.startsWith("Bearer ")) {
|
|
|
+ return authorization.substring(7).trim();
|
|
|
+ }
|
|
|
+ // 直接返回Token
|
|
|
+ return authorization.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证访问Token
|
|
|
+ */
|
|
|
+ private boolean validateAccessToken(String accessToken, String operatorId) {
|
|
|
+ String tokenKey = TOKEN_KEY_PREFIX + accessToken;
|
|
|
+ String storedOperatorId = (String) redisTemplate.opsForValue().get(tokenKey);
|
|
|
+ return operatorId.equals(storedOperatorId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从数据库查询充值档位分页数据
|
|
|
+ */
|
|
|
+ private IPage<RechargeLevel> queryRechargeLevelFromDb(RechargeLevelPageRequestData pageRequest) {
|
|
|
+ Page<RechargeLevel> page = new Page<>(pageRequest.getPageNum(), pageRequest.getPageSize());
|
|
|
+ LambdaQueryWrapper<RechargeLevel> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+
|
|
|
+ // 按ID排序
|
|
|
+ queryWrapper.eq(RechargeLevel::getStatus, SystemConstants.STATUS_ONE);
|
|
|
+ queryWrapper.orderByAsc(RechargeLevel::getId);
|
|
|
+
|
|
|
+ return rechargeLevelMapper.selectPage(page, queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 转换为响应项
|
|
|
+ */
|
|
|
+ private RechargeLevelPageResponseData.RechargeLevelItem convertToItem(RechargeLevel entity) {
|
|
|
+ RechargeLevelPageResponseData.RechargeLevelItem item = new RechargeLevelPageResponseData.RechargeLevelItem();
|
|
|
+ item.setId(entity.getId());
|
|
|
+ item.setName(entity.getName());
|
|
|
+ item.setMoney(entity.getMoney());
|
|
|
+ item.setStatus(entity.getStatus());
|
|
|
+ item.setTips(entity.getTips());
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过operatorId查询第三方配置信息
|
|
|
+ */
|
|
|
+ private ThirdPartyInfo getThirdPartyInfoByOperatorId(String operatorId) {
|
|
|
+ LambdaQueryWrapper<ThirdPartyInfo> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(ThirdPartyInfo::getOperatorId, operatorId)
|
|
|
+ .eq(ThirdPartyInfo::getStatus, 0) // 状态正常
|
|
|
+ .last("LIMIT 1");
|
|
|
+ return thirdPartyInfoMapper.selectOne(queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验请求参数完整性
|
|
|
+ */
|
|
|
+ private boolean validateRequestParams(ThirdPartyRequest request) {
|
|
|
+ return request != null
|
|
|
+ && StrUtil.isNotBlank(request.getOperatorId())
|
|
|
+ && StrUtil.isNotBlank(request.getData())
|
|
|
+ && StrUtil.isNotBlank(request.getTimeStamp())
|
|
|
+ && StrUtil.isNotBlank(request.getSeq())
|
|
|
+ && StrUtil.isNotBlank(request.getSig());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取或生成Token
|
|
|
+ * 如果Redis中存在有效Token则返回,否则生成新Token
|
|
|
+ */
|
|
|
+ private String getOrGenerateToken(String operatorId) {
|
|
|
+ // 检查是否有已存在的有效Token
|
|
|
+ String operatorTokenKey = OPERATOR_TOKEN_KEY_PREFIX + operatorId;
|
|
|
+ String existingToken = (String) redisTemplate.opsForValue().get(operatorTokenKey);
|
|
|
+
|
|
|
+ if (StrUtil.isNotBlank(existingToken)) {
|
|
|
+ // 验证Token是否仍然有效
|
|
|
+ String tokenKey = TOKEN_KEY_PREFIX + existingToken;
|
|
|
+ Boolean hasKey = redisTemplate.hasKey(tokenKey);
|
|
|
+ if (Boolean.TRUE.equals(hasKey)) {
|
|
|
+ log.info("返回已存在的有效Token, operatorId: {}", operatorId);
|
|
|
+ return existingToken;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成新Token
|
|
|
+ String newToken = IdUtil.fastSimpleUUID() + IdUtil.fastSimpleUUID();
|
|
|
+
|
|
|
+ // 存储Token
|
|
|
+ String tokenKey = TOKEN_KEY_PREFIX + newToken;
|
|
|
+ redisTemplate.opsForValue().set(tokenKey, operatorId, DEFAULT_TOKEN_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ // 存储OperatorId与Token的映射关系
|
|
|
+ redisTemplate.opsForValue().set(operatorTokenKey, newToken, DEFAULT_TOKEN_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ log.info("生成新Token, operatorId: {}, expireSeconds: {}", operatorId, DEFAULT_TOKEN_EXPIRE_SECONDS);
|
|
|
+ return newToken;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取Token剩余有效时间(秒)
|
|
|
+ */
|
|
|
+ private Integer getRemainingTTL(String token) {
|
|
|
+ String tokenKey = TOKEN_KEY_PREFIX + token;
|
|
|
+ Long ttl = redisTemplate.getExpire(tokenKey, TimeUnit.SECONDS);
|
|
|
+ return ttl != null && ttl > 0 ? ttl.intValue() : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建成功响应(通用)
|
|
|
+ */
|
|
|
+ private ThirdPartyResponse buildSuccessResponse(Object responseData, ThirdPartyInfo thirdPartyInfo) throws Exception {
|
|
|
+ String jsonData = objectMapper.writeValueAsString(responseData);
|
|
|
+ String encryptedData = AESCryptoUtils.encrypt(
|
|
|
+ jsonData,
|
|
|
+ thirdPartyInfo.getDataSecret(),
|
|
|
+ thirdPartyInfo.getDataSecretIV()
|
|
|
+ );
|
|
|
+ String sig = HmacMD5Util.genSign(0, "", encryptedData, thirdPartyInfo.getSigSecret());
|
|
|
+ return ThirdPartyResponse.success(encryptedData, sig);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建错误响应
|
|
|
+ */
|
|
|
+ private ThirdPartyResponse buildErrorResponse(Integer ret, String msg, ThirdPartyInfo thirdPartyInfo) {
|
|
|
+ try {
|
|
|
+ String sigSecret = thirdPartyInfo != null ? thirdPartyInfo.getSigSecret() : "";
|
|
|
+ String sig = StrUtil.isNotBlank(sigSecret) ? HmacMD5Util.genSign(ret, msg, "", sigSecret) : "";
|
|
|
+ return ThirdPartyResponse.error(ret, msg, sig);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("生成错误响应签名失败", e);
|
|
|
+ ThirdPartyResponse response = new ThirdPartyResponse();
|
|
|
+ response.setRet(ret);
|
|
|
+ response.setMsg(msg);
|
|
|
+ response.setData("");
|
|
|
+ response.setSig("");
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 充电站与设备查询接口 ====================
|
|
|
+ /**
|
|
|
+ * 时间格式化器 HHmmss
|
|
|
+ */
|
|
|
+ private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HHmmss");
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ThirdPartyResponse queryChargeStationList(ThirdPartyRequest request, String authorization) {
|
|
|
+ try {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (!validateRequestParams(request)) {
|
|
|
+ return buildErrorResponse(4003, "参数不合法,缺少必需的参数", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 通过operatorId查询第三方配置信息
|
|
|
+ ThirdPartyInfo thirdPartyInfo = getThirdPartyInfoByOperatorId(request.getOperatorId());
|
|
|
+ if (thirdPartyInfo == null) {
|
|
|
+ log.warn("运营商不存在, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "运营商不存在", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证签名
|
|
|
+ String signContent = request.getOperatorId() + request.getData() + request.getTimeStamp() + request.getSeq();
|
|
|
+ if (!HmacMD5Util.verify(signContent, thirdPartyInfo.getSigSecret(), request.getSig())) {
|
|
|
+ log.warn("签名验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4001, "签名错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 从Header中解析并验证Token
|
|
|
+ String accessToken = parseAccessToken(authorization);
|
|
|
+ if (StrUtil.isBlank(accessToken)) {
|
|
|
+ log.warn("Token为空或格式错误, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+ if (!validateAccessToken(accessToken, request.getOperatorId())) {
|
|
|
+ log.warn("Token验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 解密并解析业务参数
|
|
|
+ String decryptedData = AESCryptoUtils.decrypt(
|
|
|
+ request.getData(),
|
|
|
+ thirdPartyInfo.getDataSecret(),
|
|
|
+ thirdPartyInfo.getDataSecretIV()
|
|
|
+ );
|
|
|
+ log.info("解密后的请求数据: {}", decryptedData);
|
|
|
+
|
|
|
+ ChargeStationListRequestData listRequest = objectMapper.readValue(decryptedData, ChargeStationListRequestData.class);
|
|
|
+ if (listRequest == null) {
|
|
|
+ listRequest = new ChargeStationListRequestData();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 查询充电站分页数据
|
|
|
+ // 获取当前时间(HHmmss格式)
|
|
|
+ String currentTime = LocalTime.now().format(TIME_FORMATTER);
|
|
|
+
|
|
|
+ // 构建分页对象
|
|
|
+ Page<StationInfoVO> page = new Page<>(listRequest.getPageNum(), listRequest.getPageSize());
|
|
|
+
|
|
|
+ StationInfoQuery stationInfoQuery = new StationInfoQuery().setLatitude(listRequest.getLatitude()).setLongitude(listRequest.getLongitude()).setSortType(listRequest.getSortType());
|
|
|
+ // 执行查询
|
|
|
+ IPage<StationInfoVO> pageResult = thirdPartyStationInfoMapper.selectAppletStationInfoPage(
|
|
|
+ page, stationInfoQuery, currentTime, null
|
|
|
+ );
|
|
|
+
|
|
|
+ // 7. 转换为响应数据
|
|
|
+ List<ChargeStationListResponseData.StationItem> records = pageResult.getRecords().stream()
|
|
|
+ .map(this::convertToStationItem)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ ChargeStationListResponseData responseData = ChargeStationListResponseData.of(
|
|
|
+ pageResult.getTotal(),
|
|
|
+ (int) pageResult.getCurrent(),
|
|
|
+ (int) pageResult.getSize(),
|
|
|
+ pageResult.getPages(),
|
|
|
+ records
|
|
|
+ );
|
|
|
+
|
|
|
+ log.info("查询充电站列表成功, operatorId: {}, total: {}", request.getOperatorId(), pageResult.getTotal());
|
|
|
+ return buildSuccessResponse(responseData, thirdPartyInfo);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理query_charge_station_list请求异常", e);
|
|
|
+ return buildErrorResponse(500, "系统错误", null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ThirdPartyResponse queryChargeStationDetail(ThirdPartyRequest request, String authorization) {
|
|
|
+ try {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (!validateRequestParams(request)) {
|
|
|
+ return buildErrorResponse(4003, "参数不合法,缺少必需的参数", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 通过operatorId查询第三方配置信息
|
|
|
+ ThirdPartyInfo thirdPartyInfo = getThirdPartyInfoByOperatorId(request.getOperatorId());
|
|
|
+ if (thirdPartyInfo == null) {
|
|
|
+ log.warn("运营商不存在, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "运营商不存在", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证签名
|
|
|
+ String signContent = request.getOperatorId() + request.getData() + request.getTimeStamp() + request.getSeq();
|
|
|
+ if (!HmacMD5Util.verify(signContent, thirdPartyInfo.getSigSecret(), request.getSig())) {
|
|
|
+ log.warn("签名验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4001, "签名错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 从Header中解析并验证Token
|
|
|
+ String accessToken = parseAccessToken(authorization);
|
|
|
+ if (StrUtil.isBlank(accessToken)) {
|
|
|
+ log.warn("Token为空或格式错误, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+ if (!validateAccessToken(accessToken, request.getOperatorId())) {
|
|
|
+ log.warn("Token验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 解密并解析业务参数
|
|
|
+ String decryptedData = AESCryptoUtils.decrypt(
|
|
|
+ request.getData(),
|
|
|
+ thirdPartyInfo.getDataSecret(),
|
|
|
+ thirdPartyInfo.getDataSecretIV()
|
|
|
+ );
|
|
|
+ log.info("解密后的请求数据: {}", decryptedData);
|
|
|
+
|
|
|
+ ChargeStationDetailRequestData detailRequest = objectMapper.readValue(decryptedData, ChargeStationDetailRequestData.class);
|
|
|
+ if (detailRequest == null || (detailRequest.getStationId() == null )) {
|
|
|
+ return buildErrorResponse(4003, "请求的业务参数不合法", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 查询充电站详情
|
|
|
+ // 获取当前时间(HHmmss格式)
|
|
|
+ String currentTime = LocalTime.now().format(TIME_FORMATTER);
|
|
|
+
|
|
|
+ // 查询站点详情基本信息
|
|
|
+ AppletStationDetailVO result = thirdPartyStationInfoMapper.selectStationDetail(
|
|
|
+ detailRequest.getStationId(), detailRequest.getLongitude(), detailRequest.getLatitude(), currentTime, null
|
|
|
+ );
|
|
|
+
|
|
|
+ if (result == null) {
|
|
|
+ return buildErrorResponse(4004, "充电站不存在", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询站点对应的stationId
|
|
|
+ ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectById(detailRequest.getStationId());
|
|
|
+ if (stationInfo == null) {
|
|
|
+ return buildErrorResponse(4004, "充电站不存在", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据站点stationId查询设备列表(third_party_equipment_info)
|
|
|
+ List<ThirdPartyEquipmentInfo> equipmentList = thirdPartyEquipmentInfoMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ThirdPartyEquipmentInfo>()
|
|
|
+ .eq(ThirdPartyEquipmentInfo::getStationId, stationInfo.getStationId())
|
|
|
+ );
|
|
|
+
|
|
|
+ // 构建设备ID到设备类型的映射
|
|
|
+ Map<String, Integer> equipmentTypeMap = equipmentList.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ ThirdPartyEquipmentInfo::getEquipmentId,
|
|
|
+ e -> e.getEquipmentType() != null ? e.getEquipmentType() : 0,
|
|
|
+ (v1, v2) -> v1
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 根据设备列表的equipmentId查询充电接口信息(third_party_connector_info)
|
|
|
+ List<ThirdPartyConnectorInfo> connectorList = new ArrayList<>();
|
|
|
+ if (!equipmentList.isEmpty()) {
|
|
|
+ List<String> equipmentIds = equipmentList.stream()
|
|
|
+ .map(ThirdPartyEquipmentInfo::getEquipmentId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ connectorList = thirdPartyConnectorInfoMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ThirdPartyConnectorInfo>()
|
|
|
+ .in(ThirdPartyConnectorInfo::getEquipmentId, equipmentIds)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统计终端状态
|
|
|
+ int idleCount = 0;
|
|
|
+ int occupiedCount = 0;
|
|
|
+ int offlineCount = 0;
|
|
|
+
|
|
|
+ List<AppletStationDetailVO.ConnectorInfoVO> connectorVOList = new ArrayList<>();
|
|
|
+ for (ThirdPartyConnectorInfo connector : connectorList) {
|
|
|
+ AppletStationDetailVO.ConnectorInfoVO vo = new AppletStationDetailVO.ConnectorInfoVO();
|
|
|
+ vo.setConnectorId(connector.getId());
|
|
|
+ vo.setConnectorName(connector.getConnectorName());
|
|
|
+ vo.setConnectorCode(connector.getConnectorId());
|
|
|
+
|
|
|
+ // 设备分类:从设备表获取设备类型(1-直流设备,2-交流设备,3-交直流一体设备,4-无线设备,5-其他)
|
|
|
+ Integer equipmentType = equipmentTypeMap.get(connector.getEquipmentId());
|
|
|
+ if (equipmentType != null) {
|
|
|
+ switch (equipmentType) {
|
|
|
+ case 1:
|
|
|
+ vo.setEquipmentType("直流设备");
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ vo.setEquipmentType("交流设备");
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ vo.setEquipmentType("交直流一体设备");
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ vo.setEquipmentType("无线设备");
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+ vo.setEquipmentType("其他");
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ vo.setEquipmentType("未知");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从充电接口表获取实际状态
|
|
|
+ // 0-离网,1-空闲,2-占用(未充电),3-占用(充电中),4-占用(预约锁定),255-故障
|
|
|
+ Integer connectorStatus = connector.getStatus();
|
|
|
+ if (connectorStatus == null) {
|
|
|
+ // 默认为离网
|
|
|
+ connectorStatus = 0;
|
|
|
+ }
|
|
|
+ vo.setStatus(connectorStatus);
|
|
|
+
|
|
|
+ // 设置状态名称并统计
|
|
|
+ switch (connectorStatus) {
|
|
|
+ case 0:
|
|
|
+ vo.setStatusName("离网");
|
|
|
+ offlineCount++;
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ vo.setStatusName("空闲");
|
|
|
+ idleCount++;
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ vo.setStatusName("占用(未充电)");
|
|
|
+ occupiedCount++;
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ vo.setStatusName("占用(充电中)");
|
|
|
+ occupiedCount++;
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ vo.setStatusName("占用(预约锁定)");
|
|
|
+ occupiedCount++;
|
|
|
+ break;
|
|
|
+ case 255:
|
|
|
+ vo.setStatusName("故障");
|
|
|
+ offlineCount++;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ vo.setStatusName("未知");
|
|
|
+ offlineCount++;
|
|
|
+ }
|
|
|
+
|
|
|
+ connectorVOList.add(vo);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.setConnectorList(connectorVOList);
|
|
|
+ result.setIdleCount(idleCount);
|
|
|
+ result.setOccupiedCount(occupiedCount);
|
|
|
+ result.setOfflineCount(offlineCount);
|
|
|
+
|
|
|
+ // 7. 转换为响应数据
|
|
|
+ ChargeStationDetailResponseData responseData = convertToStationDetail(result);
|
|
|
+
|
|
|
+ log.info("查询充电站详情成功, operatorId: {}, stationId: {}", request.getOperatorId(), stationInfo.getStationId());
|
|
|
+ return buildSuccessResponse(responseData, thirdPartyInfo);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理query_charge_station_detail请求异常", e);
|
|
|
+ return buildErrorResponse(500, "系统错误", null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ThirdPartyResponse queryChargeDeviceDetail(ThirdPartyRequest request, String authorization) {
|
|
|
+ try {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (!validateRequestParams(request)) {
|
|
|
+ return buildErrorResponse(4003, "参数不合法,缺少必需的参数", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 通过operatorId查询第三方配置信息
|
|
|
+ ThirdPartyInfo thirdPartyInfo = getThirdPartyInfoByOperatorId(request.getOperatorId());
|
|
|
+ if (thirdPartyInfo == null) {
|
|
|
+ log.warn("运营商不存在, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4004, "运营商不存在", null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证签名
|
|
|
+ String signContent = request.getOperatorId() + request.getData() + request.getTimeStamp() + request.getSeq();
|
|
|
+ if (!HmacMD5Util.verify(signContent, thirdPartyInfo.getSigSecret(), request.getSig())) {
|
|
|
+ log.warn("签名验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4001, "签名错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 从Header中解析并验证Token
|
|
|
+ String accessToken = parseAccessToken(authorization);
|
|
|
+ if (StrUtil.isBlank(accessToken)) {
|
|
|
+ log.warn("Token为空或格式错误, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+ if (!validateAccessToken(accessToken, request.getOperatorId())) {
|
|
|
+ log.warn("Token验证失败, operatorId: {}", request.getOperatorId());
|
|
|
+ return buildErrorResponse(4002, "token错误", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 解密并解析业务参数
|
|
|
+ String decryptedData = AESCryptoUtils.decrypt(
|
|
|
+ request.getData(),
|
|
|
+ thirdPartyInfo.getDataSecret(),
|
|
|
+ thirdPartyInfo.getDataSecretIV()
|
|
|
+ );
|
|
|
+ log.info("解密后的请求数据: {}", decryptedData);
|
|
|
+
|
|
|
+ ChargeDeviceDetailRequestData detailRequest = objectMapper.readValue(decryptedData, ChargeDeviceDetailRequestData.class);
|
|
|
+ if (detailRequest == null || (detailRequest.getId() == null && StrUtil.isBlank(detailRequest.getEquipmentId()))) {
|
|
|
+ return buildErrorResponse(4003, "请求的业务参数不合法", thirdPartyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 查询充电设备详情
|
|
|
+ // 获取当前时间(HHmmss格式)
|
|
|
+ String currentTime = LocalTime.now().format(TIME_FORMATTER);
|
|
|
+
|
|
|
+ AppletConnectorDetailVO result = thirdPartyConnectorInfoMapper.selectConnectorDetailById(
|
|
|
+ detailRequest.getEquipmentId(), null, currentTime, null, null
|
|
|
+ );
|
|
|
+
|
|
|
+ // 8. 转换为响应数据
|
|
|
+ ChargeDeviceDetailResponseData responseData = convertToDeviceDetail(result);
|
|
|
+
|
|
|
+ log.info("查询充电终端详情成功, operatorId: {}, equipmentId: {}", request.getOperatorId(), result.getConnectorId());
|
|
|
+ return buildSuccessResponse(responseData, thirdPartyInfo);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理query_charge_device_detail请求异常", e);
|
|
|
+ return buildErrorResponse(500, "系统错误", null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 转换充电站信息为详情响应(从AppletStationDetailVO转换)
|
|
|
+ */
|
|
|
+ private ChargeStationDetailResponseData convertToStationDetail(AppletStationDetailVO result) {
|
|
|
+ ChargeStationDetailResponseData detail = new ChargeStationDetailResponseData();
|
|
|
+ detail.setStationId(result.getStationId());
|
|
|
+ detail.setStationName(result.getStationName());
|
|
|
+ detail.setTips(result.getTips());
|
|
|
+ detail.setDistance(result.getDistance());
|
|
|
+ detail.setAddress(result.getAddress());
|
|
|
+ detail.setLongitude(result.getLongitude());
|
|
|
+ detail.setLatitude(result.getLatitude());
|
|
|
+ detail.setPictures(result.getPictures());
|
|
|
+ detail.setCurrentPrice(result.getCurrentPrice());
|
|
|
+ detail.setCurrentPeriod(result.getCurrentPeriod());
|
|
|
+ detail.setOriginalPrice(result.getOriginalPrice());
|
|
|
+ detail.setPeriodFlag(result.getPeriodFlag());
|
|
|
+ detail.setIdleCount(result.getIdleCount());
|
|
|
+ detail.setOccupiedCount(result.getOccupiedCount());
|
|
|
+ detail.setOfflineCount(result.getOfflineCount());
|
|
|
+ detail.setBusinessHours(result.getBusinessHours());
|
|
|
+ detail.setServiceProvider(result.getServiceProvider());
|
|
|
+ detail.setInvoiceProvider(result.getInvoiceProvider());
|
|
|
+ detail.setCustomerServiceHotline(result.getCustomerServiceHotline());
|
|
|
+
|
|
|
+ // 转换充电终端列表
|
|
|
+ if (result.getConnectorList() != null) {
|
|
|
+ List<ChargeStationDetailResponseData.ConnectorInfoVO> connectorVOList = result.getConnectorList().stream()
|
|
|
+ .map(connector -> {
|
|
|
+ ChargeStationDetailResponseData.ConnectorInfoVO vo = new ChargeStationDetailResponseData.ConnectorInfoVO();
|
|
|
+ vo.setConnectorId(connector.getConnectorId());
|
|
|
+ vo.setConnectorName(connector.getConnectorName());
|
|
|
+ vo.setEquipmentType(connector.getEquipmentType());
|
|
|
+ vo.setConnectorCode(connector.getConnectorCode());
|
|
|
+ vo.setStatus(connector.getStatus());
|
|
|
+ vo.setStatusName(connector.getStatusName());
|
|
|
+ return vo;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ detail.setConnectorList(connectorVOList);
|
|
|
+ }
|
|
|
+
|
|
|
+ return detail;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 转换充电站信息为列表项
|
|
|
+ */
|
|
|
+ private ChargeStationListResponseData.StationItem convertToStationItem(StationInfoVO stationInfoVO) {
|
|
|
+ ChargeStationListResponseData.StationItem item = new ChargeStationListResponseData.StationItem();
|
|
|
+ item.setStationId(stationInfoVO.getStationId());
|
|
|
+ item.setStationName(stationInfoVO.getStationName());
|
|
|
+ item.setTips(stationInfoVO.getTips());
|
|
|
+ item.setDistance(stationInfoVO.getDistance());
|
|
|
+ item.setFastCharging(stationInfoVO.getFastCharging());
|
|
|
+ item.setSlowCharging(stationInfoVO.getSlowCharging());
|
|
|
+ item.setPeakValue(stationInfoVO.getPeakValue());
|
|
|
+ item.setPeakTime(stationInfoVO.getPeakTime());
|
|
|
+ item.setPeriodFlag(stationInfoVO.getPeriodFlag());
|
|
|
+ item.setPlatformPrice(stationInfoVO.getPlatformPrice());
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 转换充电设备信息为详情响应(从AppletConnectorDetailVO转换)
|
|
|
+ */
|
|
|
+ private ChargeDeviceDetailResponseData convertToDeviceDetail(AppletConnectorDetailVO connectorDetail) {
|
|
|
+ ChargeDeviceDetailResponseData detail = new ChargeDeviceDetailResponseData();
|
|
|
+ detail.setConnectorId(connectorDetail.getConnectorId());
|
|
|
+ detail.setConnectorCode(connectorDetail.getConnectorCode());
|
|
|
+ detail.setConnectorName(connectorDetail.getConnectorName());
|
|
|
+ detail.setStationId(connectorDetail.getStationId());
|
|
|
+ detail.setStationName(connectorDetail.getStationName());
|
|
|
+ detail.setStationAddress(connectorDetail.getStationAddress());
|
|
|
+ detail.setEquipmentId(connectorDetail.getEquipmentId());
|
|
|
+ detail.setEquipmentCode(connectorDetail.getEquipmentCode());
|
|
|
+ detail.setEquipmentName(connectorDetail.getEquipmentName());
|
|
|
+ detail.setEquipmentType(connectorDetail.getEquipmentType());
|
|
|
+ detail.setEquipmentTypeName(connectorDetail.getEquipmentTypeName());
|
|
|
+ detail.setParkNo(connectorDetail.getParkNo());
|
|
|
+ detail.setStatus(connectorDetail.getStatus());
|
|
|
+ detail.setStatusName(connectorDetail.getStatusName());
|
|
|
+ detail.setConnectorType(connectorDetail.getConnectorType());
|
|
|
+ detail.setConnectorTypeName(connectorDetail.getConnectorTypeName());
|
|
|
+ detail.setVoltageUpperLimits(connectorDetail.getVoltageUpperLimits());
|
|
|
+ detail.setVoltageLowerLimits(connectorDetail.getVoltageLowerLimits());
|
|
|
+ detail.setCurrent(connectorDetail.getCurrent());
|
|
|
+ detail.setPower(connectorDetail.getPower());
|
|
|
+ detail.setNationalStandard(connectorDetail.getNationalStandard());
|
|
|
+ detail.setNationalStandardName(connectorDetail.getNationalStandardName());
|
|
|
+ detail.setCurrentPrice(connectorDetail.getCurrentPrice());
|
|
|
+ detail.setPeriodFlag(connectorDetail.getPeriodFlag());
|
|
|
+ detail.setCurrentPeriodDesc(connectorDetail.getCurrentPeriodDesc());
|
|
|
+ detail.setParkingTips(connectorDetail.getParkingTips());
|
|
|
+ return detail;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|