高德充电设备状态推送接口实现文档
一、接口基本信息
- 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();
}
}