zhuml4
2026-03-06 c53bf4c2ac885780f3a63921a06233411458c87c
fix:车控调用参数和验签
7个文件已添加
5个文件已修改
688 ■■■■■ 已修改文件
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/VehicleControlClientFallbackFactory.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/VehicleControlClient.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/VehiclesCommandVo.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/DecodingException.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/enums/SignatureType.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/HexUtil.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/ResourceUtil.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/SHA1Util.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/SignatureUtils.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/ThreadLocalSHA1.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/util/VehicleControlUtil.java 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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("车控接口签名失败");
        }
    }
}