特来电参数

This commit is contained in:
18631081161 2026-04-15 17:29:42 +08:00
parent ae071b8be6
commit 0a65290940
4 changed files with 116 additions and 36 deletions

View File

@ -0,0 +1,30 @@
| 【特来电】互联互通对接信息登记表 | | | | |
|------------------------|--------------------------------------------------|----------------------------------------------------------|---|---|
| | | | | |
| 类型 | 测试环境 | | | |
| 组织机构代码(营业执照) | 395815801 | 平台对接的运营商IDOperatorID | | |
| 企业名称: | 特来电 | | | |
| 运营商秘钥OperatorSecret | 1234567890abcdef | 第三方调用特来电密钥 | | |
| 签名秘钥SigSecret | 1234567890abcdef | | | |
| 数据加密秘钥DataSecret | 1234567890abcdef | | | |
| 初始化向量DataSecretIV | 1234567890abcdef | | | |
| 测试环境互联互通url地址 | http://hlht.teld9.xyz/evcs/fv2016/Teld/商户组织机构代码/ | | | |
| 其他: | | | | |
| | | | | |
| | | | | |
| 【第三方】互联互通对接信息登记表 | | | | |
| | | | | |
| 类型 | 测试环境 | | | |
| 组织机构代码(营业执照) | | 用于创建运营商ID是9位组织机构代码三证合一后18位的统一社会信用代码的第9~17位与生产环境保持一致。 | | |
| 企业名称: | | | | |
| 运营商秘钥OperatorSecret | | 特来电调用第三方密钥 | | |
| 签名秘钥SigSecret | | | | |
| 数据加密秘钥DataSecret | | | | |
| 初始化向量DataSecretIV | | | | |
| 测试环境互联互通url地址 | | | | |
| 其他: | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |

View File

@ -7,6 +7,11 @@ namespace HuangyanParking.Api.Controllers;
/// <summary>
/// 特来电回调控制器
/// 接收特来电推送的充电订单和设备状态
///
/// 密钥说明(互联互通标准双向密钥):
/// - DataSecret/SigSecret我方调特来电时用的密钥特来电提供
/// - PeerDataSecret/PeerSigSecret特来电调我方时用的密钥我方提供
/// 本控制器处理的是特来电调我方所以用Peer系列密钥
/// </summary>
[ApiController]
[Route("api/callback/tld")]
@ -31,7 +36,6 @@ public class TldCallbackController : ControllerBase
/// <summary>
/// 充电订单推送回调
/// 特来电接口名notification_charge_order_info
/// POST /api/callback/tld/notification_charge_order_info
/// </summary>
[HttpPost("notification_charge_order_info")]
@ -45,7 +49,6 @@ public class TldCallbackController : ControllerBase
/// <summary>
/// 设备状态变化推送回调
/// 特来电接口名notification_stationStatus
/// POST /api/callback/tld/notification_stationStatus
/// </summary>
[HttpPost("notification_stationStatus")]
@ -57,22 +60,35 @@ public class TldCallbackController : ControllerBase
/// <summary>
/// 平台认证接口特来电调用我方获取Token
/// 特来电接口名query_token
/// POST /api/callback/tld/query_token
/// </summary>
[HttpPost("query_token")]
public ActionResult<TldResponse> QueryToken([FromBody] TldRequest request)
{
var tldConfig = _configuration.GetSection("Tld");
var dataSecret = tldConfig["DataSecret"]!;
var dataSecretIV = tldConfig["DataSecretIV"]!;
var sigSecret = tldConfig["SigSecret"]!;
// 特来电调我方,用我方密钥解密
var peerDataSecret = tldConfig["PeerDataSecret"]!;
var peerDataSecretIV = tldConfig["PeerDataSecretIV"]!;
var peerSigSecret = tldConfig["PeerSigSecret"]!;
// 验签
if (!string.IsNullOrEmpty(request.Sig))
{
var sigValid = _tldCrypto.Verify(
request.OperatorID, request.Data, request.TimeStamp, request.Seq,
request.Sig, peerSigSecret);
if (!sigValid)
{
_logger.LogWarning("特来电Token请求验签失败");
return Ok(new TldResponse { Ret = 4001, Msg = "签名错误" });
}
}
// 解密请求
string decryptedData;
try
{
decryptedData = _tldCrypto.Decrypt(request.Data, dataSecret, dataSecretIV);
decryptedData = _tldCrypto.Decrypt(request.Data, peerDataSecret, peerDataSecretIV);
}
catch (Exception ex)
{
@ -85,7 +101,7 @@ public class TldCallbackController : ControllerBase
var tokenRequest = JsonSerializer.Deserialize<TldTokenRequest>(decryptedData);
var expectedSecret = tldConfig["PeerOperatorSecret"];
// 验证对方的OperatorSecret(如果配置了的话)
// 验证对方的OperatorSecret
if (!string.IsNullOrEmpty(expectedSecret) &&
tokenRequest?.OperatorSecret != expectedSecret)
{
@ -93,12 +109,12 @@ public class TldCallbackController : ControllerBase
{
OperatorID = request.OperatorID,
SuccStat = 1,
FailReason = 2 // 密钥错误
FailReason = 2
};
return Ok(BuildEncryptedResponse(failResult));
}
// 生成Token使用简单的GUID实际可用JWT
// 生成Token
var token = request.OperatorID + DateTime.Now.ToString("yyyyMMddHHmmss") +
Guid.NewGuid().ToString("N")[..32];
var result = new TldTokenResult
@ -113,19 +129,22 @@ public class TldCallbackController : ControllerBase
return Ok(BuildEncryptedResponse(result));
}
/// <summary>构建加密响应加密Data + 响应签名)</summary>
/// <summary>
/// 构建加密响应
/// 响应给特来电时,用我方密钥加密和签名
/// </summary>
private TldResponse BuildEncryptedResponse<T>(T data)
{
var tldConfig = _configuration.GetSection("Tld");
var dataSecret = tldConfig["DataSecret"]!;
var dataSecretIV = tldConfig["DataSecretIV"]!;
var sigSecret = tldConfig["SigSecret"]!;
var peerDataSecret = tldConfig["PeerDataSecret"]!;
var peerDataSecretIV = tldConfig["PeerDataSecretIV"]!;
var peerSigSecret = tldConfig["PeerSigSecret"]!;
var responseJson = JsonSerializer.Serialize(data);
var encryptedData = _tldCrypto.Encrypt(responseJson, dataSecret, dataSecretIV);
var encryptedData = _tldCrypto.Encrypt(responseJson, peerDataSecret, peerDataSecretIV);
// 响应签名拼接顺序Ret + Msg + Data
var sig = _tldCrypto.SignResponse(0, "", encryptedData, sigSecret);
var sig = _tldCrypto.SignResponse(0, "", encryptedData, peerSigSecret);
return new TldResponse
{

View File

@ -14,12 +14,17 @@
"AppSecret": "c227aad12a3fbd0d5413759424124150"
},
"Tld": {
"BaseUrl": "https://your-tld-api-url",
"OperatorID": "your_operator_id",
"OperatorSecret": "your_operator_secret",
"DataSecret": "your_data_secret",
"DataSecretIV": "your_data_secret_iv",
"SigSecret": "your_sig_secret"
"BaseUrl": "http://hlht.teld9.xyz/evcs/fv2016/Teld/MA2DT09B8",
"OperatorID": "MA2DT09B8",
"OperatorSecret": "1234567890abcdef",
"DataSecret": "1234567890abcdef",
"DataSecretIV": "1234567890abcdef",
"SigSecret": "1234567890abcdef",
"TldOperatorID": "395815801",
"PeerOperatorSecret": "WUscAwtCHz5Uogqg",
"PeerDataSecret": "5uBBwJjf71uboUq8",
"PeerDataSecretIV": "5qCP6f5Jbx7J8Ird",
"PeerSigSecret": "UEVo9ruFt8aPSt73"
},
"Ygl": {
"BaseUrl": "https://api-test.1kmxc.com",

View File

@ -29,6 +29,10 @@ public class TldService : ITldService
private readonly string _dataSecret;
private readonly string _dataSecretIV;
private readonly string _sigSecret;
// 我方密钥(特来电调用我方时使用)
private readonly string _peerDataSecret;
private readonly string _peerDataSecretIV;
private readonly string _peerSigSecret;
private const string TokenCacheKey = "tld:access_token";
@ -55,6 +59,9 @@ public class TldService : ITldService
_dataSecret = tldConfig["DataSecret"]!;
_dataSecretIV = tldConfig["DataSecretIV"]!;
_sigSecret = tldConfig["SigSecret"]!;
_peerDataSecret = tldConfig["PeerDataSecret"]!;
_peerDataSecretIV = tldConfig["PeerDataSecretIV"]!;
_peerSigSecret = tldConfig["PeerSigSecret"]!;
}
/// <summary>平台认证获取Token带Redis缓存</summary>
@ -122,12 +129,12 @@ public class TldService : ITldService
/// </summary>
public async Task<TldChargeOrderResponse> HandleChargeOrderAsync(TldRequest request)
{
// 1. 验签
// 1. 验签特来电用我方密钥签名所以用PeerSigSecret验签
if (!string.IsNullOrEmpty(request.Sig))
{
var isValid = _crypto.Verify(
request.OperatorID, request.Data, request.TimeStamp, request.Seq,
request.Sig, _sigSecret);
request.Sig, _peerSigSecret);
if (!isValid)
{
@ -136,8 +143,8 @@ public class TldService : ITldService
}
}
// 2. 解密订单数据
var decryptedData = _crypto.Decrypt(request.Data, _dataSecret, _dataSecretIV);
// 2. 解密订单数据特来电用我方密钥加密所以用PeerDataSecret解密
var decryptedData = _crypto.Decrypt(request.Data, _peerDataSecret, _peerDataSecretIV);
_logger.LogInformation("特来电充电订单推送: {Data}", decryptedData);
var order = JsonSerializer.Deserialize<TldChargeOrder>(decryptedData);
@ -228,13 +235,32 @@ public class TldService : ITldService
};
}
// 6. 发放积分
await _pointsService.GrantPointsAsync(
userId.Value, points,
$"充电积分:消费{order.TotalMoney:F2}元",
tldOrderId: order.StartChargeSeq);
// 6. 发放积分(使用事务确保一致性)
await using var transaction = await _db.Database.BeginTransactionAsync();
try
{
await _pointsService.GrantPointsAsync(
userId.Value, points,
$"充电积分:消费{order.TotalMoney:F2}元",
tldOrderId: order.StartChargeSeq);
await _db.SaveChangesAsync();
// 保存充电记录的更新(如果有)
if (chargeRecord is not null)
await _db.SaveChangesAsync();
await transaction.CommitAsync();
}
catch (Exception ex)
{
await transaction.RollbackAsync();
_logger.LogError(ex, "特来电订单积分发放失败: OrderId={OrderId}", order.StartChargeSeq);
return new TldChargeOrderResponse
{
StartChargeSeq = order.StartChargeSeq,
ConnectorID = order.ConnectorID,
ConfirmResult = 1
};
}
_logger.LogInformation("特来电订单积分发放完成: OrderId={OrderId}, UserId={UserId}, Points={Points}",
order.StartChargeSeq, userId.Value, points);
@ -254,12 +280,12 @@ public class TldService : ITldService
/// </summary>
public async Task<TldStationStatusCallbackResponse> HandleStationStatusAsync(TldRequest request)
{
// 验签
// 验签(特来电用我方密钥签名)
if (!string.IsNullOrEmpty(request.Sig))
{
var isValid = _crypto.Verify(
request.OperatorID, request.Data, request.TimeStamp, request.Seq,
request.Sig, _sigSecret);
request.Sig, _peerSigSecret);
if (!isValid)
{
@ -268,8 +294,8 @@ public class TldService : ITldService
}
}
// 解密并缓存到Redis(供前端查询设备状态)
var decryptedData = _crypto.Decrypt(request.Data, _dataSecret, _dataSecretIV);
// 解密(特来电用我方密钥加密)并缓存到Redis
var decryptedData = _crypto.Decrypt(request.Data, _peerDataSecret, _peerDataSecretIV);
_logger.LogDebug("特来电设备状态变化: {Data}", decryptedData);
try