国内充电运营平台接入第三方聚合平台、城市监管平台或其他运营商时,常会采用中电联充换电服务信息交换相关规范。它解决的不是充电桩与平台之间的通信,而是平台与平台之间如何交换场站、设备、状态和充电订单等信息。
这类项目真正困难的部分通常不是发送一个 HTTP 请求,而是统一协议版本、处理加密签名、建立可靠的业务状态机,并在双方数据口径不一致时保证订单可追溯。本文从工程落地角度给出一套实现方案。文中的地址、运营商编号和业务数据均为演示内容;不同标准版本、地方平台和运营商可能存在扩展,生产接入应以双方确认的接口清单和联调文档为准。
一套典型的充电业务会涉及三层通信:
充电桩 <--设备协议--> 运营商平台 <--中电联信息交换协议--> 对接平台
设备协议负责启停充电、实时遥测和故障上报;中电联信息交换协议负责平台间的数据共享与业务协同。对接平台发起启动请求后,运营商平台仍需把请求转换成设备协议命令,再等待充电桩执行。因此,“接口受理成功”和“充电已经启动”是两个不同状态。
落地前应由双方冻结一份接入基线,至少包括:
项目 | 需要确认的内容 |
|---|---|
协议版本 | 标准版本、地方扩展版本、修订日期 |
运营商身份 |
|
接口范围 | 基础信息、状态、鉴权、启停、订单等 |
安全参数 | 签名密钥、数据密钥、初始向量及换钥方式 |
字段口径 | 时间、金额、电量、枪口编号、状态枚举 |
回调策略 | 地址、超时、重试次数、幂等规则 |
不要只写“按中电联协议接入”。同一接口在不同项目中可能存在字段增减、路径差异或枚举扩展,版本基线必须能够落到具体文档和联调样例。
建议在现有充电业务之外增加独立的互联互通网关:
对接平台 | v 接入网关:验签、解密、限流、审计 | v 协议适配层:字段转换、版本适配、幂等控制 | +--> 基础数据服务:场站、设备、价格 +--> 状态服务:空闲、充电、离线、故障 +--> 充电服务:鉴权、启动、停止、订单 +--> 回调任务:结果通知、状态通知、订单通知
接入网关不直接操作充电桩。它先完成安全校验,再把标准报文转换成内部命令。这样既能隔离外部协议变化,也能避免在核心充电服务中散落大量外部字段判断。
每个合作方应维护独立配置,包括协议版本、密钥版本、回调地址、超时时间、启用接口和扩展字段。不要把合作方差异写成遍布代码的 if/else。
常见协议实现会在 JSON 外层携带运营商标识、加密数据、时间戳、序列号和签名,结构类似:
{
"OperatorID": "1000000001",
"Data": "Base64EncodedCipherText",
"TimeStamp": "20260923153045",
"Seq": "0001",
"Sig": "RequestSignature"}其中 Data 是业务 JSON 加密后的 Base64 文本,Sig 用于验证请求来源和报文完整性。工程上应把处理顺序固定下来:
校验报文长度和必填字段 -> 根据 OperatorID 加载协议配置与密钥版本 -> 校验时间窗口和 Seq -> 按约定的原始字符串计算并校验 Sig -> Base64 解码并解密 Data -> 解析业务 JSON -> 执行业务幂等检查
签名串的字段顺序、字符编码、大小写以及加密算法参数必须严格按双方采用的协议版本实现。常见项目会使用共享密钥签名以及对称加密,但不能仅凭经验猜测算法、填充方式或初始向量。
建议使用成熟密码库,并注意以下细节:
明确使用 UTF-8,禁止依赖操作系统默认编码;
签名使用协议要求的原始值,不对 Data 做二次 Base64 或格式化;
使用常量时间比较方法校验签名,减少时序差异;
密钥放入密钥管理服务或受控配置中心,日志中不得输出明文密钥、解密后的用户标识或完整凭证;
支持密钥版本和灰度换钥,在约定窗口内兼容新旧密钥;
对时间戳设置合理窗口,并以 OperatorID + TimeStamp + Seq 等组合进行防重放控制。
下面的 Java 伪代码强调处理边界,具体算法名称和签名拼接规则应来自已确认的协议版本:
public BusinessRequest decode(Envelope envelope) {
PartnerConfig config = configService.require(envelope.operatorId());
validateTimestamp(envelope.timeStamp(), config.allowedClockSkew());
replayGuard.check(envelope.operatorId(), envelope.timeStamp(), envelope.seq());
String signSource = config.signRule().build(envelope);
byte[] expected = signer.sign(signSource, config.signSecret());
if (!MessageDigest.isEqual(expected, decodeSignature(envelope.sig()))) {
throw new ProtocolException("INVALID_SIGNATURE");
}
byte[] cipherText = Base64.getDecoder().decode(envelope.data());
byte[] plainText = cipher.decrypt(cipherText, config.dataKey(), config.iv());
return json.readValue(plainText, BusinessRequest.class);
}响应也要经过相同版本的封装流程。即使业务失败,也应返回协议约定的结果码和加密数据,不能把内部异常堆栈直接暴露给调用方。
互联互通接口可以按数据变化频率分成三组。
场站、充电设备、充电接口和计费规则变化较慢,适合“全量初始化 + 增量更新 + 定期校准”。首次接入先完成分页全量同步,此后由变更通知驱动更新,每天再执行一次差异校准。
核心关联关系建议保留为:
运营商 OperatorID -> 场站 StationID -> 设备 EquipmentID -> 充电接口 ConnectorID
外部 ID 应按字符串保存,不要转换成整数。数据库唯一键至少包含合作方和外部 ID,例如 (partner_id, connector_id),因为不同平台的编号空间未必全局唯一。
计费规则应按版本保存生效区间,不要覆盖历史记录。一笔订单最终使用哪个价格版本,应由启动时间和双方约定的计费规则确定,以便后续账单复核。
枪口状态变化频繁,通常采用主动通知,查询接口用于补偿。平台接到状态通知后,应比较事件时间或内部版本号,防止网络延迟导致旧状态覆盖新状态。
内部状态不要直接等同于某一家平台的枚举。可以先归一化为:
UNKNOWN、OFFLINE、IDLE、OCCUPIED、CHARGING、FAULT、RESERVED
再通过版本适配器映射到对方协议值。对于无法精确映射的状态,按双方约定降级,并保留原始状态码用于排查。
充电业务通常包含设备鉴权、启动充电、停止充电、启动结果通知、停止结果通知和订单信息通知。它是异步流程,不适合用一次同步请求表达最终结果。
启动充电可以设计为以下状态机:
CREATED -> ACCEPTED -> COMMAND_SENT -> STARTED -> CHARGING -> STOPPING -> FINISHED异常分支:REJECTED、START_FAILED、TIMEOUT、CANCELLED
对接平台提交启动请求后,运营商平台完成参数与幂等校验,只返回“已受理”或“拒绝受理”。随后平台向设备下发启动命令,并通过结果通知告知最终启动结果。
一条请求至少应保留这些关联字段:
字段 | 用途 |
|---|---|
合作方请求号 | 接口幂等与问题定位 |
运营商订单号 | 运营商内部业务主键 |
对接平台订单号 | 双方订单关联 |
| 目标充电接口 |
设备交易号 | 关联桩侧交易 |
当前状态与版本 | 防止乱序更新 |
原始请求摘要 | 审计与重复请求比对 |
幂等不能只根据“接口在几秒内重复”判断。相同业务请求号和相同业务内容应返回第一次处理结果;相同请求号但业务内容不同,应拒绝并记录冲突。回调也要携带稳定的事件 ID 或由业务主键、事件类型和状态版本生成幂等键。
生产环境中必须假设回调会超时、重复、乱序和短暂失败。可靠通知可以使用事务消息表或 Outbox 模式:业务状态与待发送事件在同一数据库事务中落库,再由独立任务投递。
CREATE TABLE interop_outbox ( id BIGINT PRIMARY KEY, partner_id VARCHAR(32) NOT NULL, event_type VARCHAR(48) NOT NULL, business_key VARCHAR(96) NOT NULL, state_version BIGINT NOT NULL, payload_json TEXT NOT NULL, status VARCHAR(16) NOT NULL, retry_count INT NOT NULL, next_retry_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL, UNIQUE (partner_id, event_type, business_key, state_version) );
仅在收到对方明确的成功响应后,任务才进入 SUCCESS。网络超时不能解释成业务失败,因为对方可能已经处理成功。重试采用指数退避并设置上限,超过上限进入人工处理队列;不能无限快速重试。
建议分别记录三种时间:业务事件发生时间、平台收到事件时间、通知成功时间。这样才能识别设备延迟、内部积压和外部接口延迟。
订单是双方最容易产生分歧的数据。平台应同时保留原始订单、归一化订单和计费明细,不要只保存一个最终金额。
金额优先使用最小货币单位的整数,电量使用 BigDecimal 并明确精度和舍入规则。例如:
record ChargeOrder(
String startChargeSeq,
String connectorId,
Instant startTime,
Instant endTime,
BigDecimal totalPower,
long electricityFeeFen,
long serviceFeeFen,
long totalFeeFen
) {}入库前至少校验:
结束时间不早于开始时间;
总电量不为负数,并与分时电量之和在约定误差内一致;
总金额与电费、服务费及其他约定费用之和一致;
订单中的枪口、设备和场站关系能够匹配;
同一订单的后续版本只能按状态机前进,不能覆盖已经确认的终态。
若双方因精度、计费时点或价格版本产生差异,应进入对账异常表,保留双方原值和差异原因,不能直接修改原始报文“抹平”差额。
协议错误和业务错误应分开处理。签名错误、解密失败、JSON 格式错误属于协议层错误;枪口不存在、设备离线、订单已结束属于业务层错误。内部异常先映射成稳定的对外错误码,再记录内部错误详情。
日志建议包含:
traceId、partnerId、interfaceName、requestId、businessKey、 timestamp、duration、resultCode、retryCount、payloadHash
手机号、车牌、用户令牌、密钥和解密后的完整业务报文不应写入普通日志。确需保存原始报文时,应加密存储、限制访问并设置保留期限。
监控至少覆盖接口成功率、签名失败数、解密失败数、时间戳超限数、回调积压量、重试次数、启动成功率、启动耗时、状态延迟和订单差异数。按合作方和接口分别统计,整体平均值往往会掩盖单一通道故障。
上线前建议按以下顺序验证:
用双方确认的固定报文完成加密、解密和签名互验;
同步一个测试场站,核对场站、设备、枪口和价格层级;
验证状态通知的重复、乱序、离线恢复和补偿查询;
完成启动、启动失败、主动停止、设备停止和超时等完整流程;
核对跨时段计费、零电量订单、异常结束和重复订单;
模拟回调超时、返回失败和网络恢复,确认重试不会制造重复业务;
检查日志脱敏、密钥权限、限流和告警是否生效。
正式切换时可以先开放少量测试枪,再逐步扩大场站范围。双方应约定故障联系人、问题报文留存方式以及状态和订单的补偿时间窗口。
中电联协议的互联互通实现,本质上是一个跨平台的分布式业务系统。HTTP、JSON、加密和签名只是入口,真正决定稳定性的,是清晰的版本基线、可靠的异步状态机、严格的幂等设计以及可复核的订单数据。
一个可维护的实现应把安全封装、协议适配和内部业务分层;基础信息采用全量与增量结合,状态通知配合查询补偿,启停充电采用异步结果通知,订单保留原文与版本。只有把超时、重复、乱序和差异对账当作正常情况设计,平台间互联互通才能从“接口调通”走向长期稳定运行。
说明:本文用于介绍通用工程方法,不替代标准原文、双方签署的接口文档及安全规范。示例标识、地址和数据均为虚构内容。
关注
了解更多资讯