完整的 API 接口文档,涵盖安全鉴权、签名生成及核心接口调用示例。
companyCode:
企业唯一识别码(用于请求头身份标识)
key:
接口签名密钥(仅用于本地签名生成,严禁在网络中明文传输)
time 和 sign)的参数名,按 Unicode 编码升序 排列,拼接为 key=value&key=value 格式。
time={UTC+0秒级时间戳}&key={分配的密钥}。
aiMode=0&batRatedCapacity=1&batRatedChargingPower=1000&customTimes=00:00,12:00,1000&13:00,15:00,-2000&dataTime=2025-06-26&energyMode=2&priceCompany=Germany&time=1732756652&key=2a1891544dbcf8e8b45b36d03187485a
time 和 sign 字段。
{
"energyMode": "2",
"aiMode": "0",
"customTimes": "00:00,12:00,1000&13:00,15:00,-2000",
"batRatedCapacity": "1",
"batRatedChargingPower": "1000",
"dataTime": "2025-06-26",
"priceCompany": "Germany"
}
aiMode, batRatedCapacity, batRatedChargingPower, customTimes, dataTime, energyMode, priceCompany
key=value 格式,用 & 连接。
aiMode=0&batRatedCapacity=1&batRatedChargingPower=1000&customTimes=00:00,12:00,1000&13:00,15:00,-2000&dataTime=2025-06-26&energyMode=2&priceCompany=Germany
time 和 key。
aiMode=0&batRatedCapacity=1&batRatedChargingPower=1000&customTimes=00:00,12:00,1000&13:00,15:00,-2000&dataTime=2025-06-26&energyMode=2&priceCompany=Germany&time=1732756652&key=2a1891544dbcf8e8b45b36d03187485a
status=&time=...&key=...)sign 参数值。
import java.security.MessageDigest;
import java.util.TreeMap;
public class SignGenerator {
public static String generateSign(TreeMap params, String key) {
try {
StringBuilder sb = new StringBuilder();
// Concatenate business parameters
for (String paramKey : params.keySet()) {
if (!paramKey.equals("sign")) {
sb.append(paramKey).append("=").append(params.get(paramKey)).append("&");
}
}
// Append time and key
sb.append("time=").append(params.get("time")).append("&key=").append(key);
// MD5 encrypt and convert to lowercase
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
StringBuilder hexString = new StringBuilder();
for (byte b : digest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString().toLowerCase();
} catch (Exception e) {
throw new RuntimeException("Signature generation failed", e);
}
}
}
import hashlib
from urllib.parse import urlencode
def generate_sign(params, key):
"""
Generate API signature
:param params: Business parameter dict (contains time)
:param key: Assigned key
:return: MD5 signature string (lowercase)
"""
# Exclude sign field, sort by key
sorted_params = sorted([(k, v) for k, v in params.items() if k != 'sign'])
# Concatenate parameters
param_str = '&'.join([f"{k}={v}" for k, v in sorted_params])
# Append time and key
sign_str = f"{param_str}&time={params['time']}&key={key}"
# MD5 encrypt and convert to lowercase
return hashlib.md5(sign_str.encode('utf-8')).hexdigest().lower()
# Usage example
params = {
'energyMode': '2',
'aiMode': '0',
'customTimes': '00:00,12:00,1000&13:00,15:00,-2000',
'batRatedCapacity': '1',
'batRatedChargingPower': '1000',
'dataTime': '2025-06-26',
'priceCompany': 'Germany',
'time': '1732756652'
}
sign = generate_sign(params, '2a1891544dbcf8e8b45b36d03187485a')
print(f"Generated signature: {sign}")
const crypto = require('crypto');
function generateSign(params, key) {
// Get sorted parameter keys (exclude sign)
const sortedKeys = Object.keys(params)
.filter(k => k !== 'sign')
.sort();
// Concatenate parameter string
const paramString = sortedKeys
.map(k => `=`)
.join('&');
// Append time and key
const signString = `&time=&key=`;
// MD5 encrypt and convert to lowercase
return crypto.createHash('md5')
.update(signString, 'utf8')
.digest('hex')
.toLowerCase();
}
// Usage example
const params = {
energyMode: '2',
aiMode: '0',
customTimes: '00:00,12:00,1000&13:00,15:00,-2000',
batRatedCapacity: '1',
batRatedChargingPower: '1000',
dataTime: '2025-06-26',
priceCompany: 'Germany',
time: '1732756652'
};
const sign = generateSign(params, '2a1891544dbcf8e8b45b36d03187485a');
console.log('Generated signature:', sign);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public class SignGenerator
{
public static string GenerateSign(Dictionary parameters, string key)
{
// Exclude sign field, sort by key
var sortedParams = parameters
.Where(p => p.Key != "sign")
.OrderBy(p => p.Key, StringComparer.Ordinal)
.Select(p => $"{p.Key}={p.Value}");
// Concatenate parameters
string paramString = string.Join("&", sortedParams);
// Append time and key
string signString = $"{paramString}&time={parameters["time"]}&key={key}";
// MD5 encrypt and convert to lowercase
using (var md5 = MD5.Create())
{
byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(signString));
StringBuilder sb = new StringBuilder();
foreach (byte b in hashBytes)
{
sb.Append(b.ToString("x2")); // Convert to lowercase hex
}
return sb.ToString();
}
}
}
companyCode: {企业识别码} 与 Accept-Language: en-US。
time(与签名生成时完全一致的 UTC+0 秒级时间戳)和 sign(MD5 签名值)。
curl --location 'https://api.aecc.com/openApi/price/setEnergyMode' \
--header 'companyCode: AECC2024001' \
--header 'Accept-Language: en-US' \
--header 'Content-Type: application/json' \
--data '{
"energyMode": "2",
"aiMode": "0",
"customTimes": "00:00,12:00,1000&13:00,15:00,-2000",
"batRatedCapacity": "1",
"batRatedChargingPower": "1000",
"dataTime": "2025-06-26",
"priceCompany": "Germany",
"time": "1732756652",
"sign": "c3757db87150d5efbb45009d9253d375"
}'
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class ApiClient {
private static final String API_URL = "https://api.aecc.com/openApi/price/setEnergyMode";
private static final String COMPANY_CODE = "AECC2024001";
private static final String KEY = "2a1891544dbcf8e8b45b36d03187485a";
public static void main(String[] args) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(API_URL);
// Set headers
httpPost.setHeader("companyCode", COMPANY_CODE);
httpPost.setHeader("Accept-Language", "en-US");
httpPost.setHeader("Content-Type", "application/json");
// Prepare business parameters
long currentTime = System.currentTimeMillis() / 1000; // UTC+0 second timestamp
TreeMap params = new TreeMap<>();
params.put("energyMode", "2");
params.put("aiMode", "0");
params.put("customTimes", "00:00,12:00,1000&13:00,15:00,-2000");
params.put("batRatedCapacity", "1");
params.put("batRatedChargingPower", "1000");
params.put("dataTime", "2025-06-26");
params.put("priceCompany", "Germany");
params.put("time", String.valueOf(currentTime));
// Generate signature
String sign = SignGenerator.generateSign(params, KEY);
params.put("sign", sign);
// Construct JSON request body
JSONObject requestBody = new JSONObject();
for (String key : params.keySet()) {
requestBody.put(key, params.get(key));
}
httpPost.setEntity(new StringEntity(requestBody.toString(), "UTF-8"));
// Send request
String response = httpClient.execute(httpPost, response -> {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
System.out.println("Status code: " + statusCode);
return responseBody;
});
System.out.println("Response: " + response);
httpClient.close();
}
}
companyCode 查询对应的企业密钥time 参数是否在允许的时间范围内(默认±5 分钟),防止重放攻击sign 值,完全一致方通过验证| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| companyCode | header | string | 是 | 唯一码 |
| Accept-Language | header | string | 是 | en-US |
| time | body | string | 是 | 秒级时间戳(UTC+0) |
| sign | body | string | 是 | 签名信息 |
| priceCompany | body | string | 是 | 电价区域(德国="Germany") |
| batRatedCapacity | body | string | 是 | 电池额定充满电量(kWh),0~100,充满需要消耗的能量 |
| batRatedChargingPower | body | string | 是 | 电池额定充电功率(W),根据电池充电功率和容量计算充电时间来选择最低波谷时间 |
| dataTime | body | string | 是 | 当天日期(yyyy-MM-dd) |
| energyMode | body | int | 是 | 能源模式:0=无模式,采集器不会对设备进行调控;1=智能模式;2=自定义模式 |
| aiMode | body | int | 否 | AI调控使能:0=关闭,1=开启,选择智能模式可设置此值,不传默认关闭 |
| customTimes | body | string | 否 | 自定义时间段:用户自定义设置的充放电时间段,多个时间段使用&隔开,最多16个,格式如:00:00,12:00,1000&13:00,15:00,-2000 |
{
"energyMode": "2",
"aiMode": "0",
"customTimes": "00:00,12:00,1000&13:00,15:00,-2000",
"batRatedCapacity": "1",
"batRatedChargingPower": "1000",
"dataTime": "2025-06-26",
"priceCompany": "Germany",
"time": "1732756652",
"sign": "c3757db87150d5efbb45009d9253d375"
}
packet:
十六进制控制报文,需按设备协议二次加密并计算 CRC16 校验后下发至采集器。
powerTimes:
时段策略数组,包含各时间段的充放电功率指令。
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| companyCode | header | string | 是 | 唯一码 |
| Accept-Language | header | string | 是 | en-US |
| dataTime | body | string | 是 | 日期(yyyy-MM-dd) |
| priceCompany | body | string | 是 | 电价区域(如:Germany, France) |
| mode | body | string | 否 | 电价颗粒度:0=1小时,1=15分钟,默认0 |
| time | body | string | 是 | 时间戳(UTC+0) |
| sign | body | string | 是 | 签名信息 |
{
"dataTime": "2024-09-07",
"priceCompany": "Germany",
"mode": "0",
"time": "1725677116",
"sign": "e07b26034722d166e7f059cb728ab3fd"
}
priceArr:
24小时电价数组,单位为 EUR/MWh。
pricesDayList:
时段明细列表,包含每个时间段的起止时间、电价数值及峰谷平标识。
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| companyCode | header | string | 是 | 唯一码 |
| Accept-Language | header | string | 是 | en-US |
| deviceSn | body | string | 是 | 设备序列号 |
| time | body | string | 是 | 秒级时间戳(UTC+0) |
| sign | body | string | 是 | 签名信息 |
{
"deviceSn": "NB2548300T110CHAB",
"time": "1725450897",
"sign": "e83ba9c021edd831ae69033f77528ac5"
}
obj:
设备额定数据模型。
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| companyCode | header | string | 是 | 唯一码 |
| Accept-Language | header | string | 是 | en-US |
| deviceSn | body | string | 是 | 设备序列号 |
| time | body | string | 是 | 秒级时间戳(UTC+0) |
| sign | body | string | 是 | 签名信息 |
{
"deviceSn": "NB2548300T110CHAB",
"time": "1725450897",
"sign": "e83ba9c021edd831ae69033f77528ac5"
}
obj:
设备实时数据模型。
服务端在收到请求后会按相同规则(业务参数按 Unicode 升序拼接 → 末尾追加 time 和 key → MD5 → 转小写)重算签名,并与请求体中的 sign 做精确比较。任一环节不一致都会判定为签名异常,统一返回 result=10001, msg="Signature exception"。
排查步骤(按命中率从高到低):
result=10001, msg="Signature exception"。时间偏差处理建议:
不可以。
&key=xxx),其本身不作为请求参数、不放在 header、不放在 URL query。--header 'companyCode: AECC2024001'。key 仅在本地代码中参与 MD5,不进入任何请求字段。安全要求:密钥应存储在服务器端配置文件或环境变量中,禁止硬编码到客户端、禁止打印到日志、禁止随请求外发。一旦怀疑泄露,立即联系官方重置。
开放接口的 HTTP 层基本固定返回 200,业务结果通过响应体的 result 字段表达。真正的系统级异常会统一映射为 result=4000, msg="System error, please contact the administrator!"。
说明:响应体中的状态字段实际命名为 result(而非 code),下文为便于理解统一按"状态码"描述。
响应结构:
{
"result": 0,
"msg": "Request successfully.",
"data": { /* business data */ }
}
result: 状态码,0 表示成功,非 0 表示失败。msg: 状态描述文本,支持国际化(i18n),语言由请求头 Accept-Language 决定(如 en-US、zh-CN),未传时默认 en-US。data/obj: 业务数据载体,成功时返回具体业务对象。| result | 含义 | 典型 msg(en-US) | 触发场景 |
|---|---|---|---|
| 0 | 成功 | Request successfully. | 业务正常处理完成 |
| 1 | 通用失败 | (varies) | 业务逻辑校验未通过,msg 会具体说明 |
| 10000 | 未登录/Token失效 | Please login. / token expired | Token 模式下 claims 无效或过期 |
| 10001 | 签名异常 | Signature exception | 签名错误、时间戳过期、companyCode/time/sign 缺失(合并码) |
| 10002 | 邮箱格式错误 | Incorrect email format. | 注册/绑定邮箱格式不合法 |
| 10006 | 用户被禁用 | The user has been disabled. | 账号被锁定 |
| 20000 | 参数类型有误 | Incorrect parameter type. | 请求参数类型不匹配 |
| 20001 | 数据不能为空 | Submitted data cannot be empty. | 必填参数缺失 |
| 20002 | 权限异常 | Abnormal permissions. | openApi 鉴权链路专用:companyCode 在系统中不存在、对应凭证未启用(flagState!=1)、或无操作权限 |
| 20003 | 时间格式错误 | Time format error. | 日期/时间参数格式不合法 |
| 20004 | 参数格式错误 | Parameter format error. | 参数格式校验未通过 |
| 20005 | 无此参数 | No this parameter. | 缺少必要的业务参数 |
| 20006 | 系统错误 | System error, try later. | 服务端业务异常 |
| 20007 | DTC不存在 | DeviceCode does not exist | 设备类型编码无效 |
| 20008 | 设备离线 | Device off-line | 设备未上报数据 |
| 20009 | 设备不存在 | Device not exist | deviceSn 在系统中查不到 |
| 4000 | 系统全局错误 | System error, please contact the administrator! | 未捕获异常、事务回滚、参数解析异常等(建议重试或联系官方) |