xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/VehicleControlClientFallbackFactory.java
@@ -2,10 +2,11 @@ import com.xiaopeng.api.dto.vehicleConvoy.BaseImOpenDto; import com.xiaopeng.api.feignClient.vehicleConvoy.VehicleControlClient; import com.xiaopeng.api.vo.vehicleConvoy.VehicleTemperatureVo; import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandVo; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FallbackFactory; import java.util.Set; @Slf4j public class VehicleControlClientFallbackFactory implements FallbackFactory<VehicleControlClient> { @@ -13,13 +14,13 @@ public VehicleControlClient create(Throwable cause) { return new VehicleControlClient() { @Override public BaseImOpenDto queryVehicleTemperature(VehicleTemperatureVo vo, String uid) { public BaseImOpenDto queryVehicleTemperature(String appId, String nonce, String accessToken, String sign, String openId, String vin, Set<String> stateList, String uid) { log.error("查询车辆内温度失败:{}", cause.getMessage()); return null; } @Override public BaseImOpenDto vehiclesCommand(VehiclesCommandVo vo, Long uid) { public BaseImOpenDto vehiclesCommand(String appId, String nonce, String accessToken, String sign, String openId, VehiclesCommandVo vo, Long uid) { log.error("车辆控制失败:{}", cause.getMessage()); return null; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/VehicleControlClient.java
@@ -1,16 +1,14 @@ package com.xiaopeng.api.feignClient.vehicleConvoy; import com.alibaba.fastjson.JSONObject; import com.xiaopeng.api.dto.vehicleConvoy.BaseImOpenDto; import com.xiaopeng.api.factory.vehicleConvoy.VehicleControlClientFallbackFactory; import com.xiaopeng.api.vo.vehicleConvoy.VehicleTemperatureVo; import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandVo; import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.*; import java.util.Set; /** * 小鹏内部车控服务接口 @@ -22,19 +20,35 @@ /** * 车内温度查询 * * @return */ @GetMapping("/logan/api/v1/vehicles/state") BaseImOpenDto queryVehicleTemperature(VehicleTemperatureVo vo, @RequestHeader("uid") String uid); BaseImOpenDto queryVehicleTemperature( @RequestParam("appId") String appId, @RequestParam("nonce") String nonce, @RequestParam(value = "accessToken", required = false) String accessToken, @RequestParam("sign") String sign, @RequestParam("openId") String openId, @RequestParam("vin") String vin, @RequestParam("stateList") Set<String> stateList, @RequestHeader("uid") String uid); /** * 车辆控制 * * @param vo * @param uid * @return */ @PostMapping("/logan/api/v1/vehicles/command") BaseImOpenDto vehiclesCommand(VehiclesCommandVo vo, @RequestHeader("uid") Long uid); BaseImOpenDto vehiclesCommand( @RequestParam("appId") String appId, @RequestParam("nonce") String nonce, @RequestParam(value = "accessToken", required = false) String accessToken, @RequestParam("sign") String sign, @RequestParam("openId") String openId, @RequestBody VehiclesCommandVo vo, @RequestHeader("uid") Long uid); } xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/VehiclesCommandVo.java
@@ -1,33 +1,12 @@ package com.xiaopeng.api.vo.vehicleConvoy; import lombok.Getter; import lombok.Data; /** * 车辆控制请求 */ @Getter @Data public class VehiclesCommandVo { /** * 接入方应用ID,由小鹏分配,需要向用户中心工单申请 */ private String appId; /** * 请求时间戳 */ private String nonce; /** * 签名,验签作用 */ private String sign; /** * 开放平台接入合作方ID: * 自动驾驶出租车(robot_taxi) */ private String openId; /** * 用户id */ private Long uid; /** * 车辆vin */ @@ -40,40 +19,4 @@ * 命令参数 */ private VehiclesCommandParamVo extParam; public void setUid(Long uid) { this.uid = uid; } public void setVin(String vin) { this.vin = vin; } public void setCommandCode(VehiclesCommandEnum commandCode) { this.commandCode = commandCode.getCommand(); } public void setExtParam(VehiclesCommandParamVo extParam) { this.extParam = extParam; } public void setAppId(String appId) { this.appId = appId; } public void setNonce(String nonce) { this.nonce = nonce; } public void setSign(String sign) { this.sign = sign; } public void setOpenId(String openId) { this.openId = openId; } public void setCommandCode(String commandCode) { this.commandCode = commandCode; } } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/DecodingException.java
New file @@ -0,0 +1,17 @@ package com.xiaopeng.vehicleConvoy; /** * 解码处理异常类</br> */ public class DecodingException extends RuntimeException { private static final long serialVersionUID = 1L; public DecodingException(String message) { super(message); } public DecodingException(Throwable cause) { super(cause); } } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/enums/SignatureType.java
New file @@ -0,0 +1,15 @@ package com.xiaopeng.vehicleConvoy.enums; /** 签名类型枚举 */ public enum SignatureType { MD5("MD5"), SHA1("SHA1"), SHA256("SHA256"); private String value; private SignatureType(String value) { this.value = value; } public String getValue() { return value; } } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java
@@ -40,7 +40,6 @@ import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.redis.core.RedisTemplate; @@ -128,12 +127,12 @@ // 3. 根据围栏ID查询车辆列表 (operatingStatus=1 且 state=0) LambdaQueryWrapper<XpVehicle> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(XpVehicle::getElectronicId, electronicId) .eq(XpVehicle::getOperatingStatus, 1) .eq(XpVehicle::getState, 0); .eq(XpVehicle::getOperatingStatus, 1) .eq(XpVehicle::getState, 0); // 排序:按电量降序、入库时间降序 wrapper.orderByDesc(XpVehicle::getCurrentElectricity) .orderByDesc(XpVehicle::getWarehousingTime); .orderByDesc(XpVehicle::getWarehousingTime); return this.list(wrapper); } @@ -142,20 +141,20 @@ public PageInfo<XpVehicleVO> pageList(VehicleMapQuery query) { PageInfo<XpVehicleVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); if(StringUtils.isNotBlank(query.getStatus())){ if (StringUtils.isNotBlank(query.getStatus())) { query.setStatusList(Arrays.asList(query.getStatus().split(","))); } if(StringUtils.isNotBlank(query.getElectronicId())){ if (StringUtils.isNotBlank(query.getElectronicId())) { query.setElectronicIdList(Arrays.asList(query.getElectronicId().split(","))); } List<XpVehicleVO> list = this.baseMapper.pageList(pageInfo, query); if (CollectionUtils.isEmpty(list)){ if (CollectionUtils.isEmpty(list)) { return pageInfo; } for (XpVehicleVO xpVehicleVO : list) { List<Point> positionList = redisTemplate.opsForGeo().position("vehiclePosition:" + xpVehicleVO.getVehicleVin(), xpVehicleVO.getVehicleVin()); if (CollUtil.isNotEmpty(positionList)){ if (CollUtil.isNotEmpty(positionList)) { Point geoJsonPoint = positionList.get(0); // 获取经纬度 xpVehicleVO.setLongitude(geoJsonPoint.getX()); @@ -163,10 +162,10 @@ } // 查询车辆的续航里程 VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicleVO.getVehicleVin())), VehiclePosition.class); if(Objects.nonNull(vehiclePosition)){ if (Objects.nonNull(vehiclePosition)) { xpVehicleVO.setMileageRange(vehiclePosition.getMileageRange()); xpVehicleVO.setCurrentElectricity(vehiclePosition.getCurrentElectricity()); }else { } else { xpVehicleVO.setMileageRange(0F); xpVehicleVO.setCurrentElectricity(0); } @@ -190,44 +189,43 @@ if (fenceId == null) { return Collections.emptyList(); } return lambdaQuery().eq(XpVehicle::getElectronicId, fenceId).eq(XpVehicle::getOperatingStatus,1).eq(XpVehicle::getState,0).list(); return lambdaQuery().eq(XpVehicle::getElectronicId, fenceId).eq(XpVehicle::getOperatingStatus, 1).eq(XpVehicle::getState, 0).list(); } @Override public Integer unlock(UnlockAppletDTO dto) { // 查询订单 XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData(); if(Objects.isNull(xpOrder)){ if (Objects.isNull(xpOrder)) { throw new ServiceException("未查询到有效订单信息", I18nKeyConstant.ORDER_NOT_EXISTS); } XpVehicle xpVehicle = this.getById(xpOrder.getVehicleId()); if(Objects.isNull(xpVehicle)){ if (Objects.isNull(xpVehicle)) { throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS); } // 车门解锁、后备箱解锁 VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo(); vehiclesCommandVo.setUid(xpOrder.getAppUserId()); vehiclesCommandVo.setVin(xpVehicle.getVehicleVin()); if("door".equals(dto.getDoorType())){ if ("door".equals(dto.getDoorType())) { // 开启车门 判断是否为P档 Integer gearLevel = redisService.getCacheObject(CacheConstants.GEAR_LEVEL + xpVehicle.getVehicleVin()); if(gearLevel == null || gearLevel != 4){ if (gearLevel == null || gearLevel != 4) { throw new ServiceException("车辆非P档,无法开启车门", I18nKeyConstant.VEHICLE_NOT_P_GEAR); } vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_UNLOCK); }else { vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_UNLOCK.getCommand()); } else { // 开启后备箱 判断是否为P档 Integer gearLevel = redisService.getCacheObject(CacheConstants.GEAR_LEVEL + xpVehicle.getXpuId()); if(gearLevel == null || gearLevel != 4){ if (gearLevel == null || gearLevel != 4) { throw new ServiceException("车辆非P档,无法开启后备箱", I18nKeyConstant.VEHICLE_NOT_P_GEAR); } vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_TRUNK_OPEN); vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.DOOR_TRUNK_OPEN.getCommand()); } try{ String result = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo); try { String result = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, xpOrder.getAppUserId()); log.info("车辆控制结果:{}", result); return 1; }catch (Exception e){ } catch (Exception e) { log.error("车辆控制异常:{}", e.getMessage()); return 0; } @@ -237,32 +235,31 @@ public Integer setTemperature(SetTemperatureDTO dto) { // 查询订单 XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData(); if(Objects.isNull(xpOrder)){ if (Objects.isNull(xpOrder)) { throw new ServiceException("未查询到有效订单信息", I18nKeyConstant.ORDER_NOT_EXISTS); } XpVehicle xpVehicle = this.getById(xpOrder.getVehicleId()); if(Objects.isNull(xpVehicle)){ if (Objects.isNull(xpVehicle)) { throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS); } if(dto.getTargetTemperature() > 28){ if (dto.getTargetTemperature() > 28) { throw new ServiceException("温度最高28℃", I18nKeyConstant.VEHICLE_TEMPERATURE_UP); } if(dto.getTargetTemperature() < 22){ if (dto.getTargetTemperature() < 22) { throw new ServiceException("温度最低22℃", I18nKeyConstant.VEHICLE_TEMPERATURE_DOWN); } // 设定温度 VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo(); vehiclesCommandVo.setUid(xpOrder.getAppUserId()); vehiclesCommandVo.setVin(xpVehicle.getVehicleVin()); vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.HVAC_TEMP_SET); vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.HVAC_TEMP_SET.getCommand()); VehiclesCommandParamVo vehiclesCommandParamVo = new VehiclesCommandParamVo(); vehiclesCommandParamVo.setDegree(dto.getTargetTemperature()); vehiclesCommandVo.setExtParam(vehiclesCommandParamVo); try{ String result = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo); try { String result = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, xpOrder.getAppUserId()); log.info("车辆控制结果:{}", result); return 1; }catch (Exception e){ } catch (Exception e) { log.error("车辆控制异常:{}", e.getMessage()); return 0; } @@ -460,8 +457,9 @@ * 检查车辆是否有任意一项证件过期(精确到日期) * - 精确度:yyyy-MM-dd(只比较日期部分) * - 到期日期等于今天不算过期(使用 isBefore) * * @param vehicle 车辆信息 * @param today 今天日期 * @param today 今天日期 */ private boolean isAnyDateExpired(XpVehicle vehicle, LocalDate today) { // 年检到期时间检查:已过期 @@ -624,7 +622,7 @@ if (CollUtil.isNotEmpty(expiredVehicleIds)) { LambdaUpdateWrapper<XpVehicle> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.in(XpVehicle::getId, expiredVehicleIds) .set(XpVehicle::getStatus, 3); .set(XpVehicle::getStatus, 3); this.update(updateWrapper); } } @@ -753,21 +751,21 @@ } @Override public VehicleTemperatureDto queryTemperatureByOrderId(Long orderId,String uid) { public VehicleTemperatureDto queryTemperatureByOrderId(Long orderId, String uid) { XpOrder xpOrder = xpOrderClient.queryOrderById(orderId).getData(); if(Objects.isNull(xpOrder)){ if (Objects.isNull(xpOrder)) { throw new ServiceException("未查询到有效订单信息", I18nKeyConstant.ORDER_NOT_EXISTS); } XpVehicle xpVehicle = this.getById(xpOrder.getVehicleId()); if(Objects.isNull(xpVehicle)){ if (Objects.isNull(xpVehicle)) { throw new ServiceException("未查询到有效车辆信息", I18nKeyConstant.VEHICLE_NOT_EXISTS); } try{ try { VehicleTemperatureDto vehicleTemperatureDto = vehicleControlUtil.queryVehicleTemperature(uid, xpVehicle.getVehicleVin()); log.info("查询车辆温度成功:{}",vehicleTemperatureDto); log.info("查询车辆温度成功:{}", vehicleTemperatureDto); return vehicleTemperatureDto; }catch (Exception e){ log.error("查询车辆温度失败",e); } catch (Exception e) { log.error("查询车辆温度失败", e); return new VehicleTemperatureDto(); } } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/HexUtil.java
New file @@ -0,0 +1,36 @@ package com.xiaopeng.vehicleConvoy.util; import com.xiaopeng.vehicleConvoy.DecodingException; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; /** * 与字节16进制相关的工具类<br/> */ public class HexUtil { /** 将十六进制字符串转成字节数组 */ public static byte[] toHexBytes(String hexText) { try { return Hex.decodeHex(hexText.toCharArray()); } catch (DecoderException e) { throw new DecodingException(e); } } /** 将字节数组转成十六进制字符串(字母小写) */ public static String toLowerCase(byte[] hex) { return new String(Hex.encodeHex(hex, true)); } /** 将字节数组转成十六进制字符串(字母大写) */ public static String toUpperCase(byte[] hex) { return new String(Hex.encodeHex(hex, false)); } /** 将字节数组转成整数 */ public static int hashToInt(byte[] hash) { return (int) (hash[3] << 24 | hash[2] << 16 | hash[1] << 8 | hash[0]); } } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/ResourceUtil.java
New file @@ -0,0 +1,82 @@ package com.xiaopeng.vehicleConvoy.util; import java.io.*; import java.lang.reflect.Method; import java.net.URL; /** * 资源工具类 * @author binghuoliu * */ public class ResourceUtil { /** * 读取classpath下的文件<br> * 读取classpath下的文件 一般用于读取classpath下的配置文件,注意读取的配置文件路径是相对传入参数clazz的load入路径. 例:<br> * ClassTest c1;//假设c1是从/usr/local/classpath/ 载入<br> * ClassTest c2;//假设c2是从/home/classpath/ 载入<br> * ............<br> * loadConfigFile("a.xml",c1.class);//从/usr/local/classpath1/下寻找a.xml<br> * loadConfigFile("a.xml",c2.class);//从/home/classpath/下寻找a.xml<br> * @param resource 资源路径 * @param clazz * @return InputStream 文件的inputstram,如果找不到则返回null */ @SuppressWarnings("unchecked") public static InputStream loadConfigFile(String resource, Class clazz) { ClassLoader classLoader = null; try { Method method = Thread.class.getMethod("getContextClassLoader"); classLoader = (ClassLoader) method.invoke(Thread.currentThread()); } catch (Exception e) { throw new RuntimeException("loadConfigFile error: ", e); } if (classLoader == null) { classLoader = clazz.getClassLoader(); } try { if (classLoader != null) { URL url = classLoader.getResource(resource); if (url == null) { System.out.println("Can not find resource:" + resource); return null; } if (url.toString().startsWith("jar:file:")) { System.out.println("Get resource \"" + resource + "\" from jar:\t" + url.toString()); return clazz.getResourceAsStream(resource.startsWith("/") ? resource : "/" + resource); } else { System.out.println("Get resource \"" + resource + "\" from:\t" + url.toString()); return new FileInputStream(new File(url.toURI())); } } } catch (Exception e) { throw new RuntimeException("loadConfigFile error: ", e); } return null; } /** * 释放资源(有异常抛出) * @param closeable * @throws IOException */ public static void close(Closeable closeable) throws IOException { if (closeable != null) { closeable.close(); } } /** * 以优雅的形式释放资源(内部吃掉了异常) * @param closeable */ public static void closeGracefully(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException e) { } } } } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/SHA1Util.java
New file @@ -0,0 +1,118 @@ package com.xiaopeng.vehicleConvoy.util; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; /** * 该API支持线程安全<br/> */ public class SHA1Util { // --------------------------计算SHA1大写字符串---------------------------------------------- /** * 计算出文件的SHA1大写字符串 * * @param file * @return * @throws IOException */ public static String getSHA1UpperString(File file) throws IOException { return HexUtil.toUpperCase(getSHA1Bytes(file)); } /** * 计算出SHA1后十六进制结果(大写) * * @param s * @return */ public static String getSHA1UpperString(String s) { try { return getSHA1UpperString(s.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * 计算出SHA1后十六进制结果(大写) * * @param bytes * @return */ public static String getSHA1UpperString(byte[] bytes) { return HexUtil.toUpperCase(getSHA1Bytes(bytes)); } // --------------------------计算SHA1小写字符串---------------------------------------------- /** * 计算出文件的SHA1小写字符串 * * @param file * @return * @throws IOException */ public static String getSHA1LowerString(File file) throws IOException { return HexUtil.toLowerCase(getSHA1Bytes(file)); } /** * 计算出SHA1后十六进制结果(小写) * * @param s * @return */ public static String getSHA1LowerString(String s) { try { return getSHA1LowerString(s.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * 计算出SHA1后十六进制结果(小写) * * @param bytes * @return */ public static String getSHA1LowerString(byte[] bytes) { return HexUtil.toLowerCase(getSHA1Bytes(bytes)); } // ------------------------计算出SHA1的byte[]------------------------------------------------ public static byte[] getSHA1Bytes(byte[] bytes) { ThreadLocalSHA1.current().update(bytes); return ThreadLocalSHA1.current().digest(); } public static byte[] getSHA1Bytes(File file) throws IOException { FileInputStream in = null; FileChannel ch = null; try { in = new FileInputStream(file); ch = in.getChannel(); MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); ThreadLocalSHA1.current().update(byteBuffer); return ThreadLocalSHA1.current().digest(); } finally { ResourceUtil.closeGracefully(ch); ResourceUtil.closeGracefully(in); } } // ------------------------------------------------------------------------ public static boolean checkPassword(String originContent, String sha1Content) { String s = getSHA1LowerString(originContent); return s.equals(sha1Content); } // ------------------------------------------------------------------------ } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/SignatureUtils.java
New file @@ -0,0 +1,114 @@ package com.xiaopeng.vehicleConvoy.util; import com.xiaopeng.unifiedverification.util.md5.MD5Util; import com.xiaopeng.unifiedverification.util.sha256.SHA256Util; import com.xiaopeng.vehicleConvoy.enums.SignatureType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; public class SignatureUtils { private static final Logger LOGGER = LoggerFactory.getLogger(SignatureUtils.class); /** * 通用签名方法,签名算法为SHA1<br/> * 签名原文拼装规则:<br/> * ①paraMap按key的自然排序,并按key1+value1+key2+value2的规则拼接,中间无分隔符<br/> * ②在第①步骤组装的String前面追加appid的值<br/> * ③在第①步骤组装的String后面追加appSecret的值<br/> * * @param appId 业务系统唯一标识 * @param paraMap 所需要传递的参数map * @param appSecret 分配给业务系统的秘钥 * @return */ public static String getSignatureSHA1(String appId, Map<String, String> paraMap, String appSecret) { return getSignature(appId, paraMap, appSecret, SignatureType.SHA1); } /** * 通用签名方法<br/> * 签名原文拼装规则:<br/> * ①paraMap按key的自然排序,并按key1+value1+key2+value2的规则拼接,中间无分隔符<br/> * ②在第①步骤组装的String前面追加appid的值<br/> * ③在第①步骤组装的String后面追加appSecret的值<br/> * * @param appId 业务系统唯一标识 * @param paraMap 所需要传递的参数map * @param appSecret 分配给业务系统的秘钥 * @param signatureType 签名算法类型(参考SignatureType) * @return */ public static String getSignature(String appId, Map<String, String> paraMap, String appSecret, SignatureType signatureType) { String originStr = buildAllSignatureOriginStr(appId, paraMap, appSecret); LOGGER.info("originStr: {}", originStr); return getSignature(originStr, signatureType); } // ------------------------------------------------------------------------------------------ /** * 构造计算签名的完整消息内容<br/> */ public static String buildAllSignatureOriginStr(String appId, Map<String, String> paraMap, String appSecret) { StringBuilder sb = new StringBuilder(128); String result = sb.append(appId).append(buildMiddleSignatureOriginStr(paraMap)).append(appSecret).toString(); // 用于签名的原文 return result; } /** * 构造计算签名的中间消息内容<br/> */ public static String buildMiddleSignatureOriginStr(Map<String, String> map) { if (map.isEmpty()) { return ""; } Map<String, String> treeMap = new TreeMap<String, String>(new Comparator<String>() { public int compare(String obj1, String obj2) { // 升序排序 return obj1.compareTo(obj2); } }); treeMap.putAll(map); Iterator<Map.Entry<String, String>> iter = treeMap.entrySet().iterator(); StringBuilder stringBuilder = new StringBuilder(128); while (iter.hasNext()) { Map.Entry<String, String> entry = iter.next(); stringBuilder.append(entry.getKey()).append(entry.getValue()); } return stringBuilder.toString(); } /** * 计算签名 * * @param originStr * 用于签名的原始内容 * @param signatureType * 签名算法类型(参考SignatureType) * @return 签名值 */ public static String getSignature(String originStr, SignatureType signatureType) { if (SignatureType.MD5 == signatureType) { return MD5Util.getMD5LowerString(originStr); } else if (SignatureType.SHA1 == signatureType) { return SHA1Util.getSHA1LowerString(originStr); } else if (SignatureType.SHA256 == signatureType) { return SHA256Util.getSHA256LowerString(originStr); } else { // 默认用SHA1 return SHA1Util.getSHA1LowerString(originStr); } } } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/ThreadLocalSHA1.java
New file @@ -0,0 +1,26 @@ package com.xiaopeng.vehicleConvoy.util; import java.security.MessageDigest; /** * SHA1算法计算散列值的ThreadLocal,避免多线程场景下的线程安全问题<br/> */ public class ThreadLocalSHA1 { private static final ThreadLocal<MessageDigest> localFormat = new ThreadLocal<MessageDigest>() { protected MessageDigest initialValue() { try { return MessageDigest.getInstance("SHA1"); } catch (Exception e) { System.err.println(SHA1Util.class.getName() + "初始化失败,MessageDigest不支持SHA1Util。"); e.printStackTrace(); return null; } }; }; /** 获取当前线程对应的实例 */ public static MessageDigest current() { return localFormat.get(); } } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/VehicleControlUtil.java
@@ -1,26 +1,17 @@ package com.xiaopeng.vehicleConvoy.util; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.xiaopeng.api.dto.vehicleConvoy.BaseImOpenDto; import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto; import com.xiaopeng.api.feignClient.vehicleConvoy.VehicleControlClient; import com.xiaopeng.api.vo.vehicleConvoy.VehicleTemperatureVo; import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandVo; import com.xiaopeng.common.core.utils.StringUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.*; /** * 车辆控制工具类 @@ -29,6 +20,7 @@ @Component public class VehicleControlUtil { public static final String OPEN_ID_ROBOT_TAXI = "robot_taxi"; @Resource private VehicleControlClient vehicleControlClient; // 车辆控制接口appid @@ -41,22 +33,25 @@ /** * 查询车辆内温度 * @param uid 用户id * @param vin 车辆vin * 测试车辆:L1NNSGHA5PB000204 * @return * * @param uid 用户id * @param vin 车辆vin * 测试车辆:L1NNSGHA5PB000204 * @return 车辆温度信息 */ public VehicleTemperatureDto queryVehicleTemperature(String uid, String vin) { String nonce = System.currentTimeMillis() + ""; VehicleTemperatureVo vo = new VehicleTemperatureVo(); vo.setVin(vin); vo.setStateList(Arrays.asList("hvacInnerTemp")); vo.setAppId(appid); vo.setNonce(nonce); vo.setOpenId("robot_taxi"); vo.setSign(sign("", nonce)); log.info("查询车内温度 请求参数:{}", JSON.toJSONString(vo)); BaseImOpenDto baseImOpenDto = vehicleControlClient.queryVehicleTemperature(vo, uid); Set<String> stateList = Collections.singleton("hvacInnerTemp"); Map<String, String> map = new HashMap<>(); map.put("nonce", nonce); map.put("openId", OPEN_ID_ROBOT_TAXI); map.put("accessToken", ""); map.put("body", ""); map.put("vin", vin); map.put("stateList", Arrays.toString(stateList.toArray())); String sign = SignatureUtils.getSignatureSHA1(appid, map, appSecret); log.info("查询车内温度 请求参数:uid={}, vin={}", uid, vin); BaseImOpenDto baseImOpenDto = vehicleControlClient.queryVehicleTemperature(appid, nonce, "", sign, OPEN_ID_ROBOT_TAXI, vin, stateList, uid); if (null == baseImOpenDto || null == baseImOpenDto.getData()) { throw new RuntimeException("查询车内温度失败"); } @@ -69,23 +64,21 @@ /** * 车辆控制操作 * @param commandVo * @return msgId 消息id,对比消息回调通知 * * @param commandVo 车辆控制命令参数 * @return msgId 消息id,对比消息回调通知 * 测试车辆:L1NNSGHA5PB000204 */ public String vehiclesCommand(VehiclesCommandVo commandVo){ public String vehiclesCommand(VehiclesCommandVo commandVo, Long uid) { String nonce = System.currentTimeMillis() + ""; commandVo.setAppId(appid); commandVo.setNonce(nonce); commandVo.setOpenId("robot_taxi"); Map<String, Object> map = new HashMap<>(); map.put("uid", commandVo.getUid()); map.put("vin", commandVo.getVin()); map.put("commandCode", commandVo.getCommandCode()); map.put("extParam", commandVo.getExtParam()); commandVo.setSign(sign(JSON.toJSONString(map), nonce)); Map<String, String> map = new HashMap<>(); map.put("nonce", nonce); map.put("openId", OPEN_ID_ROBOT_TAXI); map.put("accessToken", ""); map.put("body", JSON.toJSONString(commandVo)); String sign = SignatureUtils.getSignatureSHA1(appid, map, appSecret); log.info("车辆控制 请求参数:{}", JSON.toJSONString(commandVo)); BaseImOpenDto baseImOpenDto = vehicleControlClient.vehiclesCommand(commandVo, commandVo.getUid()); BaseImOpenDto baseImOpenDto = vehicleControlClient.vehiclesCommand(appid, nonce, "", sign, OPEN_ID_ROBOT_TAXI, commandVo, uid); if (null == baseImOpenDto || null == baseImOpenDto.getData()) { throw new RuntimeException("车辆控制失败"); } @@ -93,44 +86,5 @@ throw new RuntimeException("车辆控制失败:" + baseImOpenDto.getMsg()); } return baseImOpenDto.getData().toString(); } /** * 参数签名 * @param nonce 请求时间戳 * @return */ private String sign(String body, String nonce){ Map<String, String> map = new HashMap<>(); map.put("openId", appid); map.put("body", body); map.put("nonce", nonce); //将map的key进行自然排序再以key+value拼接中间无分隔符 StringBuilder stringBuilder = new StringBuilder(); map.keySet().stream().sorted(String::compareTo).forEach(key -> { String v = map.get(key); if(StringUtils.isNotEmpty(v)){ stringBuilder.append(key).append(v); } }); String stringA = stringBuilder.toString(); String stringB = appid + stringA + appSecret; log.info("车控接口待签名串:{}", stringB); try { byte[] signByte = HMACSHA1.HmacSHA1Encrypt(stringB, appSecret); String localSign = Base64.encodeBase64String(signByte); log.info("车控接口已签名串:{}", stringB); return localSign; } catch (Exception e) { e.printStackTrace(); log.error("车控接口签名失败", e); throw new RuntimeException("车控接口签名失败"); } } }