This commit is contained in:
@@ -204,7 +204,68 @@ components:
|
||||
description: 当前生效套餐已用流量(MB)
|
||||
type: integer
|
||||
card_no:
|
||||
description: 卡标识
|
||||
description: 卡标识(原始传入值)
|
||||
type: string
|
||||
device_no:
|
||||
description: 设备虚拟号(当入参被识别为设备维度时返回,表示该卡/标识关联的设备)
|
||||
type: string
|
||||
pending_packages:
|
||||
description: 待生效套餐列表
|
||||
items:
|
||||
$ref: '#/components/schemas/DtoAgentOpenAPIPackageTrafficItem'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
DtoAgentOpenAPIDeviceOperationRequest:
|
||||
properties:
|
||||
device_no:
|
||||
description: 设备标识(支持虚拟号、IMEI)
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- device_no
|
||||
type: object
|
||||
DtoAgentOpenAPIDeviceSwitchCardRequest:
|
||||
properties:
|
||||
device_no:
|
||||
description: 设备标识(支持虚拟号、IMEI)
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
iccid:
|
||||
description: 目标卡 ICCID
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- device_no
|
||||
- iccid
|
||||
type: object
|
||||
DtoAgentOpenAPIDeviceTrafficResponse:
|
||||
properties:
|
||||
active_expires_at:
|
||||
description: 当前生效套餐最晚过期时间
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
active_packages:
|
||||
description: 当前生效套餐列表
|
||||
items:
|
||||
$ref: '#/components/schemas/DtoAgentOpenAPIPackageTrafficItem'
|
||||
nullable: true
|
||||
type: array
|
||||
active_remaining_flow_mb:
|
||||
description: 当前生效套餐剩余流量(MB)
|
||||
type: integer
|
||||
active_total_flow_mb:
|
||||
description: 当前生效套餐总流量(MB)
|
||||
type: integer
|
||||
active_used_flow_mb:
|
||||
description: 当前生效套餐已用流量(MB)
|
||||
type: integer
|
||||
device_no:
|
||||
description: 设备标识
|
||||
type: string
|
||||
pending_packages:
|
||||
description: 待生效套餐列表
|
||||
@@ -1269,7 +1330,7 @@ components:
|
||||
minimum: 0
|
||||
type: integer
|
||||
paid_amount:
|
||||
description: 购买实付金额(分),线下支付或无订单分配时为空
|
||||
description: 购买成本价(分),仅平台账号可见,非平台账号不返回此字段
|
||||
nullable: true
|
||||
type: integer
|
||||
priority:
|
||||
@@ -1292,6 +1353,10 @@ components:
|
||||
refund_no:
|
||||
description: 退款单号快照(未退款时为空)
|
||||
type: string
|
||||
retail_amount:
|
||||
description: 购买零售价(分),无订单关联时为空
|
||||
nullable: true
|
||||
type: integer
|
||||
status:
|
||||
description: 状态:0待生效 1生效中 2已用完 3已过期 4已失效
|
||||
type: integer
|
||||
@@ -1679,7 +1744,7 @@ components:
|
||||
nullable: true
|
||||
type: string
|
||||
reference_type:
|
||||
description: 关联业务类型:recharge 或 order(可空)
|
||||
description: 关联业务类型:recharge、order 或 exchange(可空)
|
||||
nullable: true
|
||||
type: string
|
||||
remark:
|
||||
@@ -1687,10 +1752,10 @@ components:
|
||||
nullable: true
|
||||
type: string
|
||||
transaction_type:
|
||||
description: 交易类型:recharge/deduct/refund
|
||||
description: 交易类型:recharge/deduct/refund/exchange
|
||||
type: string
|
||||
transaction_type_text:
|
||||
description: 交易类型文本:充值/扣款/退款
|
||||
description: 交易类型文本:充值/扣款/退款/换货迁移
|
||||
type: string
|
||||
type: object
|
||||
DtoAssetWalletTransactionListResponse:
|
||||
@@ -2468,6 +2533,9 @@ components:
|
||||
exchange_reason:
|
||||
description: 换货原因
|
||||
type: string
|
||||
flow_type:
|
||||
description: 换货流程类型 (shipping:物流换货, direct:直接换货)
|
||||
type: string
|
||||
id:
|
||||
description: 换货单ID
|
||||
minimum: 0
|
||||
@@ -2475,6 +2543,9 @@ components:
|
||||
status:
|
||||
description: 换货状态 (1:待填写信息, 2:待发货, 3:已发货待确认, 4:已完成, 5:已取消)
|
||||
type: integer
|
||||
status_name:
|
||||
description: 换货状态名称(中文)
|
||||
type: string
|
||||
status_text:
|
||||
description: 换货状态文本
|
||||
type: string
|
||||
@@ -3148,6 +3219,21 @@ components:
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
flow_type:
|
||||
description: 换货流程类型 (shipping:物流换货, direct:直接换货)
|
||||
enum:
|
||||
- shipping
|
||||
- direct
|
||||
type: string
|
||||
migrate_data:
|
||||
description: 是否执行全量迁移,direct 流程未传按 false 处理
|
||||
nullable: true
|
||||
type: boolean
|
||||
new_identifier:
|
||||
description: 新资产标识符,direct 流程必填(ICCID/虚拟号/IMEI/SN)
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
old_asset_type:
|
||||
description: 旧资产类型 (iot_card:物联网卡, device:设备)
|
||||
type: string
|
||||
@@ -4895,6 +4981,11 @@ components:
|
||||
type: object
|
||||
DtoExchangeOrderResponse:
|
||||
properties:
|
||||
completed_at:
|
||||
description: 换货完成时间
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
created_at:
|
||||
description: 创建时间
|
||||
format: date-time
|
||||
@@ -4920,6 +5011,12 @@ components:
|
||||
express_no:
|
||||
description: 快递单号
|
||||
type: string
|
||||
flow_type:
|
||||
description: 换货流程类型 (shipping:物流换货, direct:直接换货)
|
||||
type: string
|
||||
flow_type_name:
|
||||
description: 换货流程类型名称
|
||||
type: string
|
||||
id:
|
||||
description: 换货单ID
|
||||
minimum: 0
|
||||
@@ -4967,6 +5064,11 @@ components:
|
||||
description: 备注
|
||||
nullable: true
|
||||
type: string
|
||||
shipped_at:
|
||||
description: 发货时间
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
shop_id:
|
||||
description: 所属店铺ID
|
||||
minimum: 0
|
||||
@@ -4975,6 +5077,9 @@ components:
|
||||
status:
|
||||
description: 换货状态 (1:待填写信息, 2:待发货, 3:已发货待确认, 4:已完成, 5:已取消)
|
||||
type: integer
|
||||
status_name:
|
||||
description: 换货状态名称(中文)
|
||||
type: string
|
||||
status_text:
|
||||
description: 换货状态文本
|
||||
type: string
|
||||
@@ -8118,6 +8223,24 @@ components:
|
||||
nullable: true
|
||||
type: string
|
||||
type: object
|
||||
DtoUpdateAssetPackageExpiresAtRequest:
|
||||
properties:
|
||||
expires_at:
|
||||
description: 新的套餐过期时间,格式:YYYY-MM-DD HH:MM:SS 或 RFC3339
|
||||
type: string
|
||||
required:
|
||||
- expires_at
|
||||
type: object
|
||||
DtoUpdateAssetPackageUsageRequest:
|
||||
properties:
|
||||
data_usage_mb:
|
||||
description: 新的套餐真实已用量(MB)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
required:
|
||||
- data_usage_mb
|
||||
type: object
|
||||
DtoUpdateAssetPollingStatusRequest:
|
||||
properties:
|
||||
enable_polling:
|
||||
@@ -10975,6 +11098,166 @@ paths:
|
||||
summary: 资产套餐列表
|
||||
tags:
|
||||
- 资产管理
|
||||
/api/admin/assets/{identifier}/packages/{package_usage_id}/expires-at:
|
||||
patch:
|
||||
description: 修改指定资产下某条套餐使用记录的过期时间。仅账号ID为2的账号可调用。
|
||||
parameters:
|
||||
- description: 资产标识符(虚拟号/ICCID/IMEI/SN/MSISDN)
|
||||
in: path
|
||||
name: identifier
|
||||
required: true
|
||||
schema:
|
||||
description: 资产标识符(虚拟号/ICCID/IMEI/SN/MSISDN)
|
||||
type: string
|
||||
- description: 套餐使用记录ID
|
||||
in: path
|
||||
name: package_usage_id
|
||||
required: true
|
||||
schema:
|
||||
description: 套餐使用记录ID
|
||||
minimum: 0
|
||||
type: integer
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DtoUpdateAssetPackageExpiresAtRequest'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
code:
|
||||
description: 响应码
|
||||
example: 0
|
||||
type: integer
|
||||
data:
|
||||
$ref: '#/components/schemas/DtoAssetPackageResponse'
|
||||
msg:
|
||||
description: 响应消息
|
||||
example: success
|
||||
type: string
|
||||
timestamp:
|
||||
description: 时间戳
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- code
|
||||
- msg
|
||||
- data
|
||||
- timestamp
|
||||
type: object
|
||||
description: 成功
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 请求参数错误
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 未认证或认证已过期
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 无权访问
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 修改资产套餐过期时间
|
||||
tags:
|
||||
- 资产管理
|
||||
/api/admin/assets/{identifier}/packages/{package_usage_id}/used-data:
|
||||
patch:
|
||||
description: 修改指定资产下某条套餐使用记录的真实已用量(MB)。仅账号ID为2的账号可调用。
|
||||
parameters:
|
||||
- description: 资产标识符(虚拟号/ICCID/IMEI/SN/MSISDN)
|
||||
in: path
|
||||
name: identifier
|
||||
required: true
|
||||
schema:
|
||||
description: 资产标识符(虚拟号/ICCID/IMEI/SN/MSISDN)
|
||||
type: string
|
||||
- description: 套餐使用记录ID
|
||||
in: path
|
||||
name: package_usage_id
|
||||
required: true
|
||||
schema:
|
||||
description: 套餐使用记录ID
|
||||
minimum: 0
|
||||
type: integer
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DtoUpdateAssetPackageUsageRequest'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
code:
|
||||
description: 响应码
|
||||
example: 0
|
||||
type: integer
|
||||
data:
|
||||
$ref: '#/components/schemas/DtoAssetPackageResponse'
|
||||
msg:
|
||||
description: 响应消息
|
||||
example: success
|
||||
type: string
|
||||
timestamp:
|
||||
description: 时间戳
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- code
|
||||
- msg
|
||||
- data
|
||||
- timestamp
|
||||
type: object
|
||||
description: 成功
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 请求参数错误
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 未认证或认证已过期
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 无权访问
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 修改资产套餐已用量
|
||||
tags:
|
||||
- 资产管理
|
||||
/api/admin/assets/{identifier}/polling-status:
|
||||
patch:
|
||||
description: 启用或禁用指定资产(IoT卡或设备)的定期状态轮询。
|
||||
@@ -11490,11 +11773,11 @@ paths:
|
||||
schema:
|
||||
description: 每页数量,默认20,最大100
|
||||
type: integer
|
||||
- description: 交易类型过滤:recharge/deduct/refund
|
||||
- description: 交易类型过滤:recharge/deduct/refund/exchange
|
||||
in: query
|
||||
name: transaction_type
|
||||
schema:
|
||||
description: 交易类型过滤:recharge/deduct/refund
|
||||
description: 交易类型过滤:recharge/deduct/refund/exchange
|
||||
nullable: true
|
||||
type: string
|
||||
- description: 开始时间(RFC3339)
|
||||
@@ -13399,6 +13682,13 @@ paths:
|
||||
description: 是否有生效中的套餐(true:有生效中主套餐, false:无生效中主套餐)
|
||||
nullable: true
|
||||
type: boolean
|
||||
- description: 关键字搜索,匹配虚拟号或IMEI(模糊查询,与virtual_no/imei独立)
|
||||
in: query
|
||||
name: keyword
|
||||
schema:
|
||||
description: 关键字搜索,匹配虚拟号或IMEI(模糊查询,与virtual_no/imei独立)
|
||||
maxLength: 100
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
@@ -15499,6 +15789,15 @@ paths:
|
||||
minimum: 1
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 换货流程类型 (shipping:物流换货, direct:直接换货)
|
||||
in: query
|
||||
name: flow_type
|
||||
schema:
|
||||
description: 换货流程类型 (shipping:物流换货, direct:直接换货)
|
||||
enum:
|
||||
- shipping
|
||||
- direct
|
||||
type: string
|
||||
- description: 资产标识符搜索(旧资产/新资产标识符模糊匹配)
|
||||
in: query
|
||||
name: identifier
|
||||
@@ -16772,6 +17071,13 @@ paths:
|
||||
description: 运营商名称(模糊查询)
|
||||
maxLength: 100
|
||||
type: string
|
||||
- description: 关键字搜索,匹配ICCID或卡虚拟号(模糊查询,与iccid/virtual_no独立)
|
||||
in: query
|
||||
name: keyword
|
||||
schema:
|
||||
description: 关键字搜索,匹配ICCID或卡虚拟号(模糊查询,与iccid/virtual_no独立)
|
||||
maxLength: 100
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
@@ -26812,7 +27118,7 @@ paths:
|
||||
}
|
||||
```
|
||||
|
||||
按 card_no 查询单卡流量、当前套餐和待生效套餐。card_no 支持 ICCID、虚拟号、MSISDN。
|
||||
按 card_no 查询流量、当前套餐和待生效套餐。card_no 支持三种解析路径:1) ICCID/虚拟号/MSISDN 对应独立卡,查卡维度流量;2) 已绑定设备的卡,自动反查绑定设备,查设备维度流量(响应含 device_no);3) IMEI 或设备虚拟号,直接查设备维度流量(响应含 device_no)。
|
||||
parameters:
|
||||
- description: 卡标识(支持 ICCID、虚拟号、MSISDN)
|
||||
in: query
|
||||
@@ -26880,7 +27186,462 @@ paths:
|
||||
AgentOpenAPIPassword: []
|
||||
AgentOpenAPISign: []
|
||||
AgentOpenAPITimestamp: []
|
||||
summary: 查询单卡流量
|
||||
summary: 查询单卡或设备流量
|
||||
tags:
|
||||
- 代理开放接口
|
||||
/api/open/v1/devices/reboot:
|
||||
post:
|
||||
description: |-
|
||||
认证方式:
|
||||
1. 每个请求必须携带 Header:X-Agent-Account、X-Agent-Password、X-Agent-Timestamp、X-Agent-Nonce、X-Agent-Sign。
|
||||
2. Header 字段说明:
|
||||
- X-Agent-Account:代理账号标识,填写代理登录平台使用的用户名或手机号,例如 agent001;该值由平台开通代理账号时提供,必须与签名原文 account 行完全一致。
|
||||
- X-Agent-Password:代理账号当前登录密码,由代理账号持有人提供;该值同时作为 HMAC-SHA256 签名密钥,密码变更后必须使用新密码签名。
|
||||
- X-Agent-Timestamp:请求发起时间,由调用方生成,支持 Unix 秒、Unix 毫秒或 RFC3339 时间,例如 1715400000、1715400000000 或 2024-05-11T10:00:00+08:00;必须与签名原文 timestamp 行完全一致。
|
||||
- X-Agent-Nonce:请求随机串,由调用方每次请求自行生成,例如 UUID、雪花 ID 或“时间戳+随机数”;不需要平台提前分配,同一账号在 5 分钟内不可重复。
|
||||
- X-Agent-Sign:请求签名,由调用方按下方规则实时计算得到,不是平台分配的固定值;只要 method、path、query、body、timestamp、nonce、account 或 password 任意一个变化,签名都会变化。
|
||||
3. X-Agent-Timestamp 超出允许时间窗口会认证失败;客户端服务器时间应保持同步。
|
||||
4. X-Agent-Nonce 重复会被判定为重放请求。
|
||||
5. 签名原文按 7 行拼接,行尾使用 \n,最后一行后不追加换行:
|
||||
METHOD
|
||||
PATH
|
||||
canonical_query
|
||||
body_sha256
|
||||
timestamp
|
||||
nonce
|
||||
account
|
||||
6. METHOD 使用大写 HTTP 方法;PATH 只取请求路径,例如 /api/open/v1/cards/status,不包含域名和 query。
|
||||
7. canonical_query 为 query 参数规范化结果:排除 sign 字段,参数名升序;同名多值按值升序;key 和 value 使用 URL QueryEscape 编码后用 key=value 拼接,多个参数用 & 连接;无 query 时为空字符串。
|
||||
8. body_sha256 为原始请求 body 字节的 SHA256 小写十六进制值;GET 或空 body 使用空字符串的 SHA256;POST JSON 请求必须用最终发送的 body 字符串计算,签名和请求发送的 body 必须完全一致。
|
||||
9. X-Agent-Sign = hex(HMAC-SHA256(password, sign_payload)),password 为 X-Agent-Password 的原始值,结果使用小写十六进制。
|
||||
|
||||
Node.js 签名示例:
|
||||
|
||||
```javascript
|
||||
const crypto = require("crypto");
|
||||
|
||||
function queryEscape(value) {
|
||||
return new URLSearchParams({ v: String(value) }).toString().slice(2);
|
||||
}
|
||||
|
||||
function canonicalQuery(params) {
|
||||
return Object.entries(params || {})
|
||||
.filter(([key]) => key.toLowerCase() !== "sign")
|
||||
.flatMap(([key, value]) => {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
return values.sort().map((item) => [key, item]);
|
||||
})
|
||||
.sort(([aKey, aValue], [bKey, bValue]) => {
|
||||
if (aKey === bKey) return String(aValue).localeCompare(String(bValue));
|
||||
return aKey.localeCompare(bKey);
|
||||
})
|
||||
.map(([key, value]) => queryEscape(key) + "=" + queryEscape(value))
|
||||
.join("&");
|
||||
}
|
||||
|
||||
function buildAgentSign({ method, path, query, body, timestamp, nonce, account, password }) {
|
||||
const bodyText = body || "";
|
||||
const bodyHash = crypto.createHash("sha256").update(bodyText).digest("hex");
|
||||
const signPayload = [
|
||||
method.toUpperCase(),
|
||||
path,
|
||||
canonicalQuery(query),
|
||||
bodyHash,
|
||||
timestamp,
|
||||
nonce,
|
||||
account,
|
||||
].join("\n");
|
||||
return crypto.createHmac("sha256", password).update(signPayload).digest("hex");
|
||||
}
|
||||
```
|
||||
|
||||
按 device_no 远程重启设备。
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DtoAgentOpenAPIDeviceOperationRequest'
|
||||
responses:
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 请求参数错误
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 未认证或认证已过期
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 无权访问
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- AgentOpenAPIAccount: []
|
||||
AgentOpenAPINonce: []
|
||||
AgentOpenAPIPassword: []
|
||||
AgentOpenAPISign: []
|
||||
AgentOpenAPITimestamp: []
|
||||
summary: 重启设备
|
||||
tags:
|
||||
- 代理开放接口
|
||||
/api/open/v1/devices/reset:
|
||||
post:
|
||||
description: |-
|
||||
认证方式:
|
||||
1. 每个请求必须携带 Header:X-Agent-Account、X-Agent-Password、X-Agent-Timestamp、X-Agent-Nonce、X-Agent-Sign。
|
||||
2. Header 字段说明:
|
||||
- X-Agent-Account:代理账号标识,填写代理登录平台使用的用户名或手机号,例如 agent001;该值由平台开通代理账号时提供,必须与签名原文 account 行完全一致。
|
||||
- X-Agent-Password:代理账号当前登录密码,由代理账号持有人提供;该值同时作为 HMAC-SHA256 签名密钥,密码变更后必须使用新密码签名。
|
||||
- X-Agent-Timestamp:请求发起时间,由调用方生成,支持 Unix 秒、Unix 毫秒或 RFC3339 时间,例如 1715400000、1715400000000 或 2024-05-11T10:00:00+08:00;必须与签名原文 timestamp 行完全一致。
|
||||
- X-Agent-Nonce:请求随机串,由调用方每次请求自行生成,例如 UUID、雪花 ID 或“时间戳+随机数”;不需要平台提前分配,同一账号在 5 分钟内不可重复。
|
||||
- X-Agent-Sign:请求签名,由调用方按下方规则实时计算得到,不是平台分配的固定值;只要 method、path、query、body、timestamp、nonce、account 或 password 任意一个变化,签名都会变化。
|
||||
3. X-Agent-Timestamp 超出允许时间窗口会认证失败;客户端服务器时间应保持同步。
|
||||
4. X-Agent-Nonce 重复会被判定为重放请求。
|
||||
5. 签名原文按 7 行拼接,行尾使用 \n,最后一行后不追加换行:
|
||||
METHOD
|
||||
PATH
|
||||
canonical_query
|
||||
body_sha256
|
||||
timestamp
|
||||
nonce
|
||||
account
|
||||
6. METHOD 使用大写 HTTP 方法;PATH 只取请求路径,例如 /api/open/v1/cards/status,不包含域名和 query。
|
||||
7. canonical_query 为 query 参数规范化结果:排除 sign 字段,参数名升序;同名多值按值升序;key 和 value 使用 URL QueryEscape 编码后用 key=value 拼接,多个参数用 & 连接;无 query 时为空字符串。
|
||||
8. body_sha256 为原始请求 body 字节的 SHA256 小写十六进制值;GET 或空 body 使用空字符串的 SHA256;POST JSON 请求必须用最终发送的 body 字符串计算,签名和请求发送的 body 必须完全一致。
|
||||
9. X-Agent-Sign = hex(HMAC-SHA256(password, sign_payload)),password 为 X-Agent-Password 的原始值,结果使用小写十六进制。
|
||||
|
||||
Node.js 签名示例:
|
||||
|
||||
```javascript
|
||||
const crypto = require("crypto");
|
||||
|
||||
function queryEscape(value) {
|
||||
return new URLSearchParams({ v: String(value) }).toString().slice(2);
|
||||
}
|
||||
|
||||
function canonicalQuery(params) {
|
||||
return Object.entries(params || {})
|
||||
.filter(([key]) => key.toLowerCase() !== "sign")
|
||||
.flatMap(([key, value]) => {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
return values.sort().map((item) => [key, item]);
|
||||
})
|
||||
.sort(([aKey, aValue], [bKey, bValue]) => {
|
||||
if (aKey === bKey) return String(aValue).localeCompare(String(bValue));
|
||||
return aKey.localeCompare(bKey);
|
||||
})
|
||||
.map(([key, value]) => queryEscape(key) + "=" + queryEscape(value))
|
||||
.join("&");
|
||||
}
|
||||
|
||||
function buildAgentSign({ method, path, query, body, timestamp, nonce, account, password }) {
|
||||
const bodyText = body || "";
|
||||
const bodyHash = crypto.createHash("sha256").update(bodyText).digest("hex");
|
||||
const signPayload = [
|
||||
method.toUpperCase(),
|
||||
path,
|
||||
canonicalQuery(query),
|
||||
bodyHash,
|
||||
timestamp,
|
||||
nonce,
|
||||
account,
|
||||
].join("\n");
|
||||
return crypto.createHmac("sha256", password).update(signPayload).digest("hex");
|
||||
}
|
||||
```
|
||||
|
||||
按 device_no 远程恢复设备出厂设置。
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DtoAgentOpenAPIDeviceOperationRequest'
|
||||
responses:
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 请求参数错误
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 未认证或认证已过期
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 无权访问
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- AgentOpenAPIAccount: []
|
||||
AgentOpenAPINonce: []
|
||||
AgentOpenAPIPassword: []
|
||||
AgentOpenAPISign: []
|
||||
AgentOpenAPITimestamp: []
|
||||
summary: 恢复出厂设置
|
||||
tags:
|
||||
- 代理开放接口
|
||||
/api/open/v1/devices/switch-card:
|
||||
post:
|
||||
description: |-
|
||||
认证方式:
|
||||
1. 每个请求必须携带 Header:X-Agent-Account、X-Agent-Password、X-Agent-Timestamp、X-Agent-Nonce、X-Agent-Sign。
|
||||
2. Header 字段说明:
|
||||
- X-Agent-Account:代理账号标识,填写代理登录平台使用的用户名或手机号,例如 agent001;该值由平台开通代理账号时提供,必须与签名原文 account 行完全一致。
|
||||
- X-Agent-Password:代理账号当前登录密码,由代理账号持有人提供;该值同时作为 HMAC-SHA256 签名密钥,密码变更后必须使用新密码签名。
|
||||
- X-Agent-Timestamp:请求发起时间,由调用方生成,支持 Unix 秒、Unix 毫秒或 RFC3339 时间,例如 1715400000、1715400000000 或 2024-05-11T10:00:00+08:00;必须与签名原文 timestamp 行完全一致。
|
||||
- X-Agent-Nonce:请求随机串,由调用方每次请求自行生成,例如 UUID、雪花 ID 或“时间戳+随机数”;不需要平台提前分配,同一账号在 5 分钟内不可重复。
|
||||
- X-Agent-Sign:请求签名,由调用方按下方规则实时计算得到,不是平台分配的固定值;只要 method、path、query、body、timestamp、nonce、account 或 password 任意一个变化,签名都会变化。
|
||||
3. X-Agent-Timestamp 超出允许时间窗口会认证失败;客户端服务器时间应保持同步。
|
||||
4. X-Agent-Nonce 重复会被判定为重放请求。
|
||||
5. 签名原文按 7 行拼接,行尾使用 \n,最后一行后不追加换行:
|
||||
METHOD
|
||||
PATH
|
||||
canonical_query
|
||||
body_sha256
|
||||
timestamp
|
||||
nonce
|
||||
account
|
||||
6. METHOD 使用大写 HTTP 方法;PATH 只取请求路径,例如 /api/open/v1/cards/status,不包含域名和 query。
|
||||
7. canonical_query 为 query 参数规范化结果:排除 sign 字段,参数名升序;同名多值按值升序;key 和 value 使用 URL QueryEscape 编码后用 key=value 拼接,多个参数用 & 连接;无 query 时为空字符串。
|
||||
8. body_sha256 为原始请求 body 字节的 SHA256 小写十六进制值;GET 或空 body 使用空字符串的 SHA256;POST JSON 请求必须用最终发送的 body 字符串计算,签名和请求发送的 body 必须完全一致。
|
||||
9. X-Agent-Sign = hex(HMAC-SHA256(password, sign_payload)),password 为 X-Agent-Password 的原始值,结果使用小写十六进制。
|
||||
|
||||
Node.js 签名示例:
|
||||
|
||||
```javascript
|
||||
const crypto = require("crypto");
|
||||
|
||||
function queryEscape(value) {
|
||||
return new URLSearchParams({ v: String(value) }).toString().slice(2);
|
||||
}
|
||||
|
||||
function canonicalQuery(params) {
|
||||
return Object.entries(params || {})
|
||||
.filter(([key]) => key.toLowerCase() !== "sign")
|
||||
.flatMap(([key, value]) => {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
return values.sort().map((item) => [key, item]);
|
||||
})
|
||||
.sort(([aKey, aValue], [bKey, bValue]) => {
|
||||
if (aKey === bKey) return String(aValue).localeCompare(String(bValue));
|
||||
return aKey.localeCompare(bKey);
|
||||
})
|
||||
.map(([key, value]) => queryEscape(key) + "=" + queryEscape(value))
|
||||
.join("&");
|
||||
}
|
||||
|
||||
function buildAgentSign({ method, path, query, body, timestamp, nonce, account, password }) {
|
||||
const bodyText = body || "";
|
||||
const bodyHash = crypto.createHash("sha256").update(bodyText).digest("hex");
|
||||
const signPayload = [
|
||||
method.toUpperCase(),
|
||||
path,
|
||||
canonicalQuery(query),
|
||||
bodyHash,
|
||||
timestamp,
|
||||
nonce,
|
||||
account,
|
||||
].join("\n");
|
||||
return crypto.createHmac("sha256", password).update(signPayload).digest("hex");
|
||||
}
|
||||
```
|
||||
|
||||
按 device_no 将多卡设备切换到指定 ICCID 的卡。
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DtoAgentOpenAPIDeviceSwitchCardRequest'
|
||||
responses:
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 请求参数错误
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 未认证或认证已过期
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 无权访问
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- AgentOpenAPIAccount: []
|
||||
AgentOpenAPINonce: []
|
||||
AgentOpenAPIPassword: []
|
||||
AgentOpenAPISign: []
|
||||
AgentOpenAPITimestamp: []
|
||||
summary: 切网(多卡设备切换 ICCID)
|
||||
tags:
|
||||
- 代理开放接口
|
||||
/api/open/v1/devices/traffic:
|
||||
get:
|
||||
description: |-
|
||||
认证方式:
|
||||
1. 每个请求必须携带 Header:X-Agent-Account、X-Agent-Password、X-Agent-Timestamp、X-Agent-Nonce、X-Agent-Sign。
|
||||
2. Header 字段说明:
|
||||
- X-Agent-Account:代理账号标识,填写代理登录平台使用的用户名或手机号,例如 agent001;该值由平台开通代理账号时提供,必须与签名原文 account 行完全一致。
|
||||
- X-Agent-Password:代理账号当前登录密码,由代理账号持有人提供;该值同时作为 HMAC-SHA256 签名密钥,密码变更后必须使用新密码签名。
|
||||
- X-Agent-Timestamp:请求发起时间,由调用方生成,支持 Unix 秒、Unix 毫秒或 RFC3339 时间,例如 1715400000、1715400000000 或 2024-05-11T10:00:00+08:00;必须与签名原文 timestamp 行完全一致。
|
||||
- X-Agent-Nonce:请求随机串,由调用方每次请求自行生成,例如 UUID、雪花 ID 或“时间戳+随机数”;不需要平台提前分配,同一账号在 5 分钟内不可重复。
|
||||
- X-Agent-Sign:请求签名,由调用方按下方规则实时计算得到,不是平台分配的固定值;只要 method、path、query、body、timestamp、nonce、account 或 password 任意一个变化,签名都会变化。
|
||||
3. X-Agent-Timestamp 超出允许时间窗口会认证失败;客户端服务器时间应保持同步。
|
||||
4. X-Agent-Nonce 重复会被判定为重放请求。
|
||||
5. 签名原文按 7 行拼接,行尾使用 \n,最后一行后不追加换行:
|
||||
METHOD
|
||||
PATH
|
||||
canonical_query
|
||||
body_sha256
|
||||
timestamp
|
||||
nonce
|
||||
account
|
||||
6. METHOD 使用大写 HTTP 方法;PATH 只取请求路径,例如 /api/open/v1/cards/status,不包含域名和 query。
|
||||
7. canonical_query 为 query 参数规范化结果:排除 sign 字段,参数名升序;同名多值按值升序;key 和 value 使用 URL QueryEscape 编码后用 key=value 拼接,多个参数用 & 连接;无 query 时为空字符串。
|
||||
8. body_sha256 为原始请求 body 字节的 SHA256 小写十六进制值;GET 或空 body 使用空字符串的 SHA256;POST JSON 请求必须用最终发送的 body 字符串计算,签名和请求发送的 body 必须完全一致。
|
||||
9. X-Agent-Sign = hex(HMAC-SHA256(password, sign_payload)),password 为 X-Agent-Password 的原始值,结果使用小写十六进制。
|
||||
|
||||
Node.js 签名示例:
|
||||
|
||||
```javascript
|
||||
const crypto = require("crypto");
|
||||
|
||||
function queryEscape(value) {
|
||||
return new URLSearchParams({ v: String(value) }).toString().slice(2);
|
||||
}
|
||||
|
||||
function canonicalQuery(params) {
|
||||
return Object.entries(params || {})
|
||||
.filter(([key]) => key.toLowerCase() !== "sign")
|
||||
.flatMap(([key, value]) => {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
return values.sort().map((item) => [key, item]);
|
||||
})
|
||||
.sort(([aKey, aValue], [bKey, bValue]) => {
|
||||
if (aKey === bKey) return String(aValue).localeCompare(String(bValue));
|
||||
return aKey.localeCompare(bKey);
|
||||
})
|
||||
.map(([key, value]) => queryEscape(key) + "=" + queryEscape(value))
|
||||
.join("&");
|
||||
}
|
||||
|
||||
function buildAgentSign({ method, path, query, body, timestamp, nonce, account, password }) {
|
||||
const bodyText = body || "";
|
||||
const bodyHash = crypto.createHash("sha256").update(bodyText).digest("hex");
|
||||
const signPayload = [
|
||||
method.toUpperCase(),
|
||||
path,
|
||||
canonicalQuery(query),
|
||||
bodyHash,
|
||||
timestamp,
|
||||
nonce,
|
||||
account,
|
||||
].join("\n");
|
||||
return crypto.createHmac("sha256", password).update(signPayload).digest("hex");
|
||||
}
|
||||
```
|
||||
|
||||
按 device_no 查询设备套餐内流量、当前套餐和待生效套餐。device_no 支持虚拟号、IMEI。
|
||||
parameters:
|
||||
- description: 设备标识(支持虚拟号、IMEI)
|
||||
in: query
|
||||
name: device_no
|
||||
required: true
|
||||
schema:
|
||||
description: 设备标识(支持虚拟号、IMEI)
|
||||
maxLength: 100
|
||||
minLength: 1
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
code:
|
||||
description: 响应码
|
||||
example: 0
|
||||
type: integer
|
||||
data:
|
||||
$ref: '#/components/schemas/DtoAgentOpenAPIDeviceTrafficResponse'
|
||||
msg:
|
||||
description: 响应消息
|
||||
example: success
|
||||
type: string
|
||||
timestamp:
|
||||
description: 时间戳
|
||||
format: date-time
|
||||
type: string
|
||||
required:
|
||||
- code
|
||||
- msg
|
||||
- data
|
||||
- timestamp
|
||||
type: object
|
||||
description: 成功
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 请求参数错误
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 未认证或认证已过期
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 无权访问
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- AgentOpenAPIAccount: []
|
||||
AgentOpenAPINonce: []
|
||||
AgentOpenAPIPassword: []
|
||||
AgentOpenAPISign: []
|
||||
AgentOpenAPITimestamp: []
|
||||
summary: 查询设备套餐内流量
|
||||
tags:
|
||||
- 代理开放接口
|
||||
/api/open/v1/packages:
|
||||
|
||||
Reference in New Issue
Block a user