diff --git a/app/Http/Controllers/Mobile/UserController.php b/app/Http/Controllers/Mobile/UserController.php index 2fe7366..f5d37a1 100755 --- a/app/Http/Controllers/Mobile/UserController.php +++ b/app/Http/Controllers/Mobile/UserController.php @@ -16,6 +16,7 @@ use App\Models\CourseSign; use App\Models\RelatedModel; use App\Models\ScoreLog; use App\Models\ThirdAppointmentLog; +use App\Models\Sms; use App\Models\User; use App\Repositories\DoorRepository; use App\Repositories\YuanheRepository; @@ -163,7 +164,7 @@ class UserController extends CommonController */ public function updateUser() { - $all = \request()->all(); + $all = \request()->except(['id','mobile','openid']); $model = User::find($this->getUserId()); if (isset($all['password'])) { // 判断旧密码是否正确 @@ -202,7 +203,8 @@ class UserController extends CommonController ->where('plate', $plate) ->where('plate_status', 1) ->first(); - if ($has) continue; + if ($has) + continue; // 车辆预约 dispatch((new SendAppointCar($appointment, $plate))); } @@ -226,11 +228,15 @@ class UserController extends CommonController public function getUserInfo() { $user = User::with('appointments') - ->withCount(['appointments as pass_appointments' => function ($query) { - $query->whereIn('status', [0, 1]); - }])->with(['courseSigns' => function ($query) { - $query->whereHas('course')->with('course.typeDetail')->where('status', 1)->where('fee_status', 1); - }])->find($this->getUserId()); + ->withCount([ + 'appointments as pass_appointments' => function ($query) { + $query->whereIn('status', [0, 1]); + } + ])->with([ + 'courseSigns' => function ($query) { + $query->whereHas('course')->with('course.typeDetail')->where('status', 1)->where('fee_status', 1); + } + ])->find($this->getUserId()); $doorRepository = new DoorRepository(); $door_appointments = Appointment::where('user_id', $this->getUserId()) @@ -324,6 +330,14 @@ class UserController extends CommonController public function bindMobile() { $all = \request()->all(); + $clientIp = request()->ip(); + + // 检查IP锁定状态 + $lockCheck = Sms::checkIpLock($clientIp, 'bind_mobile'); + if ($lockCheck['locked']) { + return $this->fail([ResponseCode::ERROR_BUSINESS, $lockCheck['message']]); + } + $messages = [ 'mobile.required' => '手机号必填', 'mobile.numeric' => '手机号格式错误', @@ -338,15 +352,29 @@ class UserController extends CommonController if ($validator->fails()) { return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } + $key = 'sms_' . $all['mobile']; $check = Cache::get($key); - if (empty($check)) return $this->fail([ResponseCode::ERROR_BUSINESS, '请先发送验证码']); - if ($check['code'] != $all['code']) return $this->fail([ResponseCode::ERROR_BUSINESS, '验证码错误']); + if (empty($check)) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '请先发送验证码']); + } + if ($check['code'] != $all['code']) { + $errorResult = Sms::recordError($clientIp, 'bind_mobile'); + return $this->fail([ResponseCode::ERROR_BUSINESS, $errorResult['message']]); + } + + // 验证码正确,清除错误计数 + Sms::clearError($clientIp, 'bind_mobile'); + + // 删除验证码缓存 + Cache::forget($key); + // 判断手机号是否存在 $hasMobile = User::where('mobile', $all['mobile'])->where('id', '!=', $this->getUserId())->first(); if ($hasMobile) { return $this->fail([ResponseCode::ERROR_BUSINESS, '手机号已存在']); } + $model = User::find($this->getUserId()); if ($all['is_bind']) { $model->mobile = $all['mobile']; @@ -373,6 +401,14 @@ class UserController extends CommonController public function checkMobile() { $all = \request()->all(); + $clientIp = request()->ip(); + + // 检查IP锁定状态 + $lockCheck = Sms::checkIpLock($clientIp, 'check_mobile'); + if ($lockCheck['locked']) { + return $this->fail([ResponseCode::ERROR_BUSINESS, $lockCheck['message']]); + } + $messages = [ 'mobile.required' => '手机号必填', 'mobile.numeric' => '手机号格式错误', @@ -385,10 +421,24 @@ class UserController extends CommonController if ($validator->fails()) { return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } + $key = 'sms_' . $all['mobile']; $check = Cache::get($key); - if (empty($check)) return $this->fail([ResponseCode::ERROR_BUSINESS, '请先发送验证码']); - if ($check['code'] != $all['code']) return $this->fail([ResponseCode::ERROR_BUSINESS, '验证码错误']); + if (empty($check)) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '请先发送验证码']); + } + + if ($check['code'] != $all['code']) { + $errorResult = Sms::recordError($clientIp, 'check_mobile'); + return $this->fail([ResponseCode::ERROR_BUSINESS, $errorResult['message']]); + } + + // 验证码正确,清除错误计数 + Sms::clearError($clientIp, 'check_mobile'); + + // 删除验证码缓存 + Cache::forget($key); + // 判断手机号是否存在 $hasMobile = User::where('mobile', $all['mobile'])->first(); if ($hasMobile) { @@ -443,7 +493,7 @@ class UserController extends CommonController if (isset($check) && time() - $check['time'] <= 60) { return $this->fail([ResponseCode::ERROR_BUSINESS, '请勿频繁发送']); } - $code = rand(1000, 9999); + $code = rand(100000, 999999); $smsSign = Config::getValueByKey('sms_sign'); $content = "{$smsSign}您的验证码是:{$code},验证码五分钟内有效,如非本人操作,请忽略。"; $result = ymSms($all['mobile'], $content); @@ -540,6 +590,14 @@ class UserController extends CommonController public function mobileLogin() { $all = \request()->all(); + $clientIp = request()->ip(); + + // 检查IP锁定状态 + $lockCheck = Sms::checkIpLock($clientIp, 'mobile_login'); + if ($lockCheck['locked']) { + return $this->fail([ResponseCode::ERROR_BUSINESS, $lockCheck['message']]); + } + $messages = [ 'code.required' => 'code必填', 'mobile.required' => '手机号必填' @@ -551,10 +609,24 @@ class UserController extends CommonController if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } + $key = 'sms_login_' . $all['mobile']; $check = Cache::get($key); - if (empty($check)) return $this->fail([ResponseCode::ERROR_BUSINESS, '请先发送验证码']); - if ($check['code'] != $all['code']) return $this->fail([ResponseCode::ERROR_BUSINESS, '验证码错误']); + if (empty($check)) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '请先发送验证码']); + } + + if ($check['code'] != $all['code']) { + $errorResult = Sms::recordError($clientIp, 'mobile_login'); + return $this->fail([ResponseCode::ERROR_BUSINESS, $errorResult['message']]); + } + + // 验证码正确,清除错误计数 + Sms::clearError($clientIp, 'mobile_login'); + + // 删除验证码缓存 + Cache::forget($key); + $user = User::where('mobile', $all['mobile'])->first(); $token = $user->createToken("mobile-token")->plainTextToken; return $this->success(compact('token')); @@ -620,7 +692,7 @@ class UserController extends CommonController if (isset($check) && time() - $check['time'] <= 60) { return $this->fail([ResponseCode::ERROR_BUSINESS, '请勿频繁发送']); } - $code = rand(1000, 9999); + $code = rand(100000, 999999); $smsSign = Config::getValueByKey('sms_sign'); $content = "{$smsSign}您的验证码是:{$code},验证码五分钟内有效,如非本人操作,请忽略。"; $result = ymSms($all['mobile'], $content); diff --git a/app/Models/Sms.php b/app/Models/Sms.php index 9c42cb9..9e8286c 100755 --- a/app/Models/Sms.php +++ b/app/Models/Sms.php @@ -1,15 +1,76 @@ bool, 'message' => string] + */ + public static function checkIpLock($ip, $type = 'bind_mobile') + { + $ipLockKey = 'sms_ip_lock_' . $type . '_' . $ip; + + if (Cache::has($ipLockKey)) { + $lockTime = Cache::get($ipLockKey); + $remainingTime = $lockTime - time(); + + if ($remainingTime > 0) { + $minutes = ceil($remainingTime / 60); + return [ + 'locked' => true, + 'message' => "访问过于频繁,请{$minutes}分钟后再试" + ]; + } + } - public function belongs() { - return $this->morphTo(); + return ['locked' => false, 'message' => '']; } -} + /** + * 记录验证码错误 + * + * @param string $ip 客户端IP地址 + * @param string $type 方法类型 (bind_mobile/check_mobile) + * @return array ['locked' => bool, 'message' => string] + */ + public static function recordError($ip, $type = 'bind_mobile') + { + $errorKey = 'sms_error_count_' . $type . '_' . $ip; + $ipLockKey = 'sms_ip_lock_' . $type . '_' . $ip; + + $errorCount = Cache::get($errorKey, 0) + 1; + Cache::put($errorKey, $errorCount, 3600); // 1小时过期 + + // 如果错误次数达到10次,锁定IP + if ($errorCount >= 10) { + Cache::put($ipLockKey, time() + 3600, 3600); // 锁定1小时 + Cache::forget($errorKey); // 清除错误计数 + + return [ + 'locked' => true, + 'message' => '验证码错误次数过多,请1小时后再试' + ]; + } + + return ['locked' => false, 'message' => '验证码错误']; + } + + /** + * 清除错误计数 + * + * @param string $ip 客户端IP地址 + * @param string $type 方法类型 (bind_mobile/check_mobile) + */ + public static function clearError($ip, $type = 'bind_mobile') + { + $errorKey = 'sms_error_count_' . $type . '_' . $ip; + Cache::forget($errorKey); + } +}