钱包订单退款时应当正确退回,充值时允许最低充值1分
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m7s

This commit is contained in:
2026-05-18 09:51:59 +08:00
parent d597a25c69
commit ed071918ed
8 changed files with 493 additions and 15 deletions

View File

@@ -135,6 +135,31 @@ components:
required:
- operation_password
type: object
DtoAgentOpenAPICardResumeRequest:
properties:
card_no:
description: 卡标识(支持 ICCID、虚拟号、MSISDN
maxLength: 100
minLength: 1
type: string
required:
- card_no
type: object
DtoAgentOpenAPICardResumeResponse:
properties:
card_no:
description: 卡标识
type: string
card_status:
description: 卡状态 (normal:正常, stopped:停机)
type: string
card_status_name:
description: 卡状态名称(中文)
type: string
gateway_extend:
description: Gateway 卡状态扩展字段,复机成功后本地清空
type: string
type: object
DtoAgentOpenAPICardStatusResponse:
properties:
card_no:
@@ -829,7 +854,7 @@ components:
nullable: true
type: string
activation_status:
description: 激活状态0:未激活, 1:已激活)
description: 激活状态0:未激活, 1:已激活;设备需有生效中主套餐且任意绑定卡已实名
type: integer
activation_status_name:
description: 激活状态名称(中文)
@@ -963,7 +988,7 @@ components:
description: 设备序列号
type: string
status:
description: 状态1:在库, 2:已分销, 3:已激活, 4:已停用
description: 资产归属状态1:在库, 2:已分销;卡仍沿用原卡状态枚举
type: integer
status_name:
description: 状态名称(中文)
@@ -1214,6 +1239,9 @@ components:
format: date-time
nullable: true
type: string
expiry_base:
description: 到期时间基准 (from_activation:实名激活时起算, from_purchase:购买时起算)
type: string
master_usage_id:
description: 主套餐ID加油包时有值
minimum: 0
@@ -1371,8 +1399,11 @@ components:
nullable: true
type: string
activation_status:
description: 激活状态
description: 激活状态0未激活 1已激活设备需有生效中主套餐且任意绑定卡已实名
type: integer
activation_status_name:
description: 激活状态名称(中文)
type: string
asset_id:
description: 资产数据库ID
minimum: 0
@@ -1538,7 +1569,7 @@ components:
description: 固件版本号(设备类型时有效)
type: string
status:
description: 资产状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)
description: 资产归属状态 (1:在库, 2:已分销;卡仍沿用原卡状态枚举)
type: integer
status_name:
description: 资产状态名称(中文)
@@ -1933,6 +1964,12 @@ components:
type: object
DtoBatchSetDeviceSeriesBindngRequest:
properties:
activation_status:
description: 激活状态 (0:未激活, 1:已激活selection_type=filter时可选)
maximum: 1
minimum: 0
nullable: true
type: integer
batch_no:
description: 批次号selection_type=filter时可选
maxLength: 100
@@ -1996,8 +2033,8 @@ components:
nullable: true
type: array
status:
description: 状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)selection_type=filter时可选
maximum: 4
description: 归属状态 (1:在库, 2:已分销)selection_type=filter时可选
maximum: 2
minimum: 1
nullable: true
type: integer
@@ -4392,6 +4429,12 @@ components:
format: date-time
nullable: true
type: string
activation_status:
description: 激活状态 (0:未激活, 1:已激活;需有生效中主套餐且任意绑定卡已实名)
type: integer
activation_status_name:
description: 激活状态名称(中文)
type: string
batch_no:
description: 批次号
type: string
@@ -4463,10 +4506,10 @@ components:
description: 固件版本号
type: string
status:
description: 状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)
description: 归属状态 (1:在库, 2:已分销)
type: integer
status_name:
description: 状态名称
description: 归属状态名称(中文)
type: string
switch_mode:
description: 切卡模式0=自动1=手动
@@ -13091,15 +13134,24 @@ paths:
description: 设备名称(模糊查询)
maxLength: 255
type: string
- description: 状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)
- description: 归属状态 (1:在库, 2:已分销)
in: query
name: status
schema:
description: 状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)
maximum: 4
description: 归属状态 (1:在库, 2:已分销)
maximum: 2
minimum: 1
nullable: true
type: integer
- description: 激活状态 (0:未激活, 1:已激活;需有生效中主套餐且任意绑定卡已实名)
in: query
name: activation_status
schema:
description: 激活状态 (0:未激活, 1:已激活;需有生效中主套餐且任意绑定卡已实名)
maximum: 1
minimum: 0
nullable: true
type: integer
- description: 店铺ID兼容旧参数单选NULL表示平台库存
in: query
name: shop_id
@@ -26220,6 +26272,138 @@ paths:
summary: 查询单卡实名状态
tags:
- 代理开放接口
/api/open/v1/cards/resume:
post:
description: |-
认证方式:
1. 每个请求必须携带 HeaderX-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 使用空字符串的 SHA256POST 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");
}
```
按 card_no 调用上游复机接口。调用前检查本地落库的 Gateway 卡状态扩展字段,仅当 gateway_extend 等于“机卡分离停机”时允许复机,否则返回“仅允许机卡分离状态下的卡复机”。
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DtoAgentOpenAPICardResumeRequest'
responses:
"200":
content:
application/json:
schema:
properties:
code:
description: 响应码
example: 0
type: integer
data:
$ref: '#/components/schemas/DtoAgentOpenAPICardResumeResponse'
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/cards/status:
get:
description: |-

View File

@@ -0,0 +1,31 @@
# 设备状态拆分功能总结
## 背景
设备原 `status` 同时表达在库、分销、激活和停用,导致“已分销且已激活”的设备只能展示一个状态,语义混淆。
## 调整内容
- `status` 收敛为设备归属状态:`1=在库``2=已分销`
- 设备响应新增 `activation_status``activation_status_name`
- 设备列表和批量筛选支持按 `activation_status` 查询
- 设备激活状态不落库,按查询实时计算
- 历史 `status=3/4` 迁移为归属状态:有 `shop_id` 归为已分销,无 `shop_id` 归为在库
## 激活判定
设备激活必须同时满足:
1. 存在生效中的主套餐(`tb_package_usage.status=1``master_usage_id IS NULL`
2. 设备任意绑定卡已实名(`tb_iot_card.real_name_status=1`
## 响应示例
```json
{
"status": 2,
"status_name": "已分销",
"activation_status": 1,
"activation_status_name": "已激活"
}
```

View File

@@ -153,7 +153,7 @@ INSERT INTO carriers (carrier_code, carrier_name, status) VALUES
| sim_slots | INT | DEFAULT 1 | SIM卡槽数量(1-4) |
| owner_type | VARCHAR(20) | NOT NULL, DEFAULT 'platform' | 所有者类型 platform-平台 agent-代理 user-用户 |
| owner_id | BIGINT | NOT NULL, DEFAULT 0 | 所有者ID |
| status | INT | NOT NULL, DEFAULT 1 | 状态 1-在库 2-已激活 3-已停用 |
| status | INT | NOT NULL, DEFAULT 1 | 归属状态 1-在库 2-已分销 |
| activated_at | TIMESTAMPTZ | | 激活时间 |
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | 创建时间 |
| updated_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | 更新时间 |