高德云店——商家推送充电设备动态数据

高德充电设备状态推送接口实现文档

一、接口基本信息

  • SPI: amap.charging.pushStationStatus
  • 方向: 商家 → 高德
  • 描述: 商家实时推送全量充电设备状态信息
  • 前置条件: 接入准备文档
  • 业务文档: 接口规范

密钥配置截图

二、核心实现代码

1. 配置类

/**
 * @author Cyf
 * @since 1.0.0
 */
@Data
@Component
@ConfigurationProperties(prefix="gaode")
public class GaodeConfig {
    private String appId;          // 应用ID
    private String privateKey;     // 商户私钥
    private String pushStationStatus; // 接口方法名
    private String appEnv;         // 环境标识
    private String url;            // API地址
}

2. 数据模型

站点信息模型

/**
 * @author Cyf
 * @since 1.0.0
 */
@Data
public class GaodePush {
    @JsonProperty("stationID")
    private String stationID;      // 场站编码
    
    @JsonProperty("fast_free")
    private Integer fastFree;      // 快充空闲数
    
    @JsonProperty("fast_total")
    private Integer fastTotal;     // 快充总数
    
    @JsonProperty("slow_free")
    private Integer slowFree;      // 慢充空闲数
    
    @JsonProperty("slow_total")
    private Integer slowTotal;     // 慢充总数
    
    @JsonProperty("connectorStatusInfo")
    private List<GaoDePushConnectorStatusInfoVO> connectorStatusInfo; // 充电枪列表
}

充电枪状态模型

/**
 * @author Cyf
 * @since 1.0.0
 */
@Data
public class GaoDePushConnectorStatusInfoVO {
    @JsonProperty("EquipmentID")
    private String equipmentId;     // 设备ID
    
    @JsonProperty("ConnectorID")
    private String connectorId;    // 充电接口编码
    
    @JsonProperty("Status")
    private Integer status;       // 接口状态(0:离线 1:空闲 2:占用)
}

3. 功能实现


/**
 * @author Cyf
 * @since 1.0.0
 */
@Service
@RequiredArgsConstructor
public class GaodeService {

    private final OkHttpClient client = new OkHttpClient();
    private final GaodeConfig gaodeConfig;

    public String pushStationStatus() {
            //具体的业务
            List<GaoDePush> list = chargingStationService.listStationStatus();
            for (GaoDePush gaoDePush : list) {
                List<GaoDePushConnectorStatusInfoVO> gunInfo = chargingGunService.getGunInfo(gaoDePush.getStationID());
                gaoDePush.setConnectorStatusInfo(gunInfo);
            }
            //将所需要的信息转换成JSON字符串
            String json = JsonUtils.toJson(list);
            try {
                //封装,加签,发送请求
                return push(json);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
    }

    public String push(String json) throws Exception {
        Map<String, String> paramMap = new HashMap<String, String>();
        // 构造业务参数
        // 构造参与加签的公共参数
        paramMap.put("charset", "UTF-8");
        paramMap.put("biz_content", json);
        paramMap.put("utc_timestamp", String.valueOf(System.currentTimeMillis()));
        paramMap.put("app_id", gaodeConfig.getAppId());
        paramMap.put("version", "1.0");
        paramMap.put("method", gaodeConfig.getPushStationStatus());
        paramMap.put("sign_type", "RSA2");

        //带着前面的参数去生成签名
        String sign = generateSign(paramMap);
        paramMap.put("sign", sign);

        //打印最终请求高德参数
        System.out.println("请求高德的参数:" + paramMap);

        //推送高德接口
        return sendRequest(paramMap, gaodeConfig.getUrl());
    }

      /**
       * 使用商家私钥生成签名
       *
       * @param paramMap
       * @return
       * @throws Exception
       */
      public String generateSign(Map<String, String> paramMap) throws Exception {
          //使用商家私钥进行加签,请在高德云店「接入准备及配置」页面生成并获取商家私钥
          String signContent = getSignContent(paramMap);
          return getSign(signContent, gaodeConfig.getPrivateKey());
      }

      /**
       * 参数转换为待加签字符串
       *
       * @param paramMap 待生成加密sign的参数集合
       */
      private String getSignContent(Map<String, String> paramMap) {
          StringBuilder content = new StringBuilder();
          List<String> keys = new ArrayList<>(paramMap.keySet());
          // 将参数集合排序
          Collections.sort(keys);
          for (int i = 0; i < keys.size(); i++) {
              String key = keys.get(i);
              //排除不需要参与签名的公共参数
              if ("sign_type".equals(key) || "sign".equals(key) || "need_encrypt".equals(key)) {
                  continue;
              }
              String value = paramMap.get(key);
              // 拼装所有非空参数
              if (key != null && !"".equalsIgnoreCase(key) && value != null && !"".equalsIgnoreCase(value)) {
                  content.append(i == 0 ? "" : "&").append(key).append("=").append(value);
              }
          }
          return content.toString();
      }

      /**
       * 字符串加签
       *
       * @param signContent        待加密的参数字符串
       * @param merchantPrivateKey 商家应用私钥
       * @throws IOException
       * @throws GeneralSecurityException
       */
      private String getSign(String signContent, String merchantPrivateKey) throws IOException, GeneralSecurityException {
          // 添加空值检查
          if (merchantPrivateKey == null || merchantPrivateKey.isEmpty()) {
              throw new IllegalStateException("商户私钥未配置或为空");
          }

          KeyFactory keyFactory = KeyFactory.getInstance("RSA");

          // 清理私钥内容,移除可能的头尾和空格
          String privateKeyContent = merchantPrivateKey
                  .replace("-----BEGIN PRIVATE KEY-----", "")
                  .replace("-----END PRIVATE KEY-----", "")
                  .replace("-----BEGIN RSA PRIVATE KEY-----", "")
                  .replace("-----END RSA PRIVATE KEY-----", "")
                  .replaceAll("\\s", "");

          byte[] encodedKey = Base64.getDecoder().decode(privateKeyContent);
          PrivateKey priKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));

          Signature signature = Signature.getInstance("SHA256WithRSA");
          signature.initSign(priKey);
          signature.update(signContent.getBytes(StandardCharsets.UTF_8));
          byte[] signed = signature.sign();
          return Base64.getEncoder().encodeToString(signed);
      }

      /**
       * 发送请求
       * @param paramMap 请求参数
       * @param apiUrl 请求地址
       * @return 响应结果
       * @throws IOException
       */
      public String sendRequest(Map<String, String> paramMap, String apiUrl) throws IOException {
          // 1. 构建FormBody.Builder
          FormBody.Builder formBuilder = new FormBody.Builder();

          // 2. 遍历paramMap,添加所有参数
          for (Map.Entry<String, String> entry : paramMap.entrySet()) {
              formBuilder.add(entry.getKey(), entry.getValue());
          }

          // 3. 构建RequestBody
          RequestBody formBody = formBuilder.build();

          // 4. 创建Request
          Request request = new Request.Builder()
                  .url(apiUrl)
                  .post(formBody)
                  .header("Content-Type", "application/x-www-form-urlencoded")
                  .build();
          System.out.println("发送请求: " + request);

          // 5. 发送请求并处理响应
          try (Response response = client.newCall(request).execute()) {
              if (!response.isSuccessful()) {
                  throw new IOException("请求失败,状态码: " + response.code());
              }
              String result = response.body().string();
              System.out.println("响应结果: " + result);
              return result;
          }
      }
}

4. JsonUtils工具类


import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.MapLikeType;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Cyf
 * @since 1.0.0
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils {

    private static final ObjectMapper OBJECT_MAPPER = SpringUtil.getBean(ObjectMapper.class);

    static {
        // 忽略未知字段
        OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, Boolean.FALSE);
        // 属性为NULL不被序列化
        OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 关闭空对象不让序列化功能
        OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, Boolean.FALSE);
        // 允许没有引号的字段名
        OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, Boolean.TRUE);
        // 允许单引号字段名
        OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, Boolean.TRUE);
        OBJECT_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
    }

    public static <T> T readValue(String json, Type type) throws JsonProcessingException {
        return OBJECT_MAPPER.readValue(json, OBJECT_MAPPER.constructType(type));
    }

    public static <T> String toJson(T t) {
        try {
            return OBJECT_MAPPER.writeValueAsString(t);
        } catch (JsonProcessingException ex) {
            log.error("Jackson对象转json异常", ex);
        }
        return null;
    }

    public static <T> T toObj(String s, Class<T> c) {
        try {
            return OBJECT_MAPPER.readValue(s, c);
        } catch (Exception ex) {
            log.error("Jackson转换对象异常:{}", s, ex);
        }
        return null;
    }

    public static <T> T toObj(String s, TypeReference<T> t) {
        try {
            return OBJECT_MAPPER.readValue(s, t);
        } catch (Exception ex) {
            log.error("Jackson转换对象异常:{}", s, ex);
        }
        return null;
    }

    public static <T> List<T> toList(String s, Class<T> c) {
        if (CharSequenceUtil.isBlank(s)) {
            return List.of();
        }
        try {
            JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(List.class, c);
            return OBJECT_MAPPER.readValue(s, javaType);
        } catch (Exception ex) {
            log.error("Jackson转换List异常:{}", s, ex);
        }
        return List.of();
    }

    public static Map<String, Object> toMap(String s) {
        if (CharSequenceUtil.isBlank(s)) {
            return Map.of();
        }
        MapLikeType mapLikeType = OBJECT_MAPPER.getTypeFactory().constructMapLikeType(HashMap.class, String.class, Object.class);
        try {
            return OBJECT_MAPPER.readValue(s, mapLikeType);
        } catch (Exception ex) {
            log.error("Jackson转换Map异常:{}", s, ex);
        }
        return Map.of();
    }

}

posted @ 2025-08-21 10:47  PromiseForYou  阅读(33)  评论(0)    收藏  举报