bianjunhui 4 дней назад
Родитель
Сommit
d6b22f65be

+ 29 - 0
packages/Longyi/Wholesale/README.md

@@ -161,6 +161,35 @@ Content-Type: application/json
 }
 ```
 
+### 防恶意提交
+
+接口内置了双重防护,防止恶意重复提交:
+
+1. **IP 限流**:
+   - 同一 IP 在 60 秒内最多提交 1 次;
+   - 同一 IP 在 24 小时内最多提交 5 次;
+   - 超出任一限制返回 429。
+
+```json
+{
+    "success": false,
+    "message": "提交过于频繁,请稍后再试。",
+    "data": []
+}
+```
+
+2. **重复提交检测**:同一 IP + 同一邮箱在 60 秒内只允许提交一次,重复提交返回 429。
+
+```json
+{
+    "success": false,
+    "message": "您已提交过询盘,请勿重复提交。",
+    "data": []
+}
+```
+
+> 阈值与窗口期通过 `WholesaleController` 中的常量 `MAX_ATTEMPTS`、`DECAY_SECONDS`、`DUPLICATE_SECONDS` 配置。
+
 ### 统一返回格式
 
 本模块的 API 统一使用 `Longyi\Wholesale\Helpers\ApiResponse` 返回,格式与 `RewardPoints` 模块一致:

+ 68 - 0
packages/Longyi/Wholesale/src/Http/Controllers/WholesaleController.php

@@ -4,11 +4,37 @@ namespace Longyi\Wholesale\Http\Controllers;
 
 use Illuminate\Http\Request;
 use Illuminate\Routing\Controller;
+use Illuminate\Support\Facades\RateLimiter;
 use Longyi\Wholesale\Helpers\ApiResponse;
 use Longyi\Wholesale\Models\Message;
 
 class WholesaleController extends Controller
 {
+    /**
+     * 短窗口:同一 IP 允许的最大提交次数(60 秒内最多 1 次)。
+     */
+    const MAX_ATTEMPTS = 1;
+
+    /**
+     * 短窗口时长(秒)。
+     */
+    const DECAY_SECONDS = 60;
+
+    /**
+     * 长窗口:同一 IP 允许的最大提交次数(24 小时内最多 5 次)。
+     */
+    const DAILY_MAX_ATTEMPTS = 5;
+
+    /**
+     * 长窗口时长(秒):24 小时。
+     */
+    const DAILY_DECAY_SECONDS = 86400;
+
+    /**
+     * 重复提交去重窗口(秒):同一 IP 相同邮箱在该时间内只允许提交一次。
+     */
+    const DUPLICATE_SECONDS = 60;
+
     /**
      * Store a new wholesale enquiry.
      *
@@ -16,6 +42,45 @@ class WholesaleController extends Controller
      */
     public function store(Request $request)
     {
+        $ip = $request->ip();
+
+        // 1. IP 级限流,防止高频恶意提交。
+        //    短窗口:60 秒内最多 1 次。
+        $throttleKey = 'wholesale-submit:'.$ip;
+
+        if (RateLimiter::tooManyAttempts($throttleKey, self::MAX_ATTEMPTS)) {
+            return ApiResponse::error(
+                trans('wholesale::app.shop.too-many-attempts'),
+                [],
+                429
+            );
+        }
+
+        //    长窗口:24 小时内最多 5 次。
+        $dailyThrottleKey = 'wholesale-submit-daily:'.$ip;
+
+        if (RateLimiter::tooManyAttempts($dailyThrottleKey, self::DAILY_MAX_ATTEMPTS)) {
+            return ApiResponse::error(
+                trans('wholesale::app.shop.too-many-attempts'),
+                [],
+                429
+            );
+        }
+
+        RateLimiter::hit($throttleKey, self::DECAY_SECONDS);
+        RateLimiter::hit($dailyThrottleKey, self::DAILY_DECAY_SECONDS);
+
+        // 2. 重复提交检测:同一 IP + 邮箱在窗口期内只允许提交一次。
+        $duplicateKey = 'wholesale-dup:'.md5($ip.'|'.strtolower((string) $request->input('email')));
+
+        if (cache()->has($duplicateKey)) {
+            return ApiResponse::error(
+                trans('wholesale::app.shop.duplicate-submission'),
+                [],
+                429
+            );
+        }
+
         try {
             $validated = $request->validate([
                 'name'          => 'required|string|max:200',
@@ -37,6 +102,9 @@ class WholesaleController extends Controller
                 'business_role' => $validated['business_role'] ?? '',
             ]);
 
+            // 记录去重标记,窗口期内重复提交会被拒绝。
+            cache()->put($duplicateKey, $message->message_id, self::DUPLICATE_SECONDS);
+
             return ApiResponse::success([
                 'id' => $message->message_id,
             ], trans('wholesale::app.shop.success'));

+ 3 - 1
packages/Longyi/Wholesale/src/Resources/lang/en/app.php

@@ -2,7 +2,9 @@
 
 return [
     'shop' => [
-        'success' => 'Your enquiry has been submitted successfully.',
+        'success'              => 'Your enquiry has been submitted successfully.',
+        'too-many-attempts'    => 'Too many submissions. Please try again later.',
+        'duplicate-submission' => 'You have already submitted an enquiry. Please wait before submitting again.',
     ],
 
     'admin' => [

+ 3 - 1
packages/Longyi/Wholesale/src/Resources/lang/zh_CN/app.php

@@ -2,7 +2,9 @@
 
 return [
     'shop' => [
-        'success' => '您的询盘已提交成功。',
+        'success'              => '您的询盘已提交成功。',
+        'too-many-attempts'    => '提交过于频繁,请稍后再试。',
+        'duplicate-submission' => '您已提交过询盘,请勿重复提交。',
     ],
 
     'admin' => [