Files
junhong_cmp_fiber/internal/routes/open.go
2026-05-11 11:59:36 +08:00

133 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routes
import (
"github.com/gofiber/fiber/v2"
openapiHandler "github.com/break/junhong_cmp_fiber/internal/handler/openapi"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// RegisterOpenAPIRoutes 注册代理开放接口路由
func RegisterOpenAPIRoutes(router fiber.Router, handler *openapiHandler.Handler, doc *openapi.Generator, basePath string) {
const tag = "代理开放接口"
const authDescription = `认证方式:
1. 每个请求必须携带 HeaderX-Agent-Account、X-Agent-Password、X-Agent-Timestamp、X-Agent-Nonce、X-Agent-Sign。
2. X-Agent-Timestamp 支持 Unix 秒、Unix 毫秒或 RFC3339 时间,服务端允许 5 分钟时间误差。
3. X-Agent-Nonce 为随机串,同一账号在 5 分钟内不可重复。
4. 签名原文按 7 行拼接,行尾使用 \n最后一行后不追加换行
METHOD
PATH
canonical_query
body_sha256
timestamp
nonce
account
5. METHOD 使用大写 HTTP 方法PATH 只取请求路径,例如 /api/open/v1/cards/status不包含域名和 query。
6. canonical_query 为 query 参数规范化结果:排除 sign 字段参数名升序同名多值按值升序key 和 value 使用 URL QueryEscape 编码后用 key=value 拼接,多个参数用 & 连接;无 query 时为空字符串。
7. body_sha256 为原始请求 body 字节的 SHA256 小写十六进制值GET 或空 body 使用空字符串的 SHA256POST JSON 请求必须用最终发送的 body 字符串计算,签名和请求发送的 body 必须完全一致。
8. X-Agent-Sign = hex(HMAC-SHA256(password, sign_payload))password 为 X-Agent-Password 的原始值,结果使用小写十六进制。
Node.js 签名示例:
` + "```javascript\n" + `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");
}
` + "```"
Register(router, doc, basePath, "GET", "/cards/traffic", handler.GetCardTraffic, RouteSpec{
Summary: "查询单卡流量",
Description: authDescription + "\n\n按 card_no 查询单卡流量、当前套餐和待生效套餐。card_no 支持 ICCID、虚拟号、MSISDN。",
Input: new(dto.AgentOpenAPICardQueryRequest),
Output: new(dto.AgentOpenAPICardTrafficResponse),
Tags: []string{tag},
Auth: true,
SecurityScheme: openapi.SecuritySchemeAgentOpenAPI,
})
Register(router, doc, basePath, "GET", "/cards/status", handler.GetCardStatus, RouteSpec{
Summary: "查询单卡状态",
Description: authDescription + "\n\n按 card_no 查询单卡状态,返回正常或停机以及停机原因。",
Input: new(dto.AgentOpenAPICardQueryRequest),
Output: new(dto.AgentOpenAPICardStatusResponse),
Tags: []string{tag},
Auth: true,
SecurityScheme: openapi.SecuritySchemeAgentOpenAPI,
})
Register(router, doc, basePath, "GET", "/cards/realname-status", handler.GetRealnameStatus, RouteSpec{
Summary: "查询单卡实名状态",
Description: authDescription + "\n\n按 card_no 查询单卡实名状态。",
Input: new(dto.AgentOpenAPICardQueryRequest),
Output: new(dto.AgentOpenAPIRealnameStatusResponse),
Tags: []string{tag},
Auth: true,
SecurityScheme: openapi.SecuritySchemeAgentOpenAPI,
})
Register(router, doc, basePath, "GET", "/packages", handler.ListPackages, RouteSpec{
Summary: "查询套餐列表",
Description: authDescription + "\n\n分页查询当前账号可购买的套餐返回套餐基础信息和价格。",
Input: new(dto.AgentOpenAPIPackageListRequest),
Output: new(dto.AgentOpenAPIPackageItem),
Tags: []string{tag},
Auth: true,
SecurityScheme: openapi.SecuritySchemeAgentOpenAPI,
})
Register(router, doc, basePath, "POST", "/wallet/package-orders", handler.CreateWalletPackageOrders, RouteSpec{
Summary: "钱包套餐购买",
Description: authDescription + "\n\n使用预充值钱包为多张卡购买同一个套餐编码。每张卡独立下单允许部分成功。",
Input: new(dto.AgentOpenAPIWalletPackageOrderRequest),
Output: new(dto.AgentOpenAPIWalletPackageOrderResponse),
Tags: []string{tag},
Auth: true,
SecurityScheme: openapi.SecuritySchemeAgentOpenAPI,
})
Register(router, doc, basePath, "GET", "/wallet/balance", handler.GetWalletBalance, RouteSpec{
Summary: "查询预充值钱包余额",
Description: authDescription + "\n\n查询当前账号预充值钱包余额。未开通钱包时返回 0。",
Output: new(dto.AgentOpenAPIWalletBalanceResponse),
Tags: []string{tag},
Auth: true,
SecurityScheme: openapi.SecuritySchemeAgentOpenAPI,
})
Register(router, doc, basePath, "GET", "/wallet/transactions", handler.ListWalletTransactions, RouteSpec{
Summary: "查询预充值钱包流水",
Description: authDescription + "\n\n分页查询当前账号预充值钱包流水。",
Input: new(dto.AgentOpenAPIWalletTransactionListRequest),
Output: new(dto.MainWalletTransactionItem),
Tags: []string{tag},
Auth: true,
SecurityScheme: openapi.SecuritySchemeAgentOpenAPI,
})
}