You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

573 lines
15 KiB

  1. <?php
  2. namespace app\common\library;
  3. use app\common\model\User;
  4. use app\common\model\UserRule;
  5. use fast\Random;
  6. use think\Config;
  7. use think\Cookie;
  8. use think\Db;
  9. use think\Exception;
  10. use think\Hook;
  11. use think\Request;
  12. use think\Validate;
  13. class Auth
  14. {
  15. protected static $instance = null;
  16. protected $_error = '';
  17. protected $_logined = false;
  18. protected $_user = null;
  19. protected $_token = '';
  20. //Token默认有效时长
  21. protected $keeptime = 14400;
  22. //protected $keeptime = 10;
  23. protected $requestUri = '';
  24. protected $rules = [];
  25. //默认配置
  26. protected $config = [];
  27. protected $options = [];
  28. protected $allowFields = ['id', 'username', 'nickname', 'mobile', 'avatar', 'score'];
  29. public function __construct($options = [])
  30. {
  31. if ($config = Config::get('user')) {
  32. $this->config = array_merge($this->config, $config);
  33. }
  34. $this->options = array_merge($this->config, $options);
  35. }
  36. /**
  37. *
  38. * @param array $options 参数
  39. * @return Auth
  40. */
  41. public static function instance($options = [])
  42. {
  43. if (is_null(self::$instance)) {
  44. self::$instance = new static($options);
  45. }
  46. return self::$instance;
  47. }
  48. /**
  49. * 获取User模型
  50. * @return User
  51. */
  52. public function getUser()
  53. {
  54. return $this->_user;
  55. }
  56. /**
  57. * 兼容调用user模型的属性
  58. *
  59. * @param string $name
  60. * @return mixed
  61. */
  62. public function __get($name)
  63. {
  64. return $this->_user ? $this->_user->$name : null;
  65. }
  66. /**
  67. * 根据Token初始化
  68. *
  69. * @param string $token Token
  70. * @return boolean
  71. */
  72. public function init($token)
  73. {
  74. if ($this->_logined) {
  75. return true;
  76. }
  77. if ($this->_error) {
  78. return false;
  79. }
  80. $data = Token::get($token);
  81. if (!$data) {
  82. return false;
  83. }
  84. $user_id = intval($data['user_id']);
  85. if ($user_id > 0) {
  86. $user = User::get($user_id);
  87. if (!$user) {
  88. $this->setError('Account not exist');
  89. return false;
  90. }
  91. if ($user['status'] != 'normal') {
  92. $this->setError('Account is locked');
  93. return false;
  94. }
  95. $this->_user = $user;
  96. $this->_logined = true;
  97. $this->_token = $token;
  98. //初始化成功的事件
  99. Hook::listen("user_init_successed", $this->_user);
  100. return true;
  101. } else {
  102. $this->setError('You are not logged in');
  103. return false;
  104. }
  105. }
  106. /**
  107. * 注册用户
  108. *
  109. * @param string $username 用户名
  110. * @param string $password 密码
  111. * @param string $email 邮箱
  112. * @param string $mobile 手机号
  113. * @param array $extend 扩展参数
  114. * @return boolean
  115. */
  116. public function register($username, $password, $email = '', $mobile = '', $extend = [])
  117. {
  118. // 检测用户名或邮箱、手机号是否存在
  119. if (User::getByUsername($username)) {
  120. $this->setError('Username already exist');
  121. return false;
  122. }
  123. if ($email && User::getByEmail($email)) {
  124. $this->setError('Email already exist');
  125. return false;
  126. }
  127. if ($mobile && User::getByMobile($mobile)) {
  128. $this->setError('Mobile already exist');
  129. return false;
  130. }
  131. $ip = request()->ip();
  132. $time = time();
  133. $data = [
  134. 'username' => $username,
  135. 'password' => $password,
  136. 'email' => $email,
  137. 'mobile' => $mobile,
  138. 'level' => 1,
  139. 'score' => 0,
  140. 'avatar' => '',
  141. ];
  142. $params = array_merge($data, [
  143. 'nickname' => $username,
  144. 'salt' => Random::alnum(),
  145. 'jointime' => $time,
  146. 'joinip' => $ip,
  147. 'logintime' => $time,
  148. 'loginip' => $ip,
  149. 'prevtime' => $time,
  150. 'status' => 'normal'
  151. ]);
  152. $params['password'] = $this->getEncryptPassword($password, $params['salt']);
  153. $params = array_merge($params, $extend);
  154. //账号注册时需要开启事务,避免出现垃圾数据
  155. Db::startTrans();
  156. try {
  157. $user = User::create($params, true);
  158. $this->_user = User::get($user->id);
  159. //设置Token
  160. $this->_token = Random::uuid();
  161. Token::set($this->_token, $user->id, $this->keeptime);
  162. //注册成功的事件
  163. Hook::listen("user_register_successed", $this->_user, $data);
  164. Db::commit();
  165. } catch (Exception $e) {
  166. $this->setError($e->getMessage());
  167. Db::rollback();
  168. return false;
  169. }
  170. return true;
  171. }
  172. /**
  173. * 用户登录
  174. *
  175. * @param string $account 账号,用户名、邮箱、手机号
  176. * @param string $password 密码
  177. * @return boolean
  178. */
  179. public function login($account, $password)
  180. {
  181. $field = Validate::is($account, 'email') ? 'email' : (Validate::regex($account, '/^1\d{10}$/') ? 'mobile' : 'username');
  182. $user = User::get([$field => $account]);
  183. if (!$user) {
  184. $this->setError('Account is incorrect');
  185. return false;
  186. }
  187. if ($user->status != 'normal') {
  188. $this->setError('Account is locked');
  189. return false;
  190. }
  191. $loginError = Cookie::get($account);
  192. if ($loginError && $loginError>=5) {
  193. $this->setError(__('登录错误次数过多,请5分钟后再登录'));;
  194. return false;
  195. }
  196. if ($user->password != $this->getEncryptPassword($password, $user->salt)) {
  197. $loginError = $loginError?$loginError+1:1;
  198. Cookie::set($account,$loginError,60*5);
  199. $this->setError('Password is incorrect');
  200. return false;
  201. }
  202. //直接登录会员
  203. $this->direct($user->id);
  204. return true;
  205. }
  206. /**
  207. * 注销
  208. *
  209. * @return boolean
  210. */
  211. public function logout()
  212. {
  213. if (!$this->_logined) {
  214. $this->setError('You are not logged in');
  215. return false;
  216. }
  217. //设置登录标识
  218. $this->_logined = false;
  219. //删除Token
  220. Token::delete($this->_token);
  221. //注销成功的事件
  222. Hook::listen("user_logout_successed", $this->_user);
  223. return true;
  224. }
  225. /**
  226. * 修改密码
  227. * @param string $newpassword 新密码
  228. * @param string $oldpassword 旧密码
  229. * @param bool $ignoreoldpassword 忽略旧密码
  230. * @return boolean
  231. */
  232. public function changepwd($newpassword, $oldpassword = '', $ignoreoldpassword = false)
  233. {
  234. if (!$this->_logined) {
  235. $this->setError('You are not logged in');
  236. return false;
  237. }
  238. //判断旧密码是否正确
  239. if ($this->_user->password == $this->getEncryptPassword($oldpassword, $this->_user->salt) || $ignoreoldpassword) {
  240. Db::startTrans();
  241. try {
  242. $salt = Random::alnum();
  243. $newpassword = $this->getEncryptPassword($newpassword, $salt);
  244. $this->_user->save(['loginfailure' => 0, 'password' => $newpassword, 'salt' => $salt]);
  245. Token::delete($this->_token);
  246. //修改密码成功的事件
  247. Hook::listen("user_changepwd_successed", $this->_user);
  248. Db::commit();
  249. } catch (Exception $e) {
  250. Db::rollback();
  251. $this->setError($e->getMessage());
  252. return false;
  253. }
  254. return true;
  255. } else {
  256. $this->setError('Password is incorrect');
  257. return false;
  258. }
  259. }
  260. /**
  261. * 直接登录账号
  262. * @param int $user_id
  263. * @return boolean
  264. */
  265. public function direct($user_id)
  266. {
  267. $user = User::get($user_id);
  268. if ($user) {
  269. Db::startTrans();
  270. try {
  271. $ip = request()->ip();
  272. $time = time();
  273. //判断连续登录和最大连续登录
  274. if ($user->logintime < \fast\Date::unixtime('day')) {
  275. $user->successions = $user->logintime < \fast\Date::unixtime('day', -1) ? 1 : $user->successions + 1;
  276. $user->maxsuccessions = max($user->successions, $user->maxsuccessions);
  277. }
  278. $user->prevtime = $user->logintime;
  279. //记录本次登录的IP和时间
  280. $user->loginip = $ip;
  281. $user->logintime = $time;
  282. //重置登录失败次数
  283. $user->loginfailure = 0;
  284. $user->save();
  285. $this->_user = $user;
  286. $this->_token = Random::uuid();
  287. Token::set($this->_token, $user->id, $this->keeptime);
  288. $this->_logined = true;
  289. //登录成功的事件
  290. Hook::listen("user_login_successed", $this->_user);
  291. Db::commit();
  292. } catch (Exception $e) {
  293. Db::rollback();
  294. $this->setError($e->getMessage());
  295. return false;
  296. }
  297. return true;
  298. } else {
  299. return false;
  300. }
  301. }
  302. /**
  303. * 检测是否是否有对应权限
  304. * @param string $path 控制器/方法
  305. * @param string $module 模块 默认为当前模块
  306. * @return boolean
  307. */
  308. public function check($path = null, $module = null)
  309. {
  310. if (!$this->_logined) {
  311. return false;
  312. }
  313. $ruleList = $this->getRuleList();
  314. $rules = [];
  315. foreach ($ruleList as $k => $v) {
  316. $rules[] = $v['name'];
  317. }
  318. $url = ($module ? $module : request()->module()) . '/' . (is_null($path) ? $this->getRequestUri() : $path);
  319. $url = strtolower(str_replace('.', '/', $url));
  320. return in_array($url, $rules) ? true : false;
  321. }
  322. /**
  323. * 判断是否登录
  324. * @return boolean
  325. */
  326. public function isLogin()
  327. {
  328. if ($this->_logined) {
  329. return true;
  330. }
  331. return false;
  332. }
  333. /**
  334. * 获取当前Token
  335. * @return string
  336. */
  337. public function getToken()
  338. {
  339. return $this->_token;
  340. }
  341. /**
  342. * 获取会员基本信息
  343. */
  344. public function getUserinfo()
  345. {
  346. $data = $this->_user->toArray();
  347. $allowFields = $this->getAllowFields();
  348. $userinfo = array_intersect_key($data, array_flip($allowFields));
  349. $userinfo = array_merge($userinfo, Token::get($this->_token));
  350. return $userinfo;
  351. }
  352. /**
  353. * 获取会员组别规则列表
  354. * @return array
  355. */
  356. public function getRuleList()
  357. {
  358. if ($this->rules) {
  359. return $this->rules;
  360. }
  361. $group = $this->_user->group;
  362. if (!$group) {
  363. return [];
  364. }
  365. $rules = explode(',', $group->rules);
  366. $this->rules = UserRule::where('status', 'normal')->where('id', 'in', $rules)->field('id,pid,name,title,ismenu')->select();
  367. return $this->rules;
  368. }
  369. /**
  370. * 获取当前请求的URI
  371. * @return string
  372. */
  373. public function getRequestUri()
  374. {
  375. return $this->requestUri;
  376. }
  377. /**
  378. * 设置当前请求的URI
  379. * @param string $uri
  380. */
  381. public function setRequestUri($uri)
  382. {
  383. $this->requestUri = $uri;
  384. }
  385. /**
  386. * 获取允许输出的字段
  387. * @return array
  388. */
  389. public function getAllowFields()
  390. {
  391. return $this->allowFields;
  392. }
  393. /**
  394. * 设置允许输出的字段
  395. * @param array $fields
  396. */
  397. public function setAllowFields($fields)
  398. {
  399. $this->allowFields = $fields;
  400. }
  401. /**
  402. * 删除一个指定会员
  403. * @param int $user_id 会员ID
  404. * @return boolean
  405. */
  406. public function delete($user_id)
  407. {
  408. $user = User::get($user_id);
  409. if (!$user) {
  410. return false;
  411. }
  412. Db::startTrans();
  413. try {
  414. // 删除会员
  415. User::destroy($user_id);
  416. // 删除会员指定的所有Token
  417. Token::clear($user_id);
  418. Hook::listen("user_delete_successed", $user);
  419. Db::commit();
  420. } catch (Exception $e) {
  421. Db::rollback();
  422. $this->setError($e->getMessage());
  423. return false;
  424. }
  425. return true;
  426. }
  427. /**
  428. * 获取密码加密后的字符串
  429. * @param string $password 密码
  430. * @param string $salt 密码盐
  431. * @return string
  432. */
  433. public function getEncryptPassword($password, $salt = '')
  434. {
  435. return md5(md5($password) . $salt);
  436. }
  437. /**
  438. * 检测当前控制器和方法是否匹配传递的数组
  439. *
  440. * @param array $arr 需要验证权限的数组
  441. * @return boolean
  442. */
  443. public function match($arr = [])
  444. {
  445. $request = Request::instance();
  446. $arr = is_array($arr) ? $arr : explode(',', $arr);
  447. if (!$arr) {
  448. return false;
  449. }
  450. $arr = array_map('strtolower', $arr);
  451. // 是否存在
  452. if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr)) {
  453. return true;
  454. }
  455. // 没找到匹配
  456. return false;
  457. }
  458. /**
  459. * 设置会话有效时间
  460. * @param int $keeptime 默认为永久
  461. */
  462. public function keeptime($keeptime = 0)
  463. {
  464. $this->keeptime = $keeptime;
  465. }
  466. /**
  467. * 渲染用户数据
  468. * @param array $datalist 二维数组
  469. * @param mixed $fields 加载的字段列表
  470. * @param string $fieldkey 渲染的字段
  471. * @param string $renderkey 结果字段
  472. * @return array
  473. */
  474. public function render(&$datalist, $fields = [], $fieldkey = 'user_id', $renderkey = 'userinfo')
  475. {
  476. $fields = !$fields ? ['id', 'nickname', 'level', 'avatar'] : (is_array($fields) ? $fields : explode(',', $fields));
  477. $ids = [];
  478. foreach ($datalist as $k => $v) {
  479. if (!isset($v[$fieldkey])) {
  480. continue;
  481. }
  482. $ids[] = $v[$fieldkey];
  483. }
  484. $list = [];
  485. if ($ids) {
  486. if (!in_array('id', $fields)) {
  487. $fields[] = 'id';
  488. }
  489. $ids = array_unique($ids);
  490. $selectlist = User::where('id', 'in', $ids)->column($fields);
  491. foreach ($selectlist as $k => $v) {
  492. $list[$v['id']] = $v;
  493. }
  494. }
  495. foreach ($datalist as $k => &$v) {
  496. $v[$renderkey] = isset($list[$v[$fieldkey]]) ? $list[$v[$fieldkey]] : null;
  497. }
  498. unset($v);
  499. return $datalist;
  500. }
  501. /**
  502. * 设置错误信息
  503. *
  504. * @param $error 错误信息
  505. * @return Auth
  506. */
  507. public function setError($error)
  508. {
  509. $this->_error = $error;
  510. return $this;
  511. }
  512. /**
  513. * 获取错误信息
  514. * @return string
  515. */
  516. public function getError()
  517. {
  518. return $this->_error ? __($this->_error) : '';
  519. }
  520. }