Auth.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. <?php
  2. namespace app\common\library;
  3. use app\common\model\User;
  4. use app\common\model\UserThird;
  5. use fast\Random;
  6. use fast\ucenter\client\Client;
  7. use think\Cookie;
  8. use think\Db;
  9. use think\Exception;
  10. use think\Request;
  11. use think\Validate;
  12. /**
  13. * Auth类
  14. */
  15. class Auth implements \JsonSerializable, \ArrayAccess
  16. {
  17. const ERR_ACCOUNT_IS_INCORRECT = 'Account is incorrect';
  18. const ERR_ACCOUNT_NOT_EXIST = 'Account not exist';
  19. const ERR_USERNAME_IS_INCORRECT = 'Username is incorrect';
  20. const ERR_EMAIL_IS_INCORRECT = 'Email is incorrect';
  21. const ERR_PASSWORD_IS_INCORRECT = 'Password is incorrect';
  22. const ERR_USERNAME_OR_PASSWORD_IS_INCORRECT = 'Username or password is incorrect';
  23. const ERR_USERNAME_ALREADY_EXIST = 'Username already exist';
  24. const ERR_EMAIL_ALREADY_EXIST = 'Email already exist';
  25. const ERR_MOBILE_ALREADY_EXIST = 'Mobile already exist';
  26. const ERR_ACCOUNT_IS_LOCKED = 'Account is locked';
  27. const ERR_USERNAME_OR_PASSWORD_IS_MODIFIED = 'Username or password is modified';
  28. const ERR_YOU_ARE_NOT_LOGGED_IN = 'You are not logged in';
  29. const ERR_ACCOUNT_ALREADY_LOGGED_IN_AT_ANOTHER_PLACE = 'Account already logged in at another';
  30. protected static $instance = null;
  31. private $_error = '';
  32. private $_logined = FALSE;
  33. private $user = NULL;
  34. private $keeptime = 0;
  35. private $requestUri = '';
  36. public function __construct()
  37. {
  38. $this->user = new User;
  39. }
  40. /**
  41. * 初始化
  42. * @param array $options 参数
  43. * @return Auth
  44. */
  45. public static function instance($options = [])
  46. {
  47. if (is_null(self::$instance))
  48. {
  49. self::$instance = new static($options);
  50. }
  51. return self::$instance;
  52. }
  53. /**
  54. *
  55. * @return User
  56. */
  57. public function getModel()
  58. {
  59. return $this->user;
  60. }
  61. public function __get($name)
  62. {
  63. return $this->check() ? $this->user->$name : NULL;
  64. }
  65. public function __call($name, $arguments)
  66. {
  67. return call_user_func_array([$this->user, $name], $arguments);
  68. }
  69. /**
  70. * 注册用户
  71. *
  72. * @param string $username 用户名
  73. * @param string $password 密码
  74. * @param string $email 邮箱
  75. * @param string $mobile 手机号
  76. * @param string $extend 扩展参数
  77. * @return boolean
  78. */
  79. public function register($username, $password, $email = '', $mobile = '', $extend = [], $keeptime = 0, $sync = TRUE)
  80. {
  81. $rule = [
  82. 'username' => 'require|length:6,30',
  83. 'password' => 'require|length:6,30',
  84. 'email' => 'email',
  85. 'mobile' => 'regex:/^1\d{10}$/',
  86. ];
  87. $msg = [
  88. 'username.require' => __('Username can not be empty'),
  89. 'username.length' => __('Username must be 6 to 30 characters'),
  90. 'password.require' => __('Password can not be empty'),
  91. 'password.length' => __('Password must be 6 to 30 characters'),
  92. 'email' => __('Email is incorrect'),
  93. 'mobile' => __('Mobile is incorrect'),
  94. ];
  95. $data = [
  96. 'username' => $username,
  97. 'password' => $password,
  98. 'email' => $email,
  99. 'mobile' => $mobile,
  100. ];
  101. $validate = new Validate($rule, $msg);
  102. $result = $validate->check($data);
  103. if (!$result)
  104. {
  105. $this->setError($validate->getError());
  106. return FALSE;
  107. }
  108. // 检测用户名或邮箱、手机号是否存在
  109. if (User::getByUsername($username))
  110. {
  111. $this->setError(__('Username already exist'));
  112. return FALSE;
  113. }
  114. if ($email && User::getByEmail($email))
  115. {
  116. $this->setError(__('Email already exist'));
  117. return FALSE;
  118. }
  119. if ($mobile && User::getByMobile($mobile))
  120. {
  121. $this->setError(__('Mobile already exist'));
  122. return FALSE;
  123. }
  124. $ip = request()->ip();
  125. $time = time();
  126. $params = array_merge($data, [
  127. 'nickname' => $username,
  128. 'salt' => Random::alnum(),
  129. 'jointime' => $time,
  130. 'joinip' => $ip,
  131. 'logintime' => $time,
  132. 'loginip' => $ip,
  133. 'prevtime' => $time,
  134. 'status' => 'normal'
  135. ]);
  136. $params['password'] = $this->getEncryptPassword($password, $params['salt']);
  137. $params = array_merge($params, $extend);
  138. ////////////////同步到Ucenter////////////////
  139. if (defined('UC_STATUS') && UC_STATUS && $sync)
  140. {
  141. $uc = new Client();
  142. $user_id = $uc->uc_user_register($username, $password, $email);
  143. // 如果小于0则说明发生错误
  144. if ($user_id <= 0)
  145. {
  146. $this->setError($user_id > -4 ? self::ERR_USERNAME_IS_INCORRECT : self::ERR_EMAIL_IS_INCORRECT);
  147. return FALSE;
  148. }
  149. else
  150. {
  151. $params['id'] = $user_id;
  152. }
  153. }
  154. //账号注册时需要开启事务,避免出现垃圾数据
  155. Db::startTrans();
  156. try
  157. {
  158. $ret = $this->user->save($params);
  159. Db::commit();
  160. // 此时的Model中只包含部分数据
  161. $this->user = $this->user->get($this->user->id);
  162. $this->keeptime($keeptime);
  163. return $this->syncLogin();
  164. }
  165. catch (Exception $e)
  166. {
  167. Db::rollback();
  168. return FALSE;
  169. }
  170. }
  171. /**
  172. * 用户登录
  173. *
  174. * @param string $account 账号,用户名、邮箱、手机号
  175. * @param string $password 密码
  176. * @param int $keeptime 有效时长,默认为浏览器关闭
  177. * @return array
  178. */
  179. public function login($account, $password, $keeptime = 0, $sync = TRUE)
  180. {
  181. $field = Validate::is($account, 'email') ? 'email' : (Validate::regex($account, '/^1\d{10}$/') ? 'mobile' : 'username');
  182. $user = $this->user->get([$field => $account]);
  183. if ($user)
  184. {
  185. if ($user->status != 'normal')
  186. {
  187. $this->setError(self::ERR_ACCOUNT_IS_LOCKED);
  188. return FALSE;
  189. }
  190. if ($user->password != $this->getEncryptPassword($password, $user->salt))
  191. {
  192. $this->setError(self::ERR_PASSWORD_IS_INCORRECT);
  193. return FALSE;
  194. }
  195. $this->user = $user;
  196. // 设置登录有效时长
  197. $this->keeptime($keeptime);
  198. return $this->syncLogin($sync);
  199. }
  200. else
  201. {
  202. $this->setError(self::ERR_ACCOUNT_IS_INCORRECT);
  203. return FALSE;
  204. }
  205. }
  206. /**
  207. * 注销登录退出
  208. * @return bool
  209. */
  210. public function logout($token = NULL)
  211. {
  212. //设置登录标识
  213. $this->_logined = FALSE;
  214. $token = is_null($token) ? Cookie::get('token') : $token;
  215. Token::delete($token);
  216. Cookie::delete('user_id');
  217. //Cookie::del('username');
  218. Cookie::delete('token');
  219. return TRUE;
  220. }
  221. /**
  222. * 生成Token
  223. * @return string
  224. */
  225. public function token()
  226. {
  227. //$token = Encrypt::aesEncode($this->keeptime . '|' . $expiretime, Config::get('encrypt', 'aes_key'), TRUE);
  228. $token = Random::uuid();
  229. Token::set($token, $this->user->id, $this->keeptime);
  230. return $token;
  231. }
  232. /**
  233. * 初始化
  234. *
  235. * @param int $user_id 会员ID,默认从Cookie中取
  236. * @param string $token 会员Token,默认从Cookie中取
  237. *
  238. * @return boolean
  239. */
  240. public function init($user_id = NULL, $token = NULL)
  241. {
  242. $user_id = $user_id ? $user_id : Cookie::get('user_id');
  243. $user_id = intval($user_id);
  244. if ($user_id > 0)
  245. {
  246. if ($this->_error)
  247. return FALSE;
  248. $user = $this->get($user_id);
  249. if (!$user)
  250. {
  251. $this->setError(self::ERR_ACCOUNT_NOT_EXIST);
  252. return FALSE;
  253. }
  254. if ($user['status'] != 'normal')
  255. {
  256. $this->setError(self::ERR_ACCOUNT_IS_LOCKED);
  257. return FALSE;
  258. }
  259. $token = $token ? $token : Cookie::get('token');
  260. if (!Token::check($token))
  261. {
  262. return FALSE;
  263. }
  264. if (Token::identity($token) != $user['id'])
  265. {
  266. return FALSE;
  267. }
  268. $this->user = $user;
  269. $this->_logined = TRUE;
  270. return TRUE;
  271. }
  272. else
  273. {
  274. $this->setError(self::ERR_YOU_ARE_NOT_LOGGED_IN);
  275. return FALSE;
  276. }
  277. }
  278. /**
  279. * 检测是否登录
  280. *
  281. * @return boolean
  282. */
  283. public function check()
  284. {
  285. return $this->_logined;
  286. }
  287. /**
  288. * 检测是否登录
  289. *
  290. * @return boolean
  291. */
  292. public function isLogin()
  293. {
  294. return $this->check();
  295. }
  296. /**
  297. * 获取当前请求的URI
  298. * @return string
  299. */
  300. public function getRequestUri()
  301. {
  302. return $this->requestUri;
  303. }
  304. /**
  305. * 设置当前请求的URI
  306. * @param string $uri
  307. */
  308. public function setRequestUri($uri)
  309. {
  310. $this->requestUri = $uri;
  311. }
  312. /**
  313. * 第三方登录
  314. * @param string $platform
  315. * @param array $params
  316. * @param int $keeptime
  317. * @return boolean
  318. */
  319. public function connect($platform, $params = [], $keeptime = 0)
  320. {
  321. $time = time();
  322. $values = [
  323. 'platform' => $platform,
  324. 'openid' => $params['openid'],
  325. 'openname' => isset($params['userinfo']['nickname']) ? $params['userinfo']['nickname'] : '',
  326. 'access_token' => $params['access_token'],
  327. 'refresh_token' => $params['refresh_token'],
  328. 'expires_in' => $params['expires_in'],
  329. 'logintime' => $time,
  330. 'expiretime' => $time + $params['expires_in'],
  331. ];
  332. $this->keeptime($keeptime);
  333. $userthird = UserThird::get(['platform' => $platform, 'openid' => $params['openid']]);
  334. if ($userthird)
  335. {
  336. $this->user = $this->user->get($userthird['user_id']);
  337. if (!$this->user)
  338. {
  339. return FALSE;
  340. }
  341. $userthird->save($values);
  342. return $this->syncLogin();
  343. }
  344. else
  345. {
  346. // 先随机一个用户名,随后再变更为u+数字id
  347. $username = Random::alnum(20);
  348. $password = Random::alnum(6);
  349. // 默认注册一个会员
  350. $result = $this->register($username, $password, '', '', [], $keeptime);
  351. if (!$result)
  352. {
  353. return FALSE;
  354. }
  355. $userarr = ['username' => 'u' . $this->user->id];
  356. if (isset($params['userinfo']['nickname']))
  357. $userarr['nickname'] = $params['userinfo']['nickname'];
  358. if (isset($params['userinfo']['avatar']))
  359. $userarr['avatar'] = $params['userinfo']['avatar'];
  360. // 更新会员资料
  361. $this->user->save($userarr);
  362. // 保存第三方信息
  363. $values['user_id'] = $this->user->id;
  364. UserThird::create($values);
  365. // 写入登录Cookies和Token
  366. $this->writeStatus();
  367. return TRUE;
  368. }
  369. }
  370. /**
  371. * 删除一个指定会员
  372. * @param int $user_id
  373. * @param bool $sync 是否同步删除
  374. */
  375. public function delete($user_id, $sync = TRUE)
  376. {
  377. $user = $this->user->get($user_id);
  378. if (!$user)
  379. {
  380. return FALSE;
  381. }
  382. ////////////////同步到Ucenter////////////////
  383. if (defined('UC_STATUS') && UC_STATUS && $sync)
  384. {
  385. $uc = new Client();
  386. $re = $uc->uc_user_delete($user['id']);
  387. // 如果小于0则说明发生错误
  388. if ($re <= 0)
  389. {
  390. $this->setError(self::ERR_ACCOUNT_IS_LOCKED);
  391. return FALSE;
  392. }
  393. }
  394. // 调用事务删除账号
  395. $result = Db::transaction(function($db) use($user_id) {
  396. // 删除会员
  397. User::destroy($user_id);
  398. // 删除会员第三方登录
  399. UserThird::destroy($user_id);
  400. });
  401. return $result ? TRUE : FALSE;
  402. }
  403. /**
  404. * 直接登录账号
  405. * @param int $user_id
  406. * @param boolean $sync
  407. * @return boolean
  408. */
  409. public function direct($user_id, $sync = TRUE)
  410. {
  411. $this->user = $this->user->get($user_id);
  412. if ($this->user)
  413. {
  414. $this->syncLogin($sync);
  415. return TRUE;
  416. }
  417. else
  418. {
  419. return FALSE;
  420. }
  421. }
  422. /**
  423. * 获取密码加密方式
  424. * @param string $password
  425. * @param string $salt
  426. * @return string
  427. */
  428. public function getEncryptPassword($password, $salt = '')
  429. {
  430. return md5(md5($password) . $salt);
  431. }
  432. /**
  433. * 检测当前控制器和方法是否匹配传递的数组
  434. *
  435. * @param array $arr 需要验证权限的数组
  436. */
  437. public function match($arr = [])
  438. {
  439. $request = Request::instance();
  440. $arr = is_array($arr) ? $arr : explode(',', $arr);
  441. if (!$arr)
  442. {
  443. return FALSE;
  444. }
  445. // 是否存在
  446. if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr))
  447. {
  448. return TRUE;
  449. }
  450. // 没找到匹配
  451. return FALSE;
  452. }
  453. /**
  454. * 同步登录信息
  455. * @param int $sync 是否同步登录到UC
  456. * @return boolean
  457. */
  458. protected function syncLogin($sync = TRUE)
  459. {
  460. ////////////////同步到Ucenter////////////////
  461. if (defined('UC_STATUS') && UC_STATUS && $sync)
  462. {
  463. $uc = new Client();
  464. $re = $uc->uc_user_login($this->user->id, $this->user->password . '#split#' . $this->user->salt, 3);
  465. // 如果小于0则说明发生错误
  466. if ($re <= 0)
  467. {
  468. $this->setError(self::ERR_USERNAME_OR_PASSWORD_IS_INCORRECT);
  469. return FALSE;
  470. }
  471. }
  472. //增加登录次数和设置最后登录时间
  473. $this->user->save([
  474. 'prevtime' => $this->user->logintime,
  475. 'logintime' => time(),
  476. 'loginip' => request()->ip(),
  477. ]);
  478. // 写入登录Cookies和Token
  479. $this->writeStatus();
  480. return TRUE;
  481. }
  482. /**
  483. * 写入登录态和Cookie
  484. *
  485. * @param int $keeptime
  486. */
  487. protected function writeStatus()
  488. {
  489. //设置登录标识
  490. $this->_logined = TRUE;
  491. $token = $this->token();
  492. Cookie::set('user_id', $this->user->id, $this->keeptime);
  493. Cookie::set('username', $this->user->username, 86400 * 365);
  494. //加密安全字符
  495. Cookie::set('token', $token, $this->keeptime);
  496. $this->setError('');
  497. }
  498. /**
  499. * 设置会话有效时间
  500. * @param int $keeptime 默认为永久
  501. */
  502. public function keeptime($keeptime = 0)
  503. {
  504. $this->keeptime = $keeptime;
  505. }
  506. /**
  507. * 渲染用户数据
  508. * @param array $datalist
  509. * @param array $fields
  510. * @param string $fieldkey
  511. * @param string $renderkey
  512. * @return array
  513. */
  514. public function render(&$datalist, $fields = [], $fieldkey = 'user_id', $renderkey = 'userinfo')
  515. {
  516. $fields = !$fields ? ['id', 'nickname', 'level', 'avatar'] : (is_array($fields) ? $fields : explode(',', $fields));
  517. $ids = [];
  518. foreach ($datalist as $k => $v)
  519. {
  520. if (!isset($v[$fieldkey]))
  521. continue;
  522. $ids[] = $v[$fieldkey];
  523. }
  524. $list = [];
  525. if ($ids)
  526. {
  527. if (!in_array('id', $fields))
  528. {
  529. $fields[] = 'id';
  530. }
  531. $ids = array_unique($ids);
  532. $selectlist = User::where('id', 'in', $ids)->column($fields);
  533. foreach ($selectlist as $k => $v)
  534. {
  535. $list[$v['id']] = $v;
  536. }
  537. }
  538. foreach ($datalist as $k => &$v)
  539. {
  540. $v[$renderkey] = isset($list[$v[$fieldkey]]) ? $list[$v[$fieldkey]] : NULL;
  541. }
  542. unset($v);
  543. return $datalist;
  544. }
  545. /**
  546. * 设置错误信息
  547. *
  548. * @param $error
  549. */
  550. public function setError($error)
  551. {
  552. $this->_error = $error;
  553. return $this;
  554. }
  555. /**
  556. * 获取错误信息
  557. * @return string
  558. */
  559. public function getError()
  560. {
  561. return __($this->_error);
  562. }
  563. public function __toString()
  564. {
  565. return $this->user->toJson();
  566. }
  567. // JsonSerializable
  568. public function jsonSerialize()
  569. {
  570. return $this->user->toArray();
  571. }
  572. // ArrayAccess
  573. public function offsetSet($name, $value)
  574. {
  575. $this->user->setAttr($name, $value);
  576. }
  577. public function offsetExists($name)
  578. {
  579. return $this->user->__isset($name);
  580. }
  581. public function offsetUnset($name)
  582. {
  583. $this->user->__unset($name);
  584. }
  585. public function offsetGet($name)
  586. {
  587. return $this->user->getAttr($name);
  588. }
  589. }