LoginController.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\api\controller\v1;
  12. use app\Request;
  13. use app\services\message\notice\SmsService;
  14. use app\services\wechat\WechatServices;
  15. use think\facade\Config;
  16. use crmeb\services\CacheService;
  17. use app\services\user\LoginServices;
  18. use think\exception\ValidateException;
  19. use app\api\validate\user\RegisterValidates;
  20. use think\facade\Env;
  21. /**
  22. * 微信小程序授权类
  23. * Class AuthController
  24. * @package app\api\controller
  25. */
  26. class LoginController
  27. {
  28. protected $services;
  29. /**
  30. * LoginController constructor.
  31. * @param LoginServices $services
  32. */
  33. public function __construct(LoginServices $services)
  34. {
  35. $this->services = $services;
  36. }
  37. /**
  38. * H5账号登陆
  39. * @param Request $request
  40. * @return mixed
  41. * @throws \think\db\exception\DataNotFoundException
  42. * @throws \think\db\exception\ModelNotFoundException
  43. * @throws \think\exception\DbException
  44. */
  45. public function login(Request $request)
  46. {
  47. [$account, $password, $spread, $agent_id] = $request->postMore([
  48. 'account', 'password', 'spread', ['agent_id', 0]
  49. ], true);
  50. if (!$account || !$password) {
  51. return app('json')->fail('请输入账号和密码');
  52. }
  53. if (strlen(trim($password)) < 6 || strlen(trim($password)) > 32) {
  54. return app('json')->fail('账号密码必须是在6到32位之间');
  55. }
  56. return app('json')->success('登录成功', $this->services->login($account, $password, $spread, $agent_id));
  57. }
  58. /**
  59. * 退出登录
  60. * @param Request $request
  61. * @return mixed
  62. */
  63. public function logout(Request $request)
  64. {
  65. $key = trim(ltrim($request->header(Config::get('cookie.token_name')), 'Bearer'));
  66. CacheService::delete(md5($key));
  67. return app('json')->success('退出成功');
  68. }
  69. /**
  70. * 获取发送验证码key
  71. * @return mixed
  72. */
  73. public function verifyCode()
  74. {
  75. $unique = password_hash(uniqid(true), PASSWORD_BCRYPT);
  76. CacheService::set('sms.key.' . $unique, 0, 300);
  77. $time = sys_config('verify_expire_time', 1);
  78. return app('json')->success(['key' => $unique, 'expire_time' => $time]);
  79. }
  80. /**
  81. * 获取图片验证码
  82. * @param Request $request
  83. * @return \think\Response
  84. */
  85. public function captcha(Request $request)
  86. {
  87. ob_clean();
  88. $rep = captcha();
  89. $key = app('session')->get('captcha.key');
  90. $uni = $request->get('key');
  91. if ($uni) {
  92. CacheService::set('sms.key.cap.' . $uni, $key, 300);
  93. }
  94. return $rep;
  95. }
  96. /**
  97. * 验证验证码是否正确
  98. * @param $uni
  99. * @param string $code
  100. * @return bool
  101. * @throws \Psr\SimpleCache\InvalidArgumentException
  102. */
  103. protected function checkCaptcha($uni, string $code): bool
  104. {
  105. $cacheName = 'sms.key.cap.' . $uni;
  106. if (!CacheService::has($cacheName)) {
  107. return false;
  108. }
  109. $key = CacheService::get($cacheName);
  110. $code = mb_strtolower($code, 'UTF-8');
  111. $res = password_verify($code, $key);
  112. if ($res) {
  113. CacheService::delete($cacheName);
  114. }
  115. return $res;
  116. }
  117. /**
  118. * 验证码发送
  119. * @param Request $request
  120. * @param SmsService $services
  121. * @return mixed
  122. */
  123. public function verify(Request $request, SmsService $services)
  124. {
  125. [$phone, $type, $key, $captchaType, $captchaVerification] = $request->postMore([
  126. ['phone', 0],
  127. ['type', ''],
  128. ['key', ''],
  129. ['captchaType', ''],
  130. ['captchaVerification', ''],
  131. ], true);
  132. $keyName = 'sms.key.' . $key;
  133. if (!CacheService::has($keyName)) return app('json')->fail('发送验证码失败,请刷新页面重新获取');
  134. // 验证限制
  135. // 验证码每分钟发送上限
  136. $maxMinuteCountKey = 'sms.minute.' . $phone . date('YmdHi');
  137. $minuteCount = 0;
  138. if (CacheService::has($maxMinuteCountKey)) {
  139. $minuteCount = CacheService::get($maxMinuteCountKey) ?? 0;
  140. $maxMinuteCount = Config::get('sms.maxMinuteCount', 5);
  141. if ($minuteCount > $maxMinuteCount) return app('json')->fail('同一手机号每分钟最多发送' . $maxMinuteCount . '条');
  142. }
  143. // 验证码单个手机每日发送上限
  144. $maxPhoneCountKey = 'sms.phone.' . $phone . '.' . date('Ymd');
  145. $phoneCount = 0;
  146. if (CacheService::has($maxPhoneCountKey)) {
  147. $phoneCount = CacheService::get($maxPhoneCountKey) ?? 0;
  148. $maxPhoneCount = Config::get('sms.maxPhoneCount', 20);
  149. if ($phoneCount > $maxPhoneCount) return app('json')->fail('同一手机号每天最多发送' . $maxPhoneCount . '条');
  150. }
  151. // 验证码单个手机每日发送上限
  152. $maxIpCountKey = 'sms.ip.' . app()->request->ip() . '.' . date('Ymd');
  153. $ipCount = 0;
  154. if (CacheService::has($maxIpCountKey)) {
  155. $ipCount = CacheService::get($maxIpCountKey) ?? 0;
  156. $maxIpCount = Config::get('sms.maxIpCount', 50);
  157. if ($ipCount > $maxIpCount) return app('json')->fail('同一IP每天最多发送' . $maxIpCount . '条');
  158. }
  159. //二次验证
  160. try {
  161. aj_captcha_check_two($captchaType, $captchaVerification);
  162. } catch (\Throwable $e) {
  163. return app('json')->fail($e->getMessage());
  164. }
  165. try {
  166. validate(RegisterValidates::class)->scene('code')->check(['phone' => $phone]);
  167. } catch (ValidateException $e) {
  168. return app('json')->fail($e->getMessage());
  169. }
  170. $time = sys_config('verify_expire_time', 1);
  171. $smsCode = $this->services->verify($services, $phone, $type, $time);
  172. if ($smsCode) {
  173. CacheService::set('code_' . $phone, $smsCode, $time * 60);
  174. CacheService::set($maxMinuteCountKey, (int)$minuteCount + 1, 61);
  175. CacheService::set($maxPhoneCountKey, (int)$phoneCount + 1, 86401);
  176. CacheService::set($maxIpCountKey, (int)$ipCount + 1, 86401);
  177. return app('json')->success('验证码发送成功');
  178. } else {
  179. return app('json')->fail('验证码发送失败');
  180. }
  181. }
  182. /**
  183. * H5注册新用户
  184. * @param Request $request
  185. * @return mixed
  186. * @throws \think\db\exception\DataNotFoundException
  187. * @throws \think\db\exception\DbException
  188. * @throws \think\db\exception\ModelNotFoundException
  189. */
  190. public function register(Request $request)
  191. {
  192. [$account, $captcha, $password, $spread] = $request->postMore([['account', ''], ['captcha', ''], ['password', ''], ['spread', 0]], true);
  193. try {
  194. validate(RegisterValidates::class)->scene('register')->check(['account' => $account, 'captcha' => $captcha, 'password' => $password]);
  195. } catch (ValidateException $e) {
  196. return app('json')->fail($e->getError());
  197. }
  198. if (strlen(trim($password)) < 6 || strlen(trim($password)) > 32) {
  199. return app('json')->fail('账号密码必须是在6到32位之间');
  200. }
  201. $verifyCode = CacheService::get('code_' . $account);
  202. if (!$verifyCode)
  203. return app('json')->fail('请先获取验证码');
  204. $verifyCode = substr($verifyCode, 0, 6);
  205. if ($verifyCode != $captcha)
  206. return app('json')->fail('验证码错误');
  207. if (md5($password) == md5('123456')) return app('json')->fail('密码太过简单,请输入较为复杂的密码');
  208. $registerStatus = $this->services->register($account, $password, $spread, 'h5');
  209. if ($registerStatus) {
  210. return app('json')->success('注册成功');
  211. }
  212. return app('json')->fail('注册失败');
  213. }
  214. /**
  215. * 密码修改
  216. * @param Request $request
  217. * @return mixed
  218. * @throws \think\db\exception\DataNotFoundException
  219. * @throws \think\db\exception\DbException
  220. * @throws \think\db\exception\ModelNotFoundException
  221. */
  222. public function reset(Request $request)
  223. {
  224. [$account, $captcha, $password] = $request->postMore([['account', ''], ['captcha', ''], ['password', '']], true);
  225. try {
  226. validate(RegisterValidates::class)->scene('register')->check(['account' => $account, 'captcha' => $captcha, 'password' => $password]);
  227. } catch (ValidateException $e) {
  228. return app('json')->fail($e->getError());
  229. }
  230. if (strlen(trim($password)) < 6 || strlen(trim($password)) > 32) {
  231. return app('json')->fail('账号密码必须是在6到32位之间');
  232. }
  233. $verifyCode = CacheService::get('code_' . $account);
  234. if (!$verifyCode)
  235. return app('json')->fail('请先获取验证码');
  236. $verifyCode = substr($verifyCode, 0, 6);
  237. if ($verifyCode != $captcha) {
  238. return app('json')->fail('验证码错误');
  239. }
  240. if ($password == '123456') return app('json')->fail('密码太过简单,请输入较为复杂的密码');
  241. $resetStatus = $this->services->reset($account, $password);
  242. if ($resetStatus) return app('json')->success('修改成功');
  243. return app('json')->fail('修改失败');
  244. }
  245. /**
  246. * 手机号登录
  247. * @param Request $request
  248. * @return mixed
  249. * @throws \think\db\exception\DataNotFoundException
  250. * @throws \think\db\exception\ModelNotFoundException
  251. * @throws \think\exception\DbException
  252. */
  253. public function mobile(Request $request)
  254. {
  255. [$phone, $captcha, $spread, $agent_id] = $request->postMore([['phone', ''], ['captcha', ''], ['spread', 0], ['agent_id', 0]], true);
  256. //验证手机号
  257. try {
  258. validate(RegisterValidates::class)->scene('code')->check(['phone' => $phone]);
  259. } catch (ValidateException $e) {
  260. return app('json')->fail($e->getError());
  261. }
  262. //验证验证码
  263. $verifyCode = CacheService::get('code_' . $phone);
  264. if (!$verifyCode)
  265. return app('json')->fail('请先获取验证码');
  266. $verifyCode = substr($verifyCode, 0, 6);
  267. if ($verifyCode != $captcha) {
  268. return app('json')->fail('验证码错误');
  269. }
  270. $user_type = $request->getFromType() ? $request->getFromType() : 'h5';
  271. $token = $this->services->mobile($phone, $spread, $user_type, $agent_id);
  272. if ($token) {
  273. CacheService::delete('code_' . $phone);
  274. return app('json')->success('登录成功', $token);
  275. } else {
  276. return app('json')->fail('退出成功');
  277. }
  278. }
  279. /**
  280. * H5切换登陆
  281. * @param Request $request
  282. * @return mixed
  283. * @throws \think\db\exception\DataNotFoundException
  284. * @throws \think\db\exception\ModelNotFoundException
  285. * @throws \think\exception\DbException
  286. */
  287. public function switch_h5(Request $request)
  288. {
  289. $from = $request->post('from', 'wechat');
  290. $user = $request->user();
  291. $token = $this->services->switchAccount($user, $from);
  292. if ($token) {
  293. $token['userInfo'] = $user;
  294. return app('json')->success('登录成功', $token);
  295. } else
  296. return app('json')->fail('退出成功');
  297. }
  298. /**
  299. * 绑定手机号
  300. * @param Request $request
  301. * @return mixed
  302. * @throws \think\db\exception\DataNotFoundException
  303. * @throws \think\db\exception\ModelNotFoundException
  304. * @throws \think\exception\DbException
  305. */
  306. public function binding_phone(Request $request)
  307. {
  308. list($phone, $captcha, $key) = $request->postMore([
  309. ['phone', ''],
  310. ['captcha', ''],
  311. ['key', '']
  312. ], true);
  313. //验证手机号
  314. try {
  315. validate(RegisterValidates::class)->scene('code')->check(['phone' => $phone]);
  316. } catch (ValidateException $e) {
  317. return app('json')->fail($e->getError());
  318. }
  319. if (!$key) {
  320. return app('json')->fail('参数错误');
  321. }
  322. if (!$phone) {
  323. return app('json')->fail('请输入手机号');
  324. }
  325. //验证验证码
  326. $verifyCode = CacheService::get('code_' . $phone);
  327. if (!$verifyCode)
  328. return app('json')->fail('请先获取验证码');
  329. $verifyCode = substr($verifyCode, 0, 6);
  330. if ($verifyCode != $captcha) {
  331. return app('json')->fail('验证码错误');
  332. }
  333. $re = $this->services->bindind_phone($phone, $key);
  334. if ($re) {
  335. CacheService::delete('code_' . $phone);
  336. return app('json')->success('绑定成功', $re);
  337. } else
  338. return app('json')->fail('绑定失败');
  339. }
  340. /**
  341. * 绑定手机号
  342. * @param Request $request
  343. * @return mixed
  344. * @throws \think\db\exception\DataNotFoundException
  345. * @throws \think\db\exception\ModelNotFoundException
  346. * @throws \think\exception\DbException
  347. */
  348. public function user_binding_phone(Request $request)
  349. {
  350. list($phone, $captcha, $step) = $request->postMore([
  351. ['phone', ''],
  352. ['captcha', ''],
  353. ['step', 0]
  354. ], true);
  355. //验证手机号
  356. try {
  357. validate(RegisterValidates::class)->scene('code')->check(['phone' => $phone]);
  358. } catch (ValidateException $e) {
  359. return app('json')->fail($e->getError());
  360. }
  361. if (!$step) {
  362. //验证验证码
  363. $verifyCode = CacheService::get('code_' . $phone);
  364. if (!$verifyCode)
  365. return app('json')->fail('请先获取验证码');
  366. $verifyCode = substr($verifyCode, 0, 6);
  367. if ($verifyCode != $captcha)
  368. return app('json')->fail('验证码错误');
  369. }
  370. $uid = (int)$request->uid();
  371. $re = $this->services->userBindindPhone($uid, $phone, $step);
  372. if ($re) {
  373. CacheService::delete('code_' . $phone);
  374. return app('json')->success($re['msg'] ?? '绑定成功', $re['data'] ?? []);
  375. } else
  376. return app('json')->fail('绑定失败');
  377. }
  378. public function update_binding_phone(Request $request)
  379. {
  380. [$phone, $captcha] = $request->postMore([
  381. ['phone', ''],
  382. ['captcha', ''],
  383. ], true);
  384. //验证手机号
  385. try {
  386. validate(RegisterValidates::class)->scene('code')->check(['phone' => $phone]);
  387. } catch (ValidateException $e) {
  388. return app('json')->fail($e->getError());
  389. }
  390. //验证验证码
  391. $verifyCode = CacheService::get('code_' . $phone);
  392. if (!$verifyCode)
  393. return app('json')->fail('请先获取验证码');
  394. $verifyCode = substr($verifyCode, 0, 6);
  395. if ($verifyCode != $captcha)
  396. return app('json')->fail('验证码错误');
  397. $uid = (int)$request->uid();
  398. $re = $this->services->updateBindindPhone($uid, $phone);
  399. if ($re) {
  400. CacheService::delete('code_' . $phone);
  401. return app('json')->success($re['msg'] ?? '修改成功', $re['data'] ?? []);
  402. } else
  403. return app('json')->fail('修改失败');
  404. }
  405. /**
  406. * 设置扫描二维码状态
  407. * @param string $code
  408. * @return mixed
  409. */
  410. public function setLoginKey(string $code)
  411. {
  412. if (!$code) {
  413. return app('json')->fail('扫码失败请重新扫描');
  414. }
  415. $cacheCode = CacheService::get($code);
  416. if ($cacheCode === false || $cacheCode === null) {
  417. return app('json')->fail('二维码已过期请重新扫描');
  418. }
  419. CacheService::set($code, '0', 600);
  420. return app('json')->success();
  421. }
  422. /**
  423. * apple快捷登陆
  424. * @param Request $request
  425. * @param WechatServices $services
  426. * @return mixed
  427. * @throws \Psr\SimpleCache\InvalidArgumentException
  428. * @throws \think\db\exception\DataNotFoundException
  429. * @throws \think\db\exception\ModelNotFoundException
  430. */
  431. public function appleLogin(Request $request, WechatServices $services)
  432. {
  433. [$openId, $phone, $email, $captcha] = $request->postMore([
  434. ['openId', ''],
  435. ['phone', ''],
  436. ['email', ''],
  437. ['captcha', '']
  438. ], true);
  439. if ($phone) {
  440. if (!$captcha) {
  441. return app('json')->fail('请输入验证码');
  442. }
  443. //验证验证码
  444. $verifyCode = CacheService::get('code_' . $phone);
  445. if (!$verifyCode)
  446. return app('json')->fail('请先获取验证码');
  447. $verifyCode = substr($verifyCode, 0, 6);
  448. if ($verifyCode != $captcha) {
  449. CacheService::delete('code_' . $phone);
  450. return app('json')->fail('验证码错误');
  451. }
  452. } else {
  453. if (!$openId) {
  454. return app('json')->fail('参数错误');
  455. }
  456. }
  457. if ($email == '') $email = substr(md5($openId), 0, 12);
  458. $userInfo = [
  459. 'openId' => $openId,
  460. 'unionid' => '',
  461. 'avatarUrl' => sys_config('h5_avatar'),
  462. 'nickName' => $email,
  463. ];
  464. $token = $services->appAuth($userInfo, $phone, 'apple');
  465. if ($token) {
  466. return app('json')->success('登录成功', $token);
  467. } else if ($token === false) {
  468. return app('json')->success('登录成功', ['isbind' => true]);
  469. } else {
  470. return app('json')->fail('登录失败');
  471. }
  472. }
  473. /**
  474. * 滑块验证
  475. * @return mixed
  476. */
  477. public function ajcaptcha(Request $request)
  478. {
  479. $captchaType = $request->get('captchaType');
  480. return app('json')->success(aj_captcha_create($captchaType));
  481. }
  482. /**
  483. * 一次验证
  484. * @return mixed
  485. */
  486. public function ajcheck(Request $request)
  487. {
  488. [$token, $pointJson, $captchaType] = $request->postMore([
  489. ['token', ''],
  490. ['pointJson', ''],
  491. ['captchaType', ''],
  492. ], true);
  493. try {
  494. aj_captcha_check_one($captchaType, $token, $pointJson);
  495. return app('json')->success();
  496. } catch (\Throwable $e) {
  497. return app('json')->fail('验证码错误');
  498. }
  499. }
  500. /**
  501. * 远程登录接口
  502. * @param Request $request
  503. * @return \think\Response
  504. * @throws \Psr\SimpleCache\InvalidArgumentException
  505. * @throws \think\db\exception\DataNotFoundException
  506. * @throws \think\db\exception\DbException
  507. * @throws \think\db\exception\ModelNotFoundException
  508. * @author wuhaotian
  509. * @email 442384644@qq.com
  510. * @date 2024/5/21
  511. */
  512. public function remoteRegister(Request $request)
  513. {
  514. if (!Env::get('app_debug', false)) {
  515. return app('json')->fail('生产环境无法使用此功能,如需使用请修改.env文件中app_debug项为true');
  516. }
  517. [$remote_token] = $request->getMore([
  518. ['remote_token', ''],
  519. ], true);
  520. if ($remote_token == '') return app('json')->success('登录失败', ['get_remote_login_url' => sys_config('get_remote_login_url')]);
  521. return app('json')->success('登录成功', $this->services->remoteRegister($remote_token));
  522. }
  523. }