LoginService.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | Niucloud-admin 企业快速开发的saas管理平台
  4. // +----------------------------------------------------------------------
  5. // | 官方网址:https://www.niucloud.com
  6. // +----------------------------------------------------------------------
  7. // | niucloud团队 版权所有 开源版本可自由商用
  8. // +----------------------------------------------------------------------
  9. // | Author: Niucloud Team
  10. // +----------------------------------------------------------------------
  11. namespace app\service\api\login;
  12. use app\dict\member\MemberLoginTypeDict;
  13. use app\dict\member\MemberRegisterTypeDict;
  14. use app\dict\sys\AppTypeDict;
  15. use app\dict\sys\SmsDict;
  16. use app\model\member\Member;
  17. use app\service\api\captcha\CaptchaService;
  18. use app\service\api\member\MemberConfigService;
  19. use app\service\api\member\MemberService;
  20. use app\service\api\notice\NoticeService;
  21. use core\base\BaseApiService;
  22. use core\exception\ApiException;
  23. use core\exception\AuthException;
  24. use core\util\TokenAuth;
  25. use Exception;
  26. use think\facade\Cache;
  27. use think\facade\Log;
  28. use Throwable;
  29. /**
  30. * 登录服务层
  31. * Class LoginService
  32. * @package app\service\api\login
  33. */
  34. class LoginService extends BaseApiService
  35. {
  36. public function __construct()
  37. {
  38. parent::__construct();
  39. $this->model = new Member();
  40. }
  41. /**
  42. * 会员注册
  43. * @param $data
  44. */
  45. public function register($data)
  46. {
  47. //检测设置是否自动注册
  48. //自动注册检测授权信息
  49. //注册登录
  50. }
  51. /**
  52. * 登录操作
  53. * @param Member $member_info
  54. * @param string $login_type
  55. * @return array
  56. */
  57. public function login(Member $member_info, string $login_type)
  58. {
  59. //绑定第三方授权
  60. $this->bingOpenid($member_info);
  61. if (!$member_info->status) throw new ApiException('MEMBER_LOCK');
  62. $member_info->login_time = time();
  63. $member_info->login_ip = $this->request->ip();
  64. $member_info->login_channel = $this->channel;
  65. $member_info->login_type = $login_type;
  66. $member_info->login_count++;
  67. $member_info->last_visit_time = time();
  68. $member_info->save();
  69. $token_info = $this->createToken($member_info);
  70. event("MemberLogin", $member_info);
  71. return [
  72. 'token' => $token_info['token'],
  73. 'expires_time' => $token_info['params']['exp'],
  74. 'mobile' => $member_info->mobile
  75. ];
  76. }
  77. /**
  78. * 账号登录
  79. * @param string $username
  80. * @param string $password
  81. * @return array|false
  82. */
  83. public function account(string $username, string $password)
  84. {
  85. $member_service = new MemberService();
  86. $member_info = $member_service->findMemberInfo(['username|mobile' => $username]);
  87. if ($member_info->isEmpty()) throw new AuthException('MEMBER_NOT_EXIST');//账号不存在
  88. if (!check_password($password, $member_info->password)) return false;//密码与账号不匹配
  89. return $this->login($member_info, MemberLoginTypeDict::USERNAME);
  90. }
  91. /**
  92. * 手机号登录
  93. * @param string $mobile
  94. * @return array
  95. */
  96. public function mobile(string|array $params){
  97. //校验手机验证码
  98. $this->checkMobileCode($params[ 'mobile' ]);
  99. //登录注册配置
  100. $config = (new MemberConfigService())->getLoginConfig();
  101. $is_mobile = $config['is_mobile'];
  102. $is_bind_mobile = $config[ 'is_bind_mobile' ];
  103. if ($is_mobile != 1 && $is_bind_mobile != 1) throw new AuthException('MOBILE_LOGIN_UNOPENED');
  104. $member_service = new MemberService();
  105. $member_info = $member_service->findMemberInfo(['mobile' => $params[ 'mobile' ]]);
  106. if ($member_info->isEmpty()) {
  107. //开启强制绑定手机号,登录会自动注册并绑定手机号
  108. if ($is_bind_mobile == 1) {
  109. $data = array (
  110. 'mobile' => $params[ 'mobile' ],
  111. 'nickname' => $params[ 'nickname' ],
  112. 'headimg' => $params[ 'headimg' ],
  113. 'wx_openid' => $params[ 'openid' ]
  114. );
  115. return (new RegisterService())->register($params[ 'mobile' ], $data, MemberRegisterTypeDict::MOBILE, false);
  116. } else {
  117. throw new AuthException('MEMBER_NOT_EXIST');//账号不存在
  118. }
  119. }
  120. return $this->login($member_info, MemberLoginTypeDict::MOBILE);
  121. }
  122. /**
  123. * 生成token
  124. * @param $member_info
  125. * @return array|null
  126. */
  127. public function createToken($member_info): ?array
  128. {
  129. $expire_time = env('system.api_token_expire_time') ?? 3600;//todo 不一定和管理端合用这个token时限
  130. return TokenAuth::createToken($member_info->member_id, AppTypeDict::API, ['member_id' => $member_info->member_id, 'username' => $member_info->username], $expire_time);
  131. }
  132. /**
  133. * 登陆退出(当前账户)
  134. */
  135. public function logout(): ?bool
  136. {
  137. self::clearToken($this->member_id, $this->request->apiToken());
  138. return true;
  139. }
  140. /**
  141. * 清理token
  142. * @param int $member_id
  143. * @param string|null $token
  144. * @return bool|null
  145. */
  146. public static function clearToken(int $member_id, ?string $token = ''): ?bool
  147. {
  148. TokenAuth::clearToken($member_id, AppTypeDict::API, $token);
  149. return true;
  150. }
  151. /**
  152. * 解析token
  153. * @param string|null $token
  154. * @return array
  155. */
  156. public function parseToken(?string $token){
  157. if(empty($token))
  158. {
  159. //定义专属于授权认证机制的错误响应, 定义专属语言包
  160. throw new AuthException('MUST_LOGIN', 401);
  161. }
  162. try {
  163. $token_info = TokenAuth::parseToken($token, AppTypeDict::API);
  164. } catch ( Throwable $e ) {
  165. // if(env('app_debug', false)){
  166. // throw new AuthException($e->getMessage(), 401);
  167. // }else{
  168. throw new AuthException('LOGIN_EXPIRE', 401);
  169. // }
  170. }
  171. if(!$token_info)
  172. {
  173. throw new AuthException('MUST_LOGIN', 401);
  174. }
  175. //验证有效次数或过期时间
  176. return $token_info;
  177. }
  178. /**
  179. * 手机发送验证码
  180. * @param $mobile
  181. * @param string $type 发送短信的业务场景
  182. * @return array
  183. * @throws Exception
  184. */
  185. public function sendMobileCode($mobile, string $type = ''){
  186. (new CaptchaService())->check();
  187. if(empty($mobile)) throw new AuthException('MOBILE_NEEDED');
  188. //发送
  189. if(!in_array($type, SmsDict::SCENE_TYPE)) throw new AuthException('MEMBER_MOBILE_CAPTCHA_ERROR');
  190. $code = str_pad(random_int(1, 9999), 4, 0, STR_PAD_LEFT);// 生成4位随机数,左侧补0
  191. (new NoticeService())->send('member_verify_code', ['code' => $code, 'mobile' => $mobile]);
  192. //将验证码存入缓存
  193. $key = md5(uniqid('', true));
  194. $cache_tag_name = "mobile_key".$mobile.$type;
  195. $this->clearMobileCode($mobile, $type);
  196. Cache::tag($cache_tag_name)->set($key, [ 'mobile' => $mobile, 'code' => $code, 'type' => $type], 600);
  197. return ['key' => $key];
  198. }
  199. public function getMobileCodeCacheName(){
  200. }
  201. public function clearMobileCode($mobile, $type){
  202. $cache_tag_name = "mobile_key".$mobile.$type;
  203. Cache::tag($cache_tag_name)->clear();
  204. }
  205. /**
  206. * 校验手机验证码
  207. * @param string $mobile
  208. * @return true
  209. */
  210. public function checkMobileCode(string $mobile){
  211. if(empty($mobile)) throw new AuthException('MOBILE_NEEDED');
  212. $mobile_key = request()->param('mobile_key', '');
  213. $mobile_code = request()->param('mobile_code', '');
  214. if(empty($mobile_key) || empty($mobile_code)) throw new AuthException('MOBILE_CAPTCHA_ERROR');
  215. $cache = Cache::get($mobile_key);
  216. if(empty($cache)) throw new AuthException('MOBILE_CAPTCHA_ERROR');
  217. $temp_mobile = $cache['mobile'];
  218. $temp_code = $cache['code'];
  219. $temp_type = $cache['type'];
  220. if($temp_mobile != $mobile || $temp_code != $mobile_code) throw new AuthException('MOBILE_CAPTCHA_ERROR');
  221. $this->clearMobileCode($temp_mobile, $temp_type);
  222. return true;
  223. }
  224. /**
  225. * 绑定openid
  226. * @param $member
  227. * @return true
  228. */
  229. public function bingOpenid($member){
  230. $open_id = $this->request->param('openid');
  231. if(!empty($open_id)){
  232. Log::write('channel_1'.$this->channel);
  233. if(!empty($this->channel)){
  234. $openid_field = match($this->channel){
  235. 'wechat' => 'wx_openid',
  236. 'weapp' => 'weapp_openid',
  237. default => ''
  238. };
  239. if(!empty($openid_field)){
  240. if(!$member->isEmpty()){
  241. if(empty($member->$openid_field)){
  242. //todo 定义当前第三方授权方没有退出登录功能,故这儿不做openid是否存在账号验证
  243. // $member_service = new MemberService();
  244. // $open_member = $member_service->findMemberInfo([$openid_field => $open_id]);
  245. if (empty($open_member)) {
  246. $member->$openid_field = $open_id;
  247. $member->save();
  248. }
  249. }else{
  250. if( $member->$openid_field != $open_id){
  251. throw new AuthException('MEMBER_IS_BIND_AUTH');
  252. }
  253. }
  254. }
  255. }
  256. }
  257. }
  258. return true;
  259. }
  260. /**
  261. * 重置密码
  262. * @param string $mobile
  263. * @param string $password
  264. */
  265. public function resetPassword(string $mobile, string $password){
  266. $member_service = new MemberService();
  267. //校验手机验证码
  268. $this->checkMobileCode($mobile);
  269. $member_info = $member_service->findMemberInfo(['mobile' => $mobile]);
  270. if ($member_info->isEmpty()) throw new AuthException('MOBILE_NOT_EXIST_MEMBER');//账号不存在
  271. //todo 需要考虑一下,新的密码和原密码一样能否通过验证
  272. $password_hash = create_password($password);
  273. $data = [
  274. 'password' => $password_hash,
  275. ];
  276. $member_service->editByFind($member_info, $data);
  277. self::clearToken($member_info['member_id'], $this->request->apiToken());
  278. return true;
  279. }
  280. }