UserService.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Bear
  5. * Date: 2018/11/22
  6. * Time: 下午12:42
  7. */
  8. namespace app\main\service;
  9. use app\common\library\Ip;
  10. use app\common\library\Redis;
  11. use app\common\library\Ua;
  12. use app\common\library\User as LibUser;
  13. use app\common\model\Sign;
  14. use app\common\model\User as mUser;
  15. use app\common\model\User;
  16. use app\common\model\UserCollect;
  17. use app\common\model\UserRecentlyRead;
  18. use app\common\utility\WhiteList;
  19. use app\main\constants\AdminConstants;
  20. use app\main\constants\CacheConstants;
  21. use app\main\constants\ErrorCodeConstants;
  22. use app\main\constants\KafkaDotConstants;
  23. use app\main\constants\MessageConstants;
  24. use app\main\constants\MqConstants;
  25. use app\main\constants\OpenPlatformConstants;
  26. use app\main\constants\PayConstants;
  27. use app\main\constants\UrlConstants;
  28. use app\main\constants\UserConstants;
  29. use app\main\model\object\AnalysisObject;
  30. use app\main\model\object\BaseObject;
  31. use app\main\model\object\DotObject;
  32. use app\main\model\object\ReturnObject;
  33. use app\main\model\object\RunTimeObject;
  34. use app\main\model\object\UserObject;
  35. use app\source\model\UserUpdate;
  36. use fast\Random;
  37. use GuzzleHttp\Client;
  38. use Overtrue\Socialite\AuthorizeFailedException;
  39. use Symfony\Component\HttpFoundation\RedirectResponse;
  40. use think\Config;
  41. use think\Cookie;
  42. use think\exception\HttpException;
  43. use think\Log;
  44. use think\Request;
  45. /**
  46. * 处理前台用户信息
  47. * Class UserService
  48. * @package app\main\service
  49. */
  50. class UserService extends BaseService
  51. {
  52. const USER_OPERATE_EXPIRE = 86400;
  53. const USER_OTHER_DATA_EXPIRE = 172800; //两天
  54. const USER_OTHER_DATA_KEY_PREFIX= 'UOTD:';
  55. /**
  56. * @var UserService
  57. */
  58. protected static $self = NULL;
  59. /**
  60. * @var UserObject
  61. */
  62. private $libUser;
  63. /**
  64. * @var RunTimeObject
  65. */
  66. private $runTimeObject;
  67. /**
  68. * 耗时统计
  69. * @var int
  70. */
  71. private $useTime = 0;
  72. /**
  73. * @return UserService | UserObject
  74. */
  75. public static function instance()
  76. {
  77. if (self::$self == NULL) {
  78. self::$self = new self();
  79. }
  80. return self::$self;
  81. }
  82. public function getUserPhoneModel()
  83. {
  84. return model('UserPhone');
  85. }
  86. /**
  87. * @return User
  88. */
  89. public function getUserModel()
  90. {
  91. return model('User');
  92. }
  93. /**
  94. * @param LibUser $user
  95. * @return LibUser
  96. */
  97. public function setUserLib(LibUser $user)
  98. {
  99. return $this->getRunTimeObject()->user = $user;
  100. }
  101. /**
  102. * @return UserCollect
  103. */
  104. public function getUserCollectModel()
  105. {
  106. return model('UserCollect');
  107. }
  108. /**
  109. * @return UserRecentlyRead
  110. */
  111. public function getUserRecentlyReadModel()
  112. {
  113. return model('UserRecentlyRead');
  114. }
  115. /**
  116. * @return Sign
  117. */
  118. public function getSignModel()
  119. {
  120. return model('Sign');
  121. }
  122. /**
  123. * 更新用户操作时间
  124. * @param $userId
  125. * @param string $event
  126. */
  127. public function updateUserOperateTime($userId, $event = MessageConstants::MESSAGE_EVENT_UNSUBSCRIBE)
  128. {
  129. $time = Request::instance()->server('REQUEST_TIME');
  130. if ($userId) {
  131. $update = ['operate_time' => $time];
  132. if ($event != MessageConstants::MESSAGE_EVENT_UNSUBSCRIBE) {
  133. $user = UserService::instance()->getUserModel()->getUserInfo($userId);
  134. if (!empty($user) && isset($user['is_subscribe']) && $user['is_subscribe'] == UserConstants::USER_IS_SUBSCRIBE_NO) {
  135. $update['is_subscribe'] = UserConstants::USER_IS_SUBSCRIBE_YES;
  136. $update['subscribe_time'] = $time;
  137. }
  138. }
  139. $where = ['id' => $userId];
  140. UserService::instance()->update($update, $where);
  141. }
  142. }
  143. /**
  144. * 处理前台用户登录
  145. * @return \app\main\model\object\ReturnObject
  146. */
  147. public function frontUserLogin()
  148. {
  149. try{
  150. $response = false;
  151. if (Ua::isWeiXin()) {// 微信内
  152. //检测是否需要验证登录 && 检测是否登录
  153. if (!UrlService::instance()->checkActionMatch(OpenPlatformConstants::WEXIN_NEED_LOGIN) && !$this->isLogin()) {
  154. //登录
  155. $response = $this->login();
  156. }
  157. } else { // 其他(浏览器)
  158. if (UrlService::instance()->checkActionMatch(OpenPlatformConstants::BROWSER_NEED_LOGIN) && !$this->isLogin()) {
  159. //登录
  160. $response = $this->login();
  161. }
  162. }
  163. if ($response instanceof ReturnObject) {
  164. if ($response->code == ErrorCodeConstants::SUCCESS) {
  165. if (UserService::instance()->getRunTimeObject()->urlType == UrlConstants::REFERRAL_GROUND_PAGE_SPREAD) {
  166. UrlService::instance()->setUserAgentId((int)Request::instance()->get('agent_id'));
  167. UrlService::instance()->setUserReferralId((int)Request::instance()->get('referral_id'));
  168. }
  169. $result = UrlService::instance()->unlimitSecondaryDomain();
  170. if ($result->code != ErrorCodeConstants::SUCCESS) {
  171. return $result;
  172. }
  173. Log::info('用户已登录-> user_id:' . Cookie::get('user_id') . ' openid:' . Cookie::get('openid'));
  174. }
  175. return $response;
  176. }
  177. return $this->getReturn();
  178. }catch (\Exception $e){
  179. if ($e instanceof HttpException) {
  180. LogService::info('HttpException:' . $e->getMessage());
  181. throw $e;
  182. } else {
  183. return $this->getExceptionReturn($e);
  184. }
  185. }
  186. }
  187. /**
  188. * 获取用户id
  189. * @param $channel_id
  190. * @param $open_id
  191. * @return ReturnObject
  192. */
  193. public function getCallbackUserId($channel_id, $open_id)
  194. {
  195. $user_id = OfficialAccountsService::instance()->getOpenidModel()->getUserId($channel_id, $open_id);
  196. if ($user_id) {
  197. return $this->setData($user_id)->getReturn();
  198. }
  199. $user_id = Redis::instanceAuto()->incr('UID');
  200. return $this->setData($user_id)->getReturn();
  201. }
  202. /**
  203. * @param $channel_id
  204. * @param $open_id
  205. * @return ReturnObject
  206. */
  207. public function weChatInsertUser($channel_id, $open_id)
  208. {
  209. try {
  210. //获取基本信息
  211. $user_id = OfficialAccountsService::instance()->getOpenidModel()->getUserId($channel_id, $open_id);
  212. $user_info = null;
  213. if ($user_id) {
  214. $user_info = $this->getUserModel()->setConnect($user_id)->getUserInfo($user_id);
  215. }
  216. $time = time();
  217. //Redis 加锁
  218. $redis = Redis::instanceCache();
  219. $nxKey = 'L:' . $channel_id . ':' . $open_id;
  220. if (!$user_id || !$user_info) {
  221. if (!$nxRes = $redis->setnx($nxKey, 0)) { //已经存在
  222. return $this->setMsg('已存在')->getReturn();
  223. }
  224. $redis->expire($nxKey, 20); //20秒过期
  225. }
  226. //用户ID不存在时创建用户ID
  227. if (!$user_id) {
  228. if (!$user_id = $this->createUserID($channel_id, $open_id, $time)) {
  229. $redis->del($nxKey);
  230. return $this->setMsg('创建用户失败')->getReturn();
  231. }
  232. }
  233. //用户基本信息不存在时创建用户基本信息
  234. if (!$user_info) {
  235. if (!$info_id = $this->createUserInfo($channel_id, $open_id, $user_id, $time)) {
  236. $redis->del($nxKey);
  237. return $this->setMsg('用户基本信息添加失败')->getReturn();
  238. }
  239. //添加用户创建时间
  240. WhiteList::addUserCreatePayTime($user_id);
  241. }
  242. $redis->del($nxKey);
  243. return $this->setData($user_id)->getReturn();
  244. } catch (\Exception $e) {
  245. LogService::error('weChatInsertUser Error:' . $e->getMessage());
  246. return $this->setMsg('用户创建异常')->setCode(ErrorCodeConstants::EXCEPTION)->getReturn();
  247. }
  248. }
  249. /**
  250. * 创建用户基本信息
  251. * @param int $channel_id 渠道ID
  252. * @param string $open_id 微信OPEN_ID
  253. * @param int $user_id 用户ID
  254. * @param string $time 创建时间
  255. * @return null
  256. */
  257. public function createUserInfo($channel_id,$open_id,$user_id,$time){
  258. $weChatUser = [];
  259. try{
  260. $weChat = OpenPlatformService::instance()->officialAccount;
  261. $client = new Client(Config::get('wechat.http'));
  262. $weChat->http_client = $client;
  263. $weChatUser = $weChat->user->get($open_id);
  264. if(!empty($weChatUser) && !isset($weChatUser['errcode'])) {
  265. //获取注册赠送看点
  266. $ids = explode(',', Config::get('site.give_shubi_channel_id'));
  267. if (in_array($channel_id, $ids)) {
  268. $kandian_reg = 3000;
  269. } else {
  270. $kandian_reg = empty(Config::get('site.kandian_reg')) ? 0 : intval(Config::get('site.kandian_reg'));
  271. }
  272. $user = new UserObject();
  273. $user->id = $user_id;
  274. $user->openid = $open_id;
  275. $user->nickname = $weChatUser['nickname'] ?? '书友';
  276. $user->sex = $weChatUser['sex'] ?? 0;
  277. $user->first_cancel_pay = 2;
  278. $user->avatar = $weChatUser['headimgurl'] ?? cdnurl('/assets/img/frontend/icon/nav_icon_4.png');
  279. $user->is_subscribe = $weChatUser['subscribe'] ?? 0;
  280. $user->subscribe_time = $weChatUser['subscribe_time'] ?? 0;
  281. $user->operate_time = $time;
  282. $user->channel_id = $channel_id;
  283. $user->country = $weChatUser['country'] ?? '';
  284. $user->province = $weChatUser['province'] ?? '';
  285. $user->city = $weChatUser['city'] ?? '';
  286. $user->createtime = $time;
  287. $user->updatetime = $time;
  288. $user_info_id = $this->getUserModel()->setConnect($user->id)->insertGetId($user->toArray());
  289. if($user_info_id){
  290. //新增用户累计
  291. $cid = AdminService::instance()->getAdminExtendModel()->getChannelId($channel_id);
  292. $cacheKey = CacheConstants::getNewUserCacheKey($cid);
  293. Redis::instance()->incr($cacheKey);
  294. Redis::instance()->expire($cacheKey, 86400);
  295. LogService::debug('用户累计增加');
  296. //赠送用户免费看点
  297. $this->addFreeKanDian($user_id,$kandian_reg,$time);
  298. //新增用户打点
  299. $dotData = new DotObject();
  300. $dotData->user_id = $user_info_id;
  301. $dotData->channel_id = $channel_id;
  302. $dotData->sex = $user->sex;
  303. $dotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE;
  304. $dotData->type = MqConstants::MSG_TYPE_INCREASE;
  305. $dotData->event_time = Request::instance()->server('REQUEST_TIME');
  306. MqService::instance()->sendMessage($dotData);
  307. //性别打点
  308. if ($user->sex) {
  309. $oSexAna = new AnalysisObject();
  310. $oSexAna->event_time = $dotData->event_time;
  311. $oSexAna->type = KafkaDotConstants::TYPE_SEX;
  312. $oSexAna->data['sex'] = $user->sex;
  313. $oSexAna->data['user_collect'] = 1;
  314. KafkaDotService::instance()->sendMsg($user_info_id, $oSexAna);
  315. }
  316. //新增打点
  317. $oAna = new AnalysisObject();
  318. $oAna->event_time = $dotData->event_time;
  319. $oAna->type = KafkaDotConstants::TYPE_NEW;
  320. $oAna->data['user_collect'] = 1;
  321. KafkaDotService::instance()->sendMsg($user_info_id, $oAna);
  322. BookService::instance()->insertShelfRecommendBooks($user_info_id, $user->sex,$channel_id);
  323. return $user_info_id;
  324. }
  325. LogService::error('创建用户基本信息失败,USER表写入失败 Data:' . $user->toJson());
  326. }
  327. }catch (\Exception $e){
  328. LogService::notice('创建用户基本信息失败,Error:' . $e->getMessage());
  329. } finally {
  330. LogService::notice(json_encode($weChatUser, JSON_UNESCAPED_UNICODE));
  331. }
  332. return null;
  333. }
  334. /**
  335. * 伪造用户信息 (手机号注册时伪造openID创造用户)
  336. * @param int $channel_id 渠道ID
  337. * @param string $open_id 微信OPEN_ID
  338. * @param string $time 创建时间
  339. * @return null
  340. */
  341. public function fakeUserInfo($channel_id, $open_id, $phone, $time, $business_line = '0')
  342. {
  343. $redisAuto = Redis::instanceAuto();
  344. $user_id = $redisAuto->incr('UID'); //redis自增返回新的user_id
  345. try {
  346. //获取注册赠送看点
  347. $kandian_reg = empty(Config::get('site.phone_bind_credit')) ? 0 : intval(Config::get('site.phone_bind_credit'));
  348. $user = new UserObject();
  349. $user->id = $user_id;
  350. $user->openid = $open_id;
  351. $user->nickname = '书友';
  352. $user->sex = 0;
  353. $user->mobile = $phone;
  354. $user->first_cancel_pay = 2;
  355. $user->avatar = cdnurl('/assets/img/frontend/icon/nav_icon_4.png');
  356. $user->is_subscribe = 0;
  357. $user->subscribe_time = 0;
  358. $user->operate_time = $time;
  359. $user->channel_id = $channel_id;
  360. $user->country = '';
  361. $user->province = '';
  362. $user->city = '';
  363. $user->createtime = $time;
  364. $user->updatetime = $time;
  365. $user->business_line = PayConstants::BUSINESS_APP;
  366. if ($user_info_id = $this->getUserModel()->setConnect($user->id)->insertGetId($user->toArray())) {
  367. //赠送用户免费看点
  368. $this->addFreeKanDian($user_id, $kandian_reg, $time, 'APP手机注册赠送书币', $business_line);
  369. //新增用户打点
  370. $dotData = new DotObject();
  371. $dotData->user_id = $user_info_id;
  372. $dotData->channel_id = $channel_id;
  373. $dotData->sex = $user->sex;
  374. $dotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE;
  375. $dotData->type = MqConstants::MSG_TYPE_INCREASE;
  376. $dotData->event_time = Request::instance()->server('REQUEST_TIME');
  377. MqService::instance()->sendMessage($dotData);
  378. return $user_info_id;
  379. }
  380. Log::error('创建用户基本信息失败,USER表写入失败 Data:' . $user->toJson());
  381. } catch (\Exception $e) {
  382. Log::error('创建用户基本信息失败,Error:' . $e->getMessage());
  383. }
  384. return null;
  385. }
  386. /**
  387. * 添加用户免费看点
  388. * @param int $user_id 用户ID
  389. * @param int $kandian_reg 注册赠送看点
  390. * @param int $time 添加时间
  391. * @param string $notes 备注
  392. */
  393. public function addFreeKanDian($user_id,$kandian_reg,$time, $notes = '新用户注册赠送', $business_line = '0'){
  394. try{
  395. if (!$kandian_reg) {
  396. return;
  397. }
  398. //赠送用户免费书币
  399. $endTime = intval(Config::get('site.kandian_free_day')) * 86400;
  400. $rdata = [];
  401. $rdata['free_kandian'] = $kandian_reg;
  402. $rdata['remain_free_kandian'] = $kandian_reg;
  403. $rdata['type'] = 3;
  404. $rdata['user_id'] = $user_id;
  405. $rdata['free_endtime'] = $endTime + $time;
  406. $rdata['business_line'] = $business_line;
  407. $rdata['notes'] = $notes;
  408. $rdata['createtime'] = $time;
  409. $rdata['updatetime'] = $time;
  410. FinancialService::instance()->getRechargeModel()->setConnect($user_id)->insertGetId($rdata);
  411. //存入redis
  412. Redis::instance()->del('ZR:' . $user_id);
  413. }catch (\Exception $e){
  414. Log::error("添加注册用户免费看点记录失败,Error:".$e->getMessage());
  415. }
  416. }
  417. /**
  418. * 创建用户ID
  419. * @param int $channel_id 渠道ID
  420. * @param string $open_id 微信OPEN_ID
  421. * @param string $time 创建时间
  422. * @return int|null
  423. */
  424. public function createUserID($channel_id, $open_id, $time)
  425. {
  426. try {
  427. if (ApiService::instance()->checkApiOn()) {
  428. return \app\source\service\UserService::instance()->createUser($channel_id, $open_id)->id;
  429. } else {
  430. $redisAuto = Redis::instanceAuto();
  431. //redis自增返回新的user_id
  432. $userId = $redisAuto->incr('UID');
  433. $data = ['channel_openid' => $channel_id . '_' . $open_id, 'user_id' => $userId, 'createtime' => $time, 'updatetime' => $time];
  434. if ($autoId = OfficialAccountsService::instance()->getOpenidModel()->setConnect($channel_id, $open_id)->insertGetId($data)) {
  435. return $userId;
  436. }
  437. }
  438. Log::error('创建用户ID失败,OPENID表写入失败 Data:' . json_encode($data));
  439. } catch (\Exception $e) {
  440. Log::error('创建用户ID失败,Error:' . $e->getMessage());
  441. }
  442. return null;
  443. }
  444. /**
  445. * 微信登录
  446. * @return bool|RedirectResponse
  447. * @throws \Exception
  448. * @throws \think\Exception
  449. * @throws \think\db\exception\DataNotFoundException
  450. * @throws \think\db\exception\ModelNotFoundException
  451. * @throws \think\exception\DbException
  452. */
  453. public function login()
  454. {
  455. $urlType = $this->getRunTimeObject()->urlType;
  456. switch ($urlType) {
  457. case OpenPlatformConstants::URL_TYPE_ADMIN: // 后台首页
  458. break;
  459. case OpenPlatformConstants::URL_TYPE_PAY: // 充值页
  460. return $this->loginPay();
  461. break;
  462. case OpenPlatformConstants::URL_TYPE_SPREAD: // 推广页
  463. return $this->loginAgent();
  464. break;
  465. }
  466. return false;
  467. }
  468. /**
  469. * 充值页 登录
  470. * @return ReturnObject|bool
  471. * @throws \Exception
  472. */
  473. private function loginPay()
  474. {
  475. if (Ua::isWeiXin()) {
  476. $officialAccount = OpenPlatformService::instance()->getOfficialAccount();
  477. if (Request::instance()->has('code')) { // 具有code,直接获取openid
  478. try {
  479. if (Request::instance()->has('tqwv')) {
  480. $startTime = floatval(Request::instance()->param('tqwv'));
  481. $endTime = microtime(true);
  482. $this->useTime = $this->useTime + ($endTime - $startTime);
  483. Log::info(sprintf('WeChat->LoginPay->Auth->Time StartTime:%f EndTime:%f RunTime:%f', $startTime, $endTime, $endTime - $startTime));
  484. }
  485. $getOpenIDSTime = microtime(true);
  486. $user = $officialAccount->oauth->user();
  487. $getOpenIDETime = microtime(true);
  488. Cookie::forever('app_id', Config::get('wechat.app_id'));
  489. Cookie::forever('openid', $user->getId());
  490. Cookie::forever('open_id', $user->getId());
  491. $this->useTime = $this->useTime + ($getOpenIDETime - $getOpenIDSTime);
  492. Log::info(sprintf('WeChat->LoginPay->OpenID->Time OpenID:%s StartTime:%f EndTime:%f RunTime:', $user->getId(), $getOpenIDSTime, $getOpenIDETime, $getOpenIDETime - $getOpenIDSTime));
  493. } catch (\Exception $e) {
  494. return $this->loginFail($officialAccount, $e, 'tqwv');
  495. }
  496. } else {
  497. $startTime = microtime(true);
  498. $url = get_url_no_port(true);
  499. if (stripos($url, '?') === false) {
  500. $url .= '?tqwv=' . $startTime;
  501. } else {
  502. $url .= '&tqwv=' . $startTime;
  503. }
  504. $url = del_url_params($url, 'code');
  505. $response = $officialAccount->oauth->scopes(['snsapi_base'])->redirect($url); //获取code
  506. return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($response->getTargetUrl())->getReturn();
  507. }
  508. } else {
  509. $redirect = '/index/login/login?redirect=' . get_url_no_port(true);
  510. return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($redirect)->getReturn();
  511. }
  512. return true;
  513. }
  514. /**
  515. * 推广页 登录
  516. * @return \app\main\model\object\ReturnObject|bool|RedirectResponse
  517. * @throws \Exception
  518. * @throws \think\Exception
  519. * @throws \think\db\exception\DataNotFoundException
  520. * @throws \think\db\exception\ModelNotFoundException
  521. * @throws \think\exception\DbException
  522. */
  523. private function loginAgent()
  524. {
  525. $urlChannelId = $this->getRunTimeObject()->channelId;
  526. if (Ua::isWeiXin()) {
  527. $adminConfig = AdminService::instance()->getAdminConfigModel()->getAdminInfoAll($urlChannelId);
  528. $refreshToken = OpenPlatformService::instance()->getRefreshToken($adminConfig['admin_id']);
  529. $officialAccount = OpenPlatformService::instance()->getOfficialAccount($adminConfig['appid'], $refreshToken);
  530. if (Request::instance()->has('code')) { // 具有code,直接获取openid
  531. try {
  532. if (Request::instance()->has('asss')) {
  533. $getAuthSTime = floatval(Request::instance()->param('asss'));
  534. $getAuthETime = microtime(true);
  535. $this->useTime = $this->useTime + ($getAuthETime - $getAuthSTime);
  536. Log::info(sprintf('WeChat->LoginAgent->Auth->Time StartTime:%f EndTime:%f RunTime:%f', $getAuthSTime, $getAuthETime, $getAuthETime - $getAuthSTime));
  537. //异常访问抛出异常
  538. if ($getAuthETime - $getAuthSTime > 60) {
  539. throw new HttpException(500, '访问超时!');
  540. }
  541. }
  542. $getOpenIDStime = microtime(true);
  543. $oauth = $officialAccount->oauth;
  544. $oauth::setGuzzleOptions(Config::get('wechat.http'));
  545. $user = $oauth->user();
  546. $openid = $user->getId();
  547. $getOpenIDEtime = microtime(true);
  548. $this->useTime = $this->useTime + ($getOpenIDEtime - $getOpenIDStime);
  549. Log::info(sprintf('WeChat->LoginAgent->OpenID->Time OpenID:%s StartTime:%f EndTime:%f RunTime:%f', $user->getId(), $getOpenIDStime, $getOpenIDEtime, $getOpenIDEtime - $getOpenIDStime));
  550. if ($openid) {
  551. $nxKey = 'L:' . $urlChannelId . ':' . $openid; //用户openid_渠道商id ,唯一,用于防止重复注册
  552. $nxRes = Redis::instanceCache()->setnx($nxKey, 0);
  553. if (!$nxRes) { //已经存在
  554. throw new HttpException(500, '请刷新页面重新访问!'); //用户创建中
  555. }
  556. Redis::instanceCache()->expire($nxKey, 20); //20秒过期
  557. //通过openid获取用户id
  558. $userId = OfficialAccountsService::instance()->getOpenidModel()->getUserId($urlChannelId, $openid);
  559. if ($userId) {
  560. //通过userid获取用户信息
  561. $userInfo = $this->getUserModel()->setConnect($userId)->getUserInfo($userId);
  562. } else {
  563. $userInfo = false;
  564. }
  565. if ($userId && $userInfo) { //已经存在用户
  566. Redis::instanceCache()->del($nxKey);
  567. UserService::instance()->setUserCache($userId);
  568. } else {
  569. try {
  570. if (!$userId) { //存在 openid表插入成功,但user表插入失败的情况。所以单独做逻辑判断
  571. // 添加openid表信息
  572. $userId = $this->createUserID($urlChannelId, $openid, time());
  573. if (!$userId) {
  574. Log::error('创建用户失败!openid表插入数据失败!' . $urlChannelId . ':' . $openid);
  575. throw new HttpException(500, '创建用户失败!');
  576. }
  577. }
  578. $user = new UserObject();
  579. $user->id = $userId;
  580. $user->openid = $openid;
  581. $user->referral_id = (int)Request::instance()->get('referral_id');
  582. $user->referral_id_permanent = $user->referral_id;
  583. $user->agent_id = (int)Request::instance()->get('agent_id');
  584. $id = $this->insert($user);
  585. } catch (\Exception $exception) {
  586. Log::error('创建用户失败!' . $exception->getMessage());
  587. $id = null;
  588. }
  589. Redis::instanceCache()->del($nxKey);
  590. if (!$id) {
  591. Log::error('创建用户失败!user表插入数据失败!');
  592. throw new HttpException(500, '创建用户失败!');
  593. }
  594. $referral_id = UrlService::instance()->getUserReferralId()->data;
  595. if (isset($wechatUser) && !isset($wechatUser['errcode']) && !$wechatUser['subscribe'] && $referral_id) {
  596. Redis::instance()->setex('U-T:' . $id, 172800, $referral_id); //2天有效期
  597. }
  598. $result = UrlService::instance()->unlimitSecondaryDomain();
  599. if ($result->code != ErrorCodeConstants::SUCCESS) {
  600. return $result;
  601. }
  602. }
  603. }
  604. Log::info(sprintf('WeChat->CallInterface->Time UseTime:%f', $this->useTime));
  605. } catch (AuthorizeFailedException $e) {
  606. return $this->loginFail($officialAccount, $e, 'asss');
  607. }
  608. } else {
  609. $startTime = microtime(true);
  610. $url = get_url_no_port(true);
  611. //删掉多余参数
  612. $url = del_url_params($url, ['code', 'asss', 'state']);
  613. if (stripos($url, '?') === false) {
  614. $url .= '?asss=' . $startTime;
  615. } else {
  616. $url .= '&asss=' . $startTime;
  617. }
  618. //todo url跳转遗漏
  619. $response = $officialAccount->oauth->scopes(['snsapi_base'])->redirect($url); //获取code
  620. return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($response->getTargetUrl())->getReturn();
  621. }
  622. } else {
  623. $redirect = '/index/login/login?redirect=' . get_url_no_port(true);
  624. return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($redirect)->getReturn();
  625. }
  626. return $this->getReturn();
  627. }
  628. /**
  629. * @param \EasyWeChat\OfficialAccount\Application $officialAccount
  630. * @param \Exception $e
  631. * @param string $startQueryParamName
  632. * @return mixed
  633. */
  634. private function loginFail($officialAccount, $e, $startQueryParamName)
  635. {
  636. $errMsg = $e->getMessage();
  637. Log::notice('WeChat->Login->Error:' . $errMsg);
  638. $errMsg = str_replace('Authorize Failed:', '', $errMsg);
  639. $body = json_decode($errMsg, true);
  640. if (is_array($body) && in_array($body['errcode'], [40163, 40029, 41008])) { //code已被使用 或 code无效
  641. Log::notice('code 无效,重试一次 [ CODE ]: ' . $body['errcode']);
  642. $startTime = microtime(true);
  643. $url = UrlService::instance()->getUrlNoPort(true);
  644. if (stripos($url, '?') === false) {
  645. $url .= "?$startQueryParamName=$startTime";
  646. } else {
  647. $url .= "&$startQueryParamName=$startTime";
  648. }
  649. $url = UrlService::instance()->delUrlParams($url, 'code');
  650. if (!Request::instance()->has('jump_self')) {
  651. $url .= '&jump_self=1';
  652. $response = $officialAccount->oauth->scopes(['snsapi_base'])->redirect($url); //获取code
  653. return $this->setCode(ErrorCodeConstants::REDIRECT)->setData($response->getTargetUrl())->getReturn();
  654. }
  655. }
  656. }
  657. /**
  658. * 注册用户-推广页
  659. * @param $data
  660. * @return int|string
  661. * @throws \Exception
  662. */
  663. public function insert($data)
  664. {
  665. if (!($data instanceof BaseObject) && is_array($data)) {
  666. $data = (new UserObject())->bind($data);
  667. }
  668. $channelId = $this->getRunTimeObject()->channelId;
  669. $time = time();
  670. $ids = explode(',', Config::get('site.give_shubi_channel_id'));
  671. if (in_array($channelId, $ids)) {
  672. $kandian_reg = 3000;
  673. } else {
  674. $kandian_reg = empty(Config::get('site.kandian_reg')) ? 0 : intval(Config::get('site.kandian_reg'));
  675. }
  676. $default = new UserObject();
  677. $default->nickname = '书友';
  678. $default->avatar = cdnurl('/assets/img/frontend/icon/nav_icon_4.png');
  679. $default->channel_id = $channelId;
  680. $default->register_ip = Ip::ip() ?? null;
  681. $default->country = Ip::country();
  682. $default->area = Ip::area();
  683. $default->province = Ip::province();
  684. $default->first_cancel_pay = 2;
  685. $default->is_subscribe = 0;
  686. $default->subscribe_time = 0;
  687. $default->city = Ip::city();
  688. $default->sex = 0;
  689. $default->isp = substr(Ip::isp(), 0, 30); //数据库长度 30
  690. $default->createtime = $time;
  691. $default->updatetime = $time;
  692. $insert = array_merge($default->toArray(), $data->toArray());
  693. $id = $this->getUserModel()->setConnect($data->id)->insertGetId($insert);
  694. if ($id) { //注册成功
  695. //新增用户累计
  696. $cid = AdminService::instance()->getAdminExtendModel()->getChannelId($channelId);
  697. $cacheKey = CacheConstants::getNewUserCacheKey($cid);
  698. Redis::instance()->incr($cacheKey);
  699. Redis::instance()->expire($cacheKey, 86400);
  700. LogService::debug('用户累计增加');
  701. $data->id = $id;
  702. UserService::setUserCache($id);
  703. //赠送用户免费书币
  704. if ($kandian_reg) {
  705. $endTime = intval(Config::get('site.kandian_free_day')) * 86400;
  706. $rdata = [];
  707. $rdata['free_kandian'] = $kandian_reg;
  708. $rdata['remain_free_kandian'] = $kandian_reg;
  709. $rdata['type'] = 3;
  710. $rdata['user_id'] = $id;
  711. $rdata['free_endtime'] = $endTime + $time;
  712. $rdata['notes'] = '新用户注册赠送';
  713. $rdata['createtime'] = $time;
  714. $rdata['updatetime'] = $time;
  715. FinancialService::instance()->getRechargeModel()->setConnect($id)->insertGetId($rdata);
  716. Redis::instance()->del('ZR:' . $id);
  717. }
  718. if ($insert['sex']) {
  719. $oSexAna = new AnalysisObject();
  720. $oSexAna->event_time = UserService::instance()->getRunTimeObject()->requestTime;
  721. $oSexAna->type = KafkaDotConstants::TYPE_SEX;
  722. $oSexAna->data['sex'] = $insert['sex'];
  723. KafkaDotService::instance()->sendMsg($id, $oSexAna);
  724. }
  725. //新增用户打点
  726. $dotData = new DotObject();
  727. $dotData->channel_id = $channelId;
  728. $dotData->sex = $insert['sex'];
  729. $dotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE;
  730. $dotData->type = MqConstants::MSG_TYPE_INCREASE;
  731. $dotData->event_time = UserService::instance()->getRunTimeObject()->requestTime;
  732. MqService::instance()->sendMessage($dotData);
  733. $oAna = new AnalysisObject();
  734. $oAna->event_time = UserService::instance()->getRunTimeObject()->requestTime;
  735. $oAna->type = KafkaDotConstants::TYPE_NEW;
  736. //推广id的创建用户
  737. if ($referral_id = UrlService::instance()->getUserReferralId()->data) {
  738. $oAna->user_from['referral_id'] = $referral_id;
  739. $dotData->referral_id = $referral_id;
  740. $dotData->action_type = MqConstants::ROUTING_KEY_SUBSCRIBE_REFERER;
  741. MqService::instance()->sendMessage($dotData);
  742. }
  743. $oAna->data['user_collect'] = 1;
  744. KafkaDotService::instance()->sendMsg($id, $oAna);
  745. //加入用户统计
  746. // $this->getUserCollectModel()->addSubscribeCache($channelId, $data->sex, true);
  747. //添加用户创建时间
  748. WhiteList::addUserCreatePayTime($id);
  749. BookService::instance()->insertShelfRecommendBooks($id, $insert['sex'],$channelId);
  750. } else {
  751. Log::error('创建用户失败!user表插入数据失败!data:' . json_encode($data));
  752. }
  753. return $id;
  754. }
  755. /**
  756. * 刷新cookie资料
  757. *
  758. * @param $userInfo
  759. */
  760. public function refreshCookie($userInfo)
  761. {
  762. if (IS_CLI) {
  763. return;
  764. }
  765. if($userInfo instanceof BaseObject){
  766. $userInfo = $userInfo->toArray();
  767. }
  768. $urlType = $this->getRunTimeObject()->urlType;
  769. if (in_array($urlType, [OpenPlatformConstants::URL_TYPE_SPREAD, OpenPlatformConstants::URL_TYPE_PAY]) && $userInfo && isset($userInfo['id'])) {
  770. Cookie::set('user_id', $userInfo['id']);
  771. Cookie::set('channel_id', $userInfo['channel_id']);
  772. if (OpenPlatformConstants::URL_TYPE_SPREAD == $urlType) {
  773. Cookie::set('openid', $userInfo['openid']);
  774. }
  775. if ($userInfo['referral_id']) {
  776. Cookie::set('referral_id', $userInfo['referral_id']);
  777. } else {
  778. Cookie::delete('referral_id');
  779. }
  780. if ($userInfo['agent_id']) {
  781. Cookie::set('agent_id', $userInfo['agent_id']);
  782. } else {
  783. Cookie::delete('agent_id');
  784. }
  785. $ext = $userInfo['ext']??'';
  786. if ($ext) {
  787. Cookie::set('ext', $ext);
  788. } else {
  789. Cookie::delete('ext');
  790. }
  791. }
  792. }
  793. /**
  794. * 检测是否登录
  795. * @return bool
  796. */
  797. public function isLogin()
  798. {
  799. $urlType = $this->getRunTimeObject()->urlType;
  800. switch ($urlType) {
  801. case OpenPlatformConstants::URL_TYPE_ADMIN: // 后台首页
  802. break;
  803. case OpenPlatformConstants::URL_TYPE_PAY: // 充值页
  804. if (Ua::isWeiXin()) {
  805. if (!Cookie::has('user_id')) {
  806. throw new HttpException(402, '账号错误');
  807. }
  808. if (request()->isAjax()) {
  809. if (Cookie::has('openid') && Cookie::has('app_id') && Cookie::get('app_id') == Config::get('wechat.app_id')) {
  810. return true;
  811. }
  812. } else {
  813. if (
  814. Cookie::has('openid')
  815. && Cookie::has('open_id')
  816. && Cookie::get('openid') == Cookie::get('open_id')
  817. && Cookie::has('app_id')
  818. && Cookie::get('app_id') == Config::get('wechat.app_id')
  819. ) {
  820. return true;
  821. }
  822. }
  823. } elseif (Cookie::has('user_id')) {
  824. return true;
  825. }
  826. break;
  827. case OpenPlatformConstants::URL_TYPE_SPREAD: // 推广页
  828. if ($this->getUserInfo()->id) {
  829. return true;
  830. }
  831. break;
  832. }
  833. return false;
  834. }
  835. /**
  836. * 更新用户资料
  837. * @param $data
  838. * @param $where
  839. * @return bool
  840. */
  841. public function update($data, $where = [])
  842. {
  843. if (!$where) {
  844. $where['id'] = UserService::instance()->getUserInfo()->id;
  845. }
  846. if (isset($where['id']) && $where['id']) {
  847. $userId = $where['id'];
  848. if (UserService::instance()->getUserInfo()->id) {
  849. $this->getRunTimeObject()->user->bind($data);
  850. }
  851. if (ApiService::instance()->checkApiOn()) {
  852. $userUpdate = new UserUpdate();
  853. $userUpdate->bind($data)->setId($where['id']);
  854. // $data['id'] = $where['id'];
  855. \app\source\service\UserService::instance()->updateUser($userUpdate)->id;
  856. return true;
  857. } else {
  858. $result = $this->getUserModel()->setConnect($userId)->update($data, $where);
  859. if ($result) {
  860. //加入redis
  861. $userKey = CacheConstants::getUserCacheKey($userId);
  862. if(Redis::instance()->exists($userKey)){ //更改
  863. Redis::instance()->hmset($userKey,$data);
  864. Redis::instance()->expire($userKey,86400);
  865. }else{ //新增
  866. $this->getUserModel()->getUserInfo($userId);
  867. }
  868. if (!Request::instance()->isAjax() && !IS_CLI) {
  869. $this->refreshCookie($this->getUserInfo());
  870. }
  871. }
  872. return true;
  873. }
  874. }
  875. return false;
  876. }
  877. /**
  878. * 获取用户资料
  879. *
  880. * @param bool $force 是否从数据库重新获取 默认false从缓存中获取 true从数据库重新获取
  881. * @return UserObject
  882. */
  883. public function getUserInfo($force = false)
  884. {
  885. $urlType = $this->getRunTimeObject()->urlType;
  886. $user = $this->getRunTimeObject()->user;
  887. if (!$user || !$user->id || $force) {
  888. switch ($urlType) {
  889. case OpenPlatformConstants::URL_TYPE_ADMIN: // 后台首页
  890. break;
  891. case OpenPlatformConstants::URL_TYPE_PAY: // 充值页
  892. if ($userId = Cookie::get('user_id')) {
  893. $user = $this->getUserModel()->getUserInfo($userId, $force);
  894. }
  895. break;
  896. case OpenPlatformConstants::URL_TYPE_SPREAD: // 推广页
  897. if ($userId = Cookie::get('user_id')) {
  898. $user = $this->getUserModel()->getUserInfo($userId, $force);
  899. if (isset($user['openid']) && ($user['openid'] == Cookie::get('openid'))) {
  900. $this->refreshCookie($user);
  901. }
  902. }
  903. break;
  904. default:
  905. if ($userId = Cookie::get('user_id')) {
  906. $user = $this->getUserModel()->getUserInfo($userId, $force);
  907. }
  908. break;
  909. }
  910. if ($user) {
  911. $this->getRunTimeObject()->user = (new UserObject())->bind($user);
  912. $this->refreshCookie($user);
  913. }else{
  914. $this->getRunTimeObject()->user = new UserObject();
  915. }
  916. }
  917. if(!$this->getRunTimeObject()->user->avatar){
  918. $this->getRunTimeObject()->user->avatar = '/assets/img/frontend/icon/nav_icon_4.png';
  919. }
  920. return $this->getRunTimeObject()->user;
  921. }
  922. /**
  923. * 获取visitor,先获取cookie,若无,自动生成一个
  924. *
  925. * @param bool $force 是否强制生成一个新的
  926. * @return mixed|string
  927. */
  928. public function visitor($force = false)
  929. {
  930. if (!$force && Cookie::has('visitor')) {
  931. return Cookie::get('visitor');
  932. } else {
  933. return date('YmdHis') . Random::uuid();
  934. }
  935. }
  936. /**
  937. * 程序运行参数汇总
  938. * @return RunTimeObject
  939. */
  940. public function getRunTimeObject()
  941. {
  942. if(!$this->runTimeObject){
  943. $this->runTimeObject = new RunTimeObject();
  944. }
  945. return $this->runTimeObject;
  946. }
  947. /**
  948. * 魔术方法获取(只支持单级属性获取,多级属性请使用getUserInfo)
  949. *
  950. * @param $name
  951. * @return mixed|null
  952. */
  953. public function __get($name)
  954. {
  955. if ($this->getUserInfo()) {
  956. return $this->getUserInfo()->get($name) ?? null;
  957. } else {
  958. return null;
  959. }
  960. }
  961. /**
  962. * 获取用户表库索引
  963. * @param $user_id
  964. * @return int
  965. */
  966. public function getUserDBIndex($user_id)
  967. {
  968. return $user_id % 512;
  969. }
  970. /**
  971. * 获取openid表的库索引
  972. * @param $channel_id
  973. * @param $open_id
  974. * @return int
  975. */
  976. public function getOpenIdDBIndex($channel_id, $open_id)
  977. {
  978. $channel_openid = $channel_id . '_' . $open_id;
  979. return hash_code($channel_openid) % 512;
  980. }
  981. /**
  982. * 获取用户admin_id
  983. * @return \app\main\model\object\ReturnObject
  984. */
  985. public function getAdminId()
  986. {
  987. if ($agentId = UrlService::instance()->getUserAgentId()->data) {
  988. return $this->setData($agentId)->getReturn();
  989. } else if ($channelId = $this->getUserInfo()->channel_id) {
  990. return $this->setData($channelId)->getReturn();
  991. }
  992. return $this->getReturn();
  993. }
  994. public function setUserCache($user_id)
  995. {
  996. if ($user_id) {
  997. Cookie::set('user_id', $user_id);
  998. UserService::instance()->getUserInfo(true);
  999. }
  1000. }
  1001. /**
  1002. * 更新用户缓存
  1003. * @param $user_id
  1004. * @param $update
  1005. */
  1006. public function updateRedisUserCache($user_id, $update)
  1007. {
  1008. $userKey = CacheConstants::getUserCacheKey($user_id);
  1009. $this->getRunTimeObject()->user = $this->getRunTimeObject()->user->bind($update);
  1010. if (Redis::instance()->exists($userKey)) {
  1011. Redis::instance()->hmset($userKey, $update);
  1012. Redis::instance()->expire($userKey, 86400);
  1013. }
  1014. }
  1015. /**
  1016. * @return ReturnObject
  1017. */
  1018. public function getUserChannelId()
  1019. {
  1020. $channelAgentId = UserService::instance()->getUserInfo()->channel_id;
  1021. $groupId = AdminService::instance()->getAuthGroupAccessModel()->getGroupId($channelAgentId);
  1022. if ($groupId == AdminConstants::ADMIN_GROUP_ID_AGENT) {
  1023. $adminExtend = AdminService::instance()->getAdminExtendModel()->getInfo($channelAgentId);
  1024. return $this->setData($adminExtend['create_by'])->getReturn();
  1025. }
  1026. return $this->setData($channelAgentId)->getReturn();
  1027. }
  1028. /**
  1029. * 设置用户额外信息
  1030. * @param $user_id
  1031. * @param array $data
  1032. * @return bool
  1033. */
  1034. public function setOtherDataToRedis($user_id, $data = [])
  1035. {
  1036. $redisKey = self::USER_OTHER_DATA_KEY_PREFIX . $user_id;
  1037. $redisData = $data;
  1038. if (Redis::instance()->exists($redisKey)) {
  1039. $redisData = Redis::instance()->hGetAll($redisKey);
  1040. $redisData = array_merge($redisData, $data);
  1041. }
  1042. Redis::instance()->hMSet($redisKey, $redisData);
  1043. return Redis::instance()->expire($redisKey, self::USER_OTHER_DATA_EXPIRE);
  1044. }
  1045. /**
  1046. * 获取用户额外信息
  1047. * @param $user_id
  1048. * @return array
  1049. */
  1050. public function getOtherDataFromRedis($user_id)
  1051. {
  1052. $result = [];
  1053. $redisKey = self::USER_OTHER_DATA_KEY_PREFIX . $user_id;
  1054. if (Redis::instance()->exists($redisKey)) {
  1055. $result = Redis::instance()->hGetAll($redisKey);
  1056. }
  1057. return $this->setData($result)->getReturn()->data;
  1058. }
  1059. public function getUserIdByPhone($phone){
  1060. $json_data = $this->getUserPhoneModel()->setConnect($phone)->getInfoByPhone($phone);
  1061. $data = [];
  1062. if($json_data){
  1063. $data = json_decode($json_data, true);
  1064. }
  1065. return $data;
  1066. }
  1067. }