404 lines
17 KiB
HTML
404 lines
17 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>API接口文档</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: #f5f7fa; color: #333; line-height: 1.6; }
|
||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||
|
||
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px 20px; border-radius: 8px; margin-bottom: 30px; }
|
||
.header h1 { font-size: 32px; font-weight: 700; margin-bottom: 10px; }
|
||
.header p { font-size: 16px; opacity: 0.9; }
|
||
|
||
.section { background: white; border-radius: 8px; padding: 30px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
|
||
.section-title { font-size: 22px; font-weight: 600; color: #333; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid #667eea; }
|
||
|
||
.endpoint { background: #f8f9fa; border-left: 4px solid #667eea; padding: 20px; margin: 20px 0; border-radius: 4px; }
|
||
.endpoint-header { display: flex; align-items: center; gap: 15px; margin-bottom: 15px; }
|
||
.method { background: #28a745; color: white; padding: 4px 12px; border-radius: 4px; font-weight: 600; font-size: 14px; }
|
||
.url { font-family: 'Consolas', monospace; font-size: 16px; color: #333; }
|
||
|
||
.param-table { width: 100%; border-collapse: collapse; margin: 15px 0; }
|
||
.param-table th, .param-table td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
|
||
.param-table th { background: #f8f9fa; font-weight: 600; color: #555; }
|
||
.param-table tr:hover { background: #f8f9fa; }
|
||
.required { color: #dc3545; font-weight: 600; }
|
||
.optional { color: #28a745; }
|
||
|
||
.code-block { background: #282c34; color: #abb2bf; padding: 20px; border-radius: 6px; overflow-x: auto; font-family: 'Consolas', monospace; font-size: 14px; margin: 15px 0; }
|
||
.code-block .keyword { color: #c678dd; }
|
||
.code-block .string { color: #98c379; }
|
||
.code-block .comment { color: #5c6370; font-style: italic; }
|
||
.code-block .function { color: #61afef; }
|
||
|
||
.response-example { background: #e8f5e9; border: 1px solid #c8e6c9; border-radius: 4px; padding: 15px; margin: 15px 0; }
|
||
.response-title { font-weight: 600; color: #2e7d32; margin-bottom: 10px; }
|
||
|
||
.signature-steps { counter-reset: step; }
|
||
.signature-step { position: relative; padding-left: 40px; margin: 15px 0; }
|
||
.signature-step::before { counter-increment: step; content: counter(step); position: absolute; left: 0; top: 0; width: 28px; height: 28px; background: #667eea; color: white; border-radius: 50%; text-align: center; line-height: 28px; font-weight: 600; }
|
||
|
||
.alert { background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 15px; margin: 15px 0; }
|
||
.alert-title { font-weight: 600; color: #856404; margin-bottom: 5px; }
|
||
|
||
.nav { display: flex; gap: 10px; margin-bottom: 20px; }
|
||
.nav a { padding: 8px 16px; background: white; border: 1px solid #ddd; border-radius: 4px; text-decoration: none; color: #333; transition: all 0.2s; }
|
||
.nav a:hover { background: #667eea; color: white; border-color: #667eea; }
|
||
.test-btn { display: inline-block; background: #28a745; color: white; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 600; transition: all 0.2s; margin-top: 15px; }
|
||
.test-btn:hover { background: #218838; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(40, 167, 69, 0.4); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>进销存开放平台 API 文档</h1>
|
||
<p>版本: v1.0 | 基础URL: https://yisheng.cpolar.cn</p>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="nav">
|
||
<a href="#auth">认证方式</a>
|
||
<a href="#chuku">出库接口</a>
|
||
<a href="#ruku">入库接口</a>
|
||
<a target="_blank" href="test_api.html">点击测试</a>
|
||
<a target="_blank" href="client.html">客户端查询</a>
|
||
</div>
|
||
|
||
<div class="section" id="auth">
|
||
<div class="section-title">认证方式</div>
|
||
|
||
<p style="margin-bottom: 20px;">所有API请求都需要通过HMAC-SHA256签名验证,确保请求的合法性和完整性。</p>
|
||
|
||
<h3 style="margin: 20px 0 10px;">请求头</h3>
|
||
<table class="param-table">
|
||
<thead>
|
||
<tr>
|
||
<th>参数名</th>
|
||
<th>类型</th>
|
||
<th>必填</th>
|
||
<th>说明</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>X-API-Key</td>
|
||
<td>String</td>
|
||
<td><span class="required">是</span></td>
|
||
<td>API密钥,用于标识调用方身份</td>
|
||
</tr>
|
||
<tr>
|
||
<td>X-Timestamp</td>
|
||
<td>String</td>
|
||
<td><span class="required">是</span></td>
|
||
<td>时间戳(秒级),用于防止重放攻击</td>
|
||
</tr>
|
||
<tr>
|
||
<td>X-Sign</td>
|
||
<td>String</td>
|
||
<td><span class="required">是</span></td>
|
||
<td>请求签名,HMAC-SHA256算法生成</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3 style="margin: 20px 0 10px;">签名生成步骤</h3>
|
||
<div class="signature-steps">
|
||
<div class="signature-step">
|
||
<strong>1. 参数排序</strong><br>
|
||
将所有请求参数(不包括签名相关参数)按参数名ASCII码升序排列
|
||
</div>
|
||
<div class="signature-step">
|
||
<strong>2. 拼接参数字符串</strong><br>
|
||
将排序后的参数按 key=value 格式拼接,参数之间用 & 连接
|
||
</div>
|
||
<div class="signature-step">
|
||
<strong>3. 添加时间戳</strong><br>
|
||
在参数字符串末尾追加 ×tamp={时间戳}
|
||
</div>
|
||
<div class="signature-step">
|
||
<strong>4. 生成签名</strong><br>
|
||
使用 HMAC-SHA256 算法,以 API Secret 为密钥,对完整字符串进行签名
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert">
|
||
<div class="alert-title">⚠️ 注意事项</div>
|
||
<ul style="margin-left: 20px;">
|
||
<li>时间戳有效期:7天</li>
|
||
<li>签名大小写敏感</li>
|
||
<li>参数值不进行URL编码</li>
|
||
<li>空值参数不参与签名</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<h3 style="margin: 20px 0 10px;">签名示例(JavaScript)</h3>
|
||
<div class="code-block">
|
||
<span class="keyword">async function</span> <span class="function">generateSignature</span>(params, timestamp, apiSecret) {
|
||
<span class="comment">// 1. 参数排序</span>
|
||
<span class="keyword">const</span> sortedKeys = Object.keys(params).sort();
|
||
|
||
<span class="comment">// 2. 拼接参数字符串</span>
|
||
<span class="keyword">let</span> paramStr = <span class="string">""</span>;
|
||
<span class="keyword">for</span> (<span class="keyword">const</span> k <span class="keyword">of</span> sortedKeys) {
|
||
<span class="keyword">const</span> v = params[k];
|
||
<span class="keyword">if</span> (v !== null && v !== <span class="string">""</span>) {
|
||
<span class="keyword">if</span> (paramStr) paramStr += <span class="string">"&"</span>;
|
||
paramStr += k + <span class="string">"="</span> + v;
|
||
}
|
||
}
|
||
|
||
<span class="comment">// 3. 添加时间戳</span>
|
||
<span class="keyword">const</span> fullStr = paramStr + <span class="string">"×tamp="</span> + timestamp;
|
||
|
||
<span class="comment">// 4. 生成签名</span>
|
||
<span class="keyword">const</span> encoder = <span class="keyword">new</span> TextEncoder();
|
||
<span class="keyword">const</span> messageData = encoder.encode(fullStr);
|
||
<span class="keyword">const</span> secretData = encoder.encode(apiSecret);
|
||
|
||
<span class="keyword">const</span> key = <span class="keyword">await</span> crypto.subtle.importKey(
|
||
<span class="string">"raw"</span>,
|
||
secretData,
|
||
{ name: <span class="string">"HMAC"</span>, hash: <span class="string">"SHA-256"</span> },
|
||
<span class="keyword">false</span>,
|
||
[<span class="string">"sign"</span>]
|
||
);
|
||
|
||
<span class="keyword">const</span> signature = <span class="keyword">await</span> crypto.subtle.sign(<span class="string">"HMAC"</span>, key, messageData);
|
||
|
||
<span class="keyword">return</span> Array.from(<span class="keyword">new</span> Uint8Array(signature))
|
||
.map(b => b.toString(16).padStart(2, <span class="string">"0"</span>))
|
||
.join(<span class="string">""</span>);
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section" id="chuku">
|
||
<div class="section-title">出库接口</div>
|
||
|
||
<div class="endpoint">
|
||
<div class="endpoint-header">
|
||
<span class="method">GET</span>
|
||
<span class="url">/api/v1/chuku</span>
|
||
</div>
|
||
<p>查询出库(销售)数据,支持分页和日期范围过滤</p>
|
||
</div>
|
||
|
||
<h3>请求参数</h3>
|
||
<table class="param-table">
|
||
<thead>
|
||
<tr>
|
||
<th>参数名</th>
|
||
<th>类型</th>
|
||
<th>必填</th>
|
||
<th>说明</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>start_date</td>
|
||
<td>String</td>
|
||
<td><span class="optional">否</span></td>
|
||
<td>开始日期,格式:YYYY-MM-DD</td>
|
||
</tr>
|
||
<tr>
|
||
<td>end_date</td>
|
||
<td>String</td>
|
||
<td><span class="optional">否</span></td>
|
||
<td>结束日期,格式:YYYY-MM-DD</td>
|
||
</tr>
|
||
<tr>
|
||
<td>page</td>
|
||
<td>Integer</td>
|
||
<td><span class="optional">否</span></td>
|
||
<td>页码,默认:1</td>
|
||
</tr>
|
||
<tr>
|
||
<td>limit</td>
|
||
<td>Integer</td>
|
||
<td><span class="optional">否</span></td>
|
||
<td>每页数量,默认:50,最大:500</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3>请求示例</h3>
|
||
<div class="code-block">
|
||
GET /api/v1/chuku?start_date=2024-02-02&end_date=2026-06-22&page=1&limit=100
|
||
|
||
Headers:
|
||
X-API-Key: 4e2bf206a82f846c699e978cea8c2f87
|
||
X-Timestamp: 1782119268
|
||
X-Sign: 3f1b80b43f2bd83f85fc08ade76515773a58b8673b137b8f77e4e90d3dab9acc
|
||
</div>
|
||
|
||
<h3>响应示例</h3>
|
||
<div class="response-example">
|
||
<div class="response-title">成功响应 (200)</div>
|
||
<div class="code-block" style="background: #f8f9fa; color: #333;">
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"total": 1500,
|
||
"data": [
|
||
{
|
||
"type": "销售登记",
|
||
"ticket_no": "A01C202606160002",
|
||
"register_time": "2026-06-16",
|
||
"customer_code": "001",
|
||
"customer_name": "喀喇沁旗中医蒙医医院",
|
||
"total_amount": 2035.0,
|
||
...
|
||
}
|
||
]
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="response-example" style="background: #f8d7da; border-color: #f5c6cb;">
|
||
<div class="response-title" style="color: #721c24;">错误响应 (500)</div>
|
||
<div class="code-block" style="background: #f8f9fa; color: #333;">
|
||
{
|
||
"code": 500,
|
||
"msg": "数据查询失败: SQL Server 连接不可用"
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section" id="ruku">
|
||
<div class="section-title">入库接口</div>
|
||
|
||
<div class="endpoint">
|
||
<div class="endpoint-header">
|
||
<span class="method">GET</span>
|
||
<span class="url">/api/v1/ruku</span>
|
||
</div>
|
||
<p>查询入库(采购)数据,支持分页和日期范围过滤</p>
|
||
</div>
|
||
|
||
<h3>请求参数</h3>
|
||
<table class="param-table">
|
||
<thead>
|
||
<tr>
|
||
<th>参数名</th>
|
||
<th>类型</th>
|
||
<th>必填</th>
|
||
<th>说明</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>start_date</td>
|
||
<td>String</td>
|
||
<td><span class="optional">否</span></td>
|
||
<td>开始日期,格式:YYYY-MM-DD</td>
|
||
</tr>
|
||
<tr>
|
||
<td>end_date</td>
|
||
<td>String</td>
|
||
<td><span class="optional">否</span></td>
|
||
<td>结束日期,格式:YYYY-MM-DD</td>
|
||
</tr>
|
||
<tr>
|
||
<td>page</td>
|
||
<td>Integer</td>
|
||
<td><span class="optional">否</span></td>
|
||
<td>页码,默认:1</td>
|
||
</tr>
|
||
<tr>
|
||
<td>limit</td>
|
||
<td>Integer</td>
|
||
<td><span class="optional">否</span></td>
|
||
<td>每页数量,默认:50,最大:500</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3>请求示例</h3>
|
||
<div class="code-block">
|
||
GET /api/v1/ruku?start_date=2024-02-02&end_date=2026-06-22&page=1&limit=100
|
||
|
||
Headers:
|
||
X-API-Key: 4e2bf206a82f846c699e978cea8c2f87
|
||
X-Timestamp: 1782119268
|
||
X-Sign: 3f1b80b43f2bd83f85fc08ade76515773a58b8673b137b8f77e4e90d3dab9acc
|
||
</div>
|
||
|
||
<h3>响应示例</h3>
|
||
<div class="response-example">
|
||
<div class="response-title">成功响应 (200)</div>
|
||
<div class="code-block" style="background: #f8f9fa; color: #333;">
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"total": 800,
|
||
"data": [
|
||
{
|
||
"type": "采购入库",
|
||
"ticket_no": "A01Q202606160002",
|
||
"register_time": "2026-06-16",
|
||
"supplier_code": "1533",
|
||
"supplier_name": "通化华夏药业有限责任公司",
|
||
"total_amount": 101890.8,
|
||
...
|
||
}
|
||
]
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="response-example" style="background: #f8d7da; border-color: #f5c6cb;">
|
||
<div class="response-title" style="color: #721c24;">错误响应 (500)</div>
|
||
<div class="code-block" style="background: #f8f9fa; color: #333;">
|
||
{
|
||
"code": 500,
|
||
"msg": "数据查询失败: SQL Server 连接不可用"
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<div class="section-title">常见错误码</div>
|
||
<table class="param-table">
|
||
<thead>
|
||
<tr>
|
||
<th>错误码</th>
|
||
<th>说明</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>200</td>
|
||
<td>请求成功</td>
|
||
</tr>
|
||
<tr>
|
||
<td>401</td>
|
||
<td>未授权,API Key无效或签名错误</td>
|
||
</tr>
|
||
<tr>
|
||
<td>403</td>
|
||
<td>禁止访问,权限不足或数据隔离配置错误</td>
|
||
</tr>
|
||
<tr>
|
||
<td>429</td>
|
||
<td>请求过于频繁,已触发限流</td>
|
||
</tr>
|
||
<tr>
|
||
<td>500</td>
|
||
<td>服务器内部错误,通常是数据库连接或查询失败</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div style="text-align: center; color: #999; margin-top: 40px; padding-bottom: 20px;">
|
||
<p>© 2026 进销存开放平台 | 文档版本 v1.0</p>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html> |