WholesaleController.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. <?php
  2. namespace Longyi\Wholesale\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Routing\Controller;
  5. use Illuminate\Support\Facades\RateLimiter;
  6. use Longyi\Wholesale\Helpers\ApiResponse;
  7. use Longyi\Wholesale\Models\Message;
  8. class WholesaleController extends Controller
  9. {
  10. /**
  11. * 短窗口:同一 IP 允许的最大提交次数(60 秒内最多 1 次)。
  12. */
  13. const MAX_ATTEMPTS = 1;
  14. /**
  15. * 短窗口时长(秒)。
  16. */
  17. const DECAY_SECONDS = 60;
  18. /**
  19. * 长窗口:同一 IP 允许的最大提交次数(24 小时内最多 5 次)。
  20. */
  21. const DAILY_MAX_ATTEMPTS = 5;
  22. /**
  23. * 长窗口时长(秒):24 小时。
  24. */
  25. const DAILY_DECAY_SECONDS = 86400;
  26. /**
  27. * 重复提交去重窗口(秒):同一 IP 相同邮箱在该时间内只允许提交一次。
  28. */
  29. const DUPLICATE_SECONDS = 60;
  30. /**
  31. * Store a new wholesale enquiry.
  32. *
  33. * POST /api/wholesale
  34. */
  35. public function store(Request $request)
  36. {
  37. $ip = $request->ip();
  38. // 1. IP 级限流,防止高频恶意提交。
  39. // 短窗口:60 秒内最多 1 次。
  40. $throttleKey = 'wholesale-submit:'.$ip;
  41. if (RateLimiter::tooManyAttempts($throttleKey, self::MAX_ATTEMPTS)) {
  42. return ApiResponse::error(
  43. trans('wholesale::app.shop.too-many-attempts'),
  44. [],
  45. 429
  46. );
  47. }
  48. // 长窗口:24 小时内最多 5 次。
  49. $dailyThrottleKey = 'wholesale-submit-daily:'.$ip;
  50. if (RateLimiter::tooManyAttempts($dailyThrottleKey, self::DAILY_MAX_ATTEMPTS)) {
  51. return ApiResponse::error(
  52. trans('wholesale::app.shop.too-many-attempts'),
  53. [],
  54. 429
  55. );
  56. }
  57. RateLimiter::hit($throttleKey, self::DECAY_SECONDS);
  58. RateLimiter::hit($dailyThrottleKey, self::DAILY_DECAY_SECONDS);
  59. // 2. 重复提交检测:同一 IP + 邮箱在窗口期内只允许提交一次。
  60. $duplicateKey = 'wholesale-dup:'.md5($ip.'|'.strtolower((string) $request->input('email')));
  61. if (cache()->has($duplicateKey)) {
  62. return ApiResponse::error(
  63. trans('wholesale::app.shop.duplicate-submission'),
  64. [],
  65. 429
  66. );
  67. }
  68. try {
  69. $validated = $request->validate([
  70. 'name' => 'required|string|max:200',
  71. 'email' => 'required|email|max:50',
  72. 'number' => 'nullable|string|max:300',
  73. 'message' => 'nullable|string|max:600',
  74. 'country' => 'nullable|string|max:50',
  75. 'business_name' => 'nullable|string|max:300',
  76. 'business_role' => 'nullable|string|max:50',
  77. ]);
  78. $message = Message::create([
  79. 'name' => $validated['name'],
  80. 'Email' => $validated['email'],
  81. 'number' => $validated['number'] ?? null,
  82. 'message' => $validated['message'] ?? null,
  83. 'country' => $validated['country'] ?? null,
  84. 'business_name' => $validated['business_name'] ?? '',
  85. 'business_role' => $validated['business_role'] ?? '',
  86. ]);
  87. // 记录去重标记,窗口期内重复提交会被拒绝。
  88. cache()->put($duplicateKey, $message->message_id, self::DUPLICATE_SECONDS);
  89. return ApiResponse::success([
  90. 'id' => $message->message_id,
  91. ], trans('wholesale::app.shop.success'));
  92. } catch (\Exception $e) {
  93. return ApiResponse::error($e->getMessage());
  94. }
  95. }
  96. }