feat: 初始化进销存数据开放平台项目

初始化完整的前后端项目结构,包含ThinkPHP后端API服务和Next.js前端管理系统,添加基础配置、中间件、模型、路由等核心代码,以及项目文档和静态资源。
This commit is contained in:
cenzuhai
2026-07-08 17:48:00 +08:00
parent 6713e13ea6
commit 3a04eede84
99 changed files with 17002 additions and 0 deletions

View File

@@ -0,0 +1,404 @@
<!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>
在参数字符串末尾追加 &timestamp={时间戳}
</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">"&timestamp="</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>