feat: 初始化进销存数据开放平台项目
初始化完整的前后端项目结构,包含ThinkPHP后端API服务和Next.js前端管理系统,添加基础配置、中间件、模型、路由等核心代码,以及项目文档和静态资源。
This commit is contained in:
55
thinkphp/app/middleware/AdminAuth.php
Normal file
55
thinkphp/app/middleware/AdminAuth.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace app\middleware;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use think\Request;
|
||||
use think\Response;
|
||||
|
||||
/**
|
||||
* 管理员认证中间件
|
||||
*/
|
||||
class AdminAuth
|
||||
{
|
||||
/**
|
||||
* 处理请求
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next)
|
||||
{
|
||||
$token = $request->header('Authorization');
|
||||
|
||||
if (empty($token)) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'msg' => '未提供认证令牌'
|
||||
], 401);
|
||||
}
|
||||
|
||||
// 移除 "Bearer " 前缀
|
||||
if (strpos($token, 'Bearer ') === 0) {
|
||||
$token = substr($token, 7);
|
||||
}
|
||||
|
||||
try {
|
||||
$secretKey = env('jwt.secret_key', 'your-super-secret-key-change-in-production-32chars!');
|
||||
$algorithm = env('jwt.algorithm', 'HS256');
|
||||
|
||||
$decoded = JWT::decode($token, new Key($secretKey, $algorithm));
|
||||
$payload = (array)$decoded;
|
||||
|
||||
// 将管理员信息存入请求
|
||||
$request->adminUser = [
|
||||
'id' => $payload['sub'],
|
||||
'username' => $payload['username'],
|
||||
];
|
||||
|
||||
return $next($request);
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'msg' => '无效的认证令牌'
|
||||
], 401);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
thinkphp/app/middleware/Auth.php
Normal file
92
thinkphp/app/middleware/Auth.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace app\middleware;
|
||||
|
||||
use app\model\ApiKey;
|
||||
use app\model\Client;
|
||||
use app\model\Permission;
|
||||
use app\service\Signature;
|
||||
use think\Request;
|
||||
use think\Response;
|
||||
|
||||
/**
|
||||
* API鉴权中间件
|
||||
*/
|
||||
class Auth
|
||||
{
|
||||
/**
|
||||
* 处理请求
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next)
|
||||
{
|
||||
// 1. 提取必要Headers
|
||||
$apiKey = $request->header('X-API-Key');
|
||||
$timestamp = $request->header('X-Timestamp');
|
||||
$sign = $request->header('X-Sign');
|
||||
|
||||
if (empty($apiKey) || empty($timestamp) || empty($sign)) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'msg' => '缺少鉴权头: 需要 X-API-Key, X-Timestamp, X-Sign'
|
||||
], 401);
|
||||
}
|
||||
|
||||
// 2. 查询API Key(连带查询client和permission)
|
||||
$apiKeyObj = ApiKey::with(['client', 'permission'])
|
||||
->where('api_key', $apiKey)
|
||||
->find();
|
||||
|
||||
if (!$apiKeyObj) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'msg' => '无效的 API Key'
|
||||
], 401);
|
||||
}
|
||||
|
||||
// 3. 检查状态
|
||||
if ($apiKeyObj->status != ApiKey::STATUS_ENABLED) {
|
||||
return json([
|
||||
'code' => 403,
|
||||
'msg' => 'API Key 已被禁用'
|
||||
], 403);
|
||||
}
|
||||
|
||||
$clientObj = $apiKeyObj->client;
|
||||
if (!$clientObj || $clientObj->status != Client::STATUS_ENABLED) {
|
||||
return json([
|
||||
'code' => 403,
|
||||
'msg' => '客户账号已被停用,API 访问被拒绝'
|
||||
], 403);
|
||||
}
|
||||
|
||||
// 4. 验证签名
|
||||
$params = $request->param();
|
||||
if (!Signature::verify($params, $timestamp, $sign, $apiKeyObj->api_secret_hash)) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'msg' => '签名验证失败'
|
||||
], 401);
|
||||
}
|
||||
|
||||
// 5. 检查权限配置
|
||||
$permission = Permission::where('api_key_id', $apiKeyObj->id)->find();
|
||||
if (!$permission) {
|
||||
return json([
|
||||
'code' => 403,
|
||||
'msg' => '未配置 API 权限,请联系管理员'
|
||||
], 403);
|
||||
}
|
||||
// 6. 将鉴权上下文存入请求
|
||||
$request->authContext = [
|
||||
'api_key_id' => $apiKeyObj->id,
|
||||
'api_key' => $apiKeyObj->api_key,
|
||||
'client_id' => $clientObj->id,
|
||||
'client_name' => $clientObj->name,
|
||||
'permission' => $permission,
|
||||
'rate_limit_per_minute' => $apiKeyObj->rate_limit_per_minute,
|
||||
'rate_limit_per_day' => $apiKeyObj->rate_limit_per_day,
|
||||
];
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
24
thinkphp/app/middleware/CrossDomain.php
Normal file
24
thinkphp/app/middleware/CrossDomain.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace app\middleware;
|
||||
|
||||
class CrossDomain
|
||||
{
|
||||
public function handle($request, \Closure $next)
|
||||
{
|
||||
$header = [
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
||||
'Access-Control-Allow-Headers' => 'Content-Type,Authorization,Token,X-Requested-With,X-API-Key,X-Timestamp,X-Sign',
|
||||
'Access-Control-Max-Age' => 1800,
|
||||
];
|
||||
|
||||
if ($request->method() == 'OPTIONS') {
|
||||
return response('', 204)->header($header);
|
||||
}
|
||||
|
||||
$response = $next($request);
|
||||
$response->header($header);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
67
thinkphp/app/middleware/RateLimit.php
Normal file
67
thinkphp/app/middleware/RateLimit.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace app\middleware;
|
||||
|
||||
use think\facade\Cache;
|
||||
use think\Request;
|
||||
|
||||
class RateLimit
|
||||
{
|
||||
public function handle(Request $request, \Closure $next)
|
||||
{
|
||||
$authContext = $request->authContext ?? null;
|
||||
if (!$authContext) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$apiKeyId = $authContext['api_key_id'];
|
||||
$limitPerMinute = $authContext['rate_limit_per_minute'] ?? 60;
|
||||
$limitPerDay = $authContext['rate_limit_per_day'] ?? 5000;
|
||||
|
||||
$minuteKey = 'rate_limit:minute:' . $apiKeyId;
|
||||
$dayKey = 'rate_limit:day:' . $apiKeyId . ':' . date('Ymd');
|
||||
|
||||
$minuteCount = $this->getCount($minuteKey);
|
||||
if ($minuteCount >= $limitPerMinute) {
|
||||
return json([
|
||||
'code' => 429,
|
||||
'msg' => '请求过于频繁,请稍后再试'
|
||||
], 429);
|
||||
}
|
||||
|
||||
$dayCount = $this->getCount($dayKey);
|
||||
if ($dayCount >= $limitPerDay) {
|
||||
return json([
|
||||
'code' => 429,
|
||||
'msg' => '今日请求次数已达上限'
|
||||
], 429);
|
||||
}
|
||||
|
||||
$this->increment($minuteKey, 60);
|
||||
$this->increment($dayKey, 86400);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function getCount($key)
|
||||
{
|
||||
try {
|
||||
$value = Cache::get($key);
|
||||
return $value !== false ? (int)$value : 0;
|
||||
} catch (\Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function increment($key, $ttl)
|
||||
{
|
||||
try {
|
||||
$current = $this->getCount($key);
|
||||
$newValue = $current + 1;
|
||||
Cache::set($key, $newValue, $ttl);
|
||||
return $newValue;
|
||||
} catch (\Exception $e) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user