123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- <?php
- namespace App\Http\Api;
- use App\Services\Login\LoginTokenService;
- use EasyWeChat\Factory;
- use EasyWeChat\Kernel\Messages\News;
- use EasyWeChat\Kernel\Messages\NewsItem;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Request;
- use Illuminate\Support\Str;
- use Illuminate\Http\JsonResponse;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\Log;
- /**
- * 微信公众号授权
- *
- * Class WechatController
- * @package App\Http\Seller\Controllers
- */
- class WechatOfficialController extends HttpBaseController
- {
- protected $app = '';
- public function __construct()
- {
- parent::__construct();
- $this->setWebsite(__NAMESPACE__);
- $config = config('easywechat.official_account.default');
- $this->app = Factory::officialAccount($config);
- }
- // 测试登录
- public function testLogin()
- {
- if ($this->isTest()) {
- $userId = 1;
- $productMid = "web";
- $loginToken = new LoginTokenService($this->siteInfo['token_table']);
- $token = $loginToken->createOnlyOneToken($userId, $productMid);
- return responseMessage(1001, '', $token);
- }
- }
- public function index()
- {
- $this->app->server->push(function ($message) {
- $msgType = $message['MsgType'];
- if ($msgType == 'event') {
- $event = $message['Event'];
- return $this->msgEvent($event, $message);
- } else {
- return $this->msgtType($msgType, $message);
- }
- });
- $response = $this->app->server->serve();
- $response->send();
- exit();
- }
- public function index2()
- {
- $this->app->server->push(function ($message) {
- $msgType = $message['MsgType'];
- $event = $message['Event'];
- $eventKey = $message['EventKey'];
- $openid = $message['FromUserName'];
- // 替换字符串
- $eventKey = str_replace('qrscene_', '', $eventKey);
- /**
- * 扫描带参数二维码事件
- */
- if ($msgType == 'event') {
- $msg = '';
- switch ($event) {
- case 'subscribe': // 1. 用户未关注时,进行关注后的事件推送
- case 'SCAN': // 2. 用户已关注时的事件推送
- // 保存用户信息
- $isSuccess = true;//$this->saveUser($openid, $eventKey);
- if ($isSuccess) {
- $msg = "您好!欢迎使用 优速办公!";
- } else {
- $msg = "对不起!扫描失败,请重试!";
- }
- break;
- case 'unsubscribe': // 取消关注
- $this->loginOut($openid, $eventKey);
- break;
- }
- return $msg;
- } else {
- return "您好!欢迎使用 优速办公!";
- }
- });
- $response = $this->app->server->serve();
- $response->send();
- exit();
- }
- /**
- * @param $openid
- * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
- */
- private function saveUser($openid, $eventKey)
- {
- $userInfo = $this->app->user->get($openid);
- $qr_scene_str = $eventKey;
- // 场景值为 productMid:sessionId
- $sceneStr = base64_decode($qr_scene_str);
- if (empty($sceneStr)) {
- abort(500, '登录授权失败,非法操作!');
- }
- // 缓存当前的scene,防止用户重复点击登录
- $isClick = Cache::get('clickOne_' . md5($sceneStr));
- if (!$isClick) {
- abort(500, '登录授权失败,请重新扫码!');
- } else {
- Cache::forget('clickOne_' . md5($sceneStr));
- }
- // 分离产品id和session
- [$productMid, $sessionId] = explode(':', $sceneStr, 2);
- if (empty($productMid) || empty($sessionId)) {
- abort(500, '登录授权失败,非法操作!');
- }
- Log::info('$openid == ' . $openid);
- $nickname = '未知'; // 对应微信的 nickname
- $avatar = 'https://www.yososoft.com/static/images/softlogo.png'; // 头像网址
- $unionid = $userInfo['unionid'] ?? '';
- $wxInfo = $userInfo;
- $isExistUser = DB::table('user_wechat_official_account')->select('id')->where('openid', $openid)->first();
- if ($isExistUser) {
- // 更新头像
- $isSuccess = DB::transaction(function () use ($openid, $isExistUser, $unionid, $nickname, $avatar, $wxInfo) {
- DB::table('user_wechat_official_account')->where('id', $isExistUser->id)->update([
- 'openid' => $openid,
- 'unionid' => $unionid,
- 'nick' => $nickname,
- 'wx_avatar' => $avatar,
- 'wx_info' => json_encode($wxInfo),
- ]);
- DB::table('user')->where('id', $isExistUser->id)->update([
- 'unionid' => $unionid,
- 'username' => $nickname,
- 'avatar' => $avatar,
- ]);
- return true;
- });
- $userId = $isExistUser->id; // 用户id
- } else {
- // 插入数据
- $isSuccess = DB::transaction(function () use ($openid, $nickname, $avatar, $unionid, $wxInfo) {
- $userData = [
- 'username' => $nickname,
- 'avatar' => $avatar,
- 'roles' => json_encode([1]),
- 'unionid' => $unionid,
- 'status' => 1,
- 'mid' => Str::random(12),
- 'created_at' => time(),
- 'updated_at' => time(),
- ];
- $userId = DB::table('user')->insertGetId($userData);
- $officialData = [
- 'user_id' => $userId,
- 'openid' => $openid,
- 'unionid' => $unionid,
- 'nick' => $nickname,
- 'wx_avatar' => $avatar,
- 'wx_info' => json_encode($wxInfo),
- 'mid' => Str::random(12),
- 'created_at' => time(),
- 'updated_at' => time(),
- ];
- DB::table('user_wechat_official_account')->insertGetId($officialData);
- return $userId;
- });
- $userId = $isSuccess; // 用户id
- }
- if ($isSuccess) {
- // 保存生成token需要的信息
- $loginToken = new LoginTokenService($this->siteInfo['token_table']);
- $token = $loginToken->createOnlyOneToken($userId, $productMid);
- Cache::put('TOKEN_' . $sceneStr, $token, 5 * 60); // 有效期5分钟
- Log::info('33333333333333333333 === ' . 'TOKEN_' . $sceneStr . ' ==== ' . $token);
- // 如果用户直接点击手机登录链接,而没有扫描,则处理一下当前的扫描状态
- if (!Cache::get("SCAN_" . $sceneStr)) {
- Cache::put("SCAN_" . $sceneStr, true, 2 * 60); // 有效期2分钟
- }
- // 网页跳转,带一个随机参数,然后再通过该参数来换取session
- $key = md5(microtime() . $sceneStr);
- Cache::put($key, $token, 2 * 60); // 有效期3分钟
- return redirect('/mobile/#/?key=' . $key);
- } else {
- abort(500, '登录授权失败,请稍后再试!');
- }
- }
- private function msgEvent($event, $message)
- {
- // 场景值为 productMid:sessionId
- $sceneStr = $message['EventKey'] ?? '';
- // 新关注用户有qrscene_,替换掉
- $sceneStr = str_replace('qrscene_', '', $sceneStr);
- // $openid = Request::input('openid');
- switch ($event) {
- case 'subscribe': // 1. 用户未关注时,进行关注后的事件推送
- case 'SCAN': // 2. 用户已关注时的事件推送
- if (empty($sceneStr)) {
- return "欢迎你关注助友办公软件";
- } else {
- $oauthUrl = $this->app->oauth->withState(base64_encode($sceneStr))->redirect();
- //$oauthUrl = str_replace('https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx1edb103f32029099&redirect_uri=https%3A%2F%2Fwww.qasimblog.com%2Fapi%2Fwechat%2FoauthCallback&response_type=code&scope=snsapi_userinfo&state=WHdCeXhSMW5vUHgzOjUyNDEzYTY3MWVjNzAzNDc4M2FhMWEzMDMyNmRlMjNk&connect_redirect=1#wechat_redirect', '', $oauthUrl);
- $items = [
- new NewsItem([
- 'title' => '',
- 'description' => '',
- 'url' => $oauthUrl,
- 'image' => 'https://www.qasimblog.com/static/login.jpg',
- ]),
- new NewsItem([
- 'title' => '点击确认登录',
- 'description' => '',
- 'url' => $oauthUrl,
- 'image' => 'https://www.qasimblog.com/static/logo.png',
- ]),
- ];
- // 记录当前的扫描状态
- Cache::put("SCAN_" . $sceneStr, true, 2 * 60); // 有效期2分钟
- return new News($items); // 发送登录链接
- }
- case 'unsubscribe': // 取消关注
- break;
- }
- return 'success';
- }
- private function msgtType($type, $message)
- {
- switch ($type) {
- case 'text':
- case 'image':
- case 'voice': //语音消息
- case 'video': //视频消息
- case 'shortvideo': // 小视频消息
- case 'location': // 地理位置消息
- case 'link': // 链接消息
- default:
- $items = [
- new NewsItem([
- 'title' => '官方客服',
- 'description' => '需要帮助,请联系客服!',
- 'url' => 'https://work.weixin.qq.com/kfid/kfc5471afeb3f4331af',
- 'image' => 'https://www.qasimblog.com/static/images/u10.png',
- ]),
- ];
- $msg = new News($items);
- }
- return $msg;
- }
- /**
- * 创建登录的临时二维码
- *
- * @return JsonResponse
- */
- public function createQrcode()
- {
- /**
- * 场景值为 productMid:sessionId
- */
- $sceneStr = Request::post('scene_str'); //
- if (empty($sceneStr)) {
- Log::error("scene_str,为空!");
- return responseMessage(2001, '参数错误!');
- }
- $arr = explode(':', $sceneStr, 2);
- if (count($arr) != 2) {
- Log::error("scene_str,参数错误2,为空!");
- return responseMessage(2002, '参数错误!');
- }
- // 缓存当前的scene,防止用户重复点击登录
- Cache::put('clickOne_' . md5($sceneStr), true, 5 * 60);
- $result = $this->app->qrcode->temporary($sceneStr, 5 * 60);
- if (isset($result['ticket']) && $result['ticket']) {
- $url = $this->app->qrcode->url($result['ticket']);
- return responseMessage(1001, '', ['url' => $url, 'expire_seconds' => $result['expire_seconds']]);
- } else {
- Log::error("scene_str,参数错误3,请求错误!");
- return responseMessage(2002, '参数错误2!');
- }
- }
- public function oauthCallback()
- {
- $code = Request::input('code');
- $state = Request::input('state');
- if (empty($state)) {
- abort(500, '登录授权失败,参数错误!');
- }
- // 场景值为 productMid:sessionId
- $sceneStr = base64_decode($state);
- if (empty($sceneStr)) {
- abort(500, '登录授权失败,非法操作!');
- }
- $oauth = $this->app->oauth;
- $user = $oauth->userFromCode($code);
- Log::info('22222222222222222222222 === ' . 'code' . $code . ' ==== ' . $user);
- $openid = $user->getId();// 对应微信的 OPENID
- $nickname = $user->getNickname(); // 对应微信的 nickname
- $avatar = $user->getAvatar(); // 头像网址
- $tokenResponse = $user->getAttribute('token_response');
- $unionid = $tokenResponse['unionid'] ?? '';
- $wxInfo = $user->getAttributes();
- // 缓存当前的scene,防止用户重复点击登录
- $isClick = Cache::get('clickOne_' . md5($sceneStr));
- if (!$isClick) {
- abort(500, '登录授权失败,请重新扫码!');
- } else {
- Cache::forget('clickOne_' . md5($sceneStr));
- }
- // 分离产品id和session
- [$productMid, $sessionId] = explode(':', $sceneStr, 2);
- if (empty($productMid) || empty($sessionId)) {
- abort(500, '登录授权失败,非法操作!');
- }
- Log::info('$openid == ' . $openid);
- $isExistUser = DB::table('user_wechat_official_account')->select('id')->where('openid', $openid)->first();
- if ($isExistUser) {
- // 更新头像
- $isSuccess = DB::transaction(function () use ($openid, $isExistUser, $unionid, $nickname, $avatar, $wxInfo) {
- DB::table('user_wechat_official_account')->where('id', $isExistUser->id)->update([
- 'openid' => $openid,
- 'unionid' => $unionid,
- 'nick' => $nickname,
- 'wx_avatar' => $avatar,
- 'wx_info' => json_encode($wxInfo),
- ]);
- DB::table('user')->where('id', $isExistUser->id)->update([
- 'unionid' => $unionid,
- 'username' => $nickname,
- 'avatar' => $avatar,
- ]);
- return true;
- });
- $userId = $isExistUser->id; // 用户id
- } else {
- // 插入数据
- $isSuccess = DB::transaction(function () use ($openid, $nickname, $avatar, $unionid, $wxInfo) {
- $userData = [
- 'username' => $nickname,
- 'avatar' => $avatar,
- 'roles' => json_encode([1]),
- 'unionid' => $unionid,
- 'status' => 1,
- 'mid' => Str::random(12),
- 'created_at' => time(),
- 'updated_at' => time(),
- ];
- $userId = DB::table('user')->insertGetId($userData);
- $officialData = [
- 'user_id' => $userId,
- 'openid' => $openid,
- 'unionid' => $unionid,
- 'nick' => $nickname,
- 'wx_avatar' => $avatar,
- 'wx_info' => json_encode($wxInfo),
- 'mid' => Str::random(12),
- 'created_at' => time(),
- 'updated_at' => time(),
- ];
- DB::table('user_wechat_official_account')->insertGetId($officialData);
- return $userId;
- });
- $userId = $isSuccess; // 用户id
- }
- if ($isSuccess) {
- // 保存生成token需要的信息
- $loginToken = new LoginTokenService($this->siteInfo['token_table']);
- $token = $loginToken->createOnlyOneToken($userId, $productMid);
- Cache::put('TOKEN_' . $sceneStr, $token, 5 * 60); // 有效期5分钟
- Log::info('33333333333333333333 === ' . 'TOKEN_' . $sceneStr . ' ==== ' . $token);
- // 如果用户直接点击手机登录链接,而没有扫描,则处理一下当前的扫描状态
- if (!Cache::get("SCAN_" . $sceneStr)) {
- Cache::put("SCAN_" . $sceneStr, true, 2 * 60); // 有效期2分钟
- }
- // 网页跳转,带一个随机参数,然后再通过该参数来换取session
- $key = md5(microtime() . $sceneStr);
- Cache::put($key, $token, 2 * 60); // 有效期3分钟
- return redirect('/mobile/#/?key=' . $key);
- } else {
- abort(500, '登录授权失败,请稍后再试!');
- }
- }
- /**
- * web 网页登录
- *
- * 通过key换取session
- */
- public function key2token()
- {
- $key = Request::post('key');
- if (empty($key)) {
- return responseMessage(2001, '非法操作!');
- } else {
- $token = Cache::get($key);
- if ($token) {
- return responseMessage(1001, '', $token);
- } else {
- return responseMessage(2002, '登录失败,请重试!');
- }
- }
- }
- /**
- * 验证登录
- *
- * @return JsonResponse
- */
- public function checkLogin()
- {
- // 注意: 实际上不是scene_str,是sessionId
- $sessionId = Request::post('scene_str');
- if (empty($sessionId)) {
- return responseMessage(2001, '参数错误!');
- }
- // 过如果用户已经登录,则验证该用户是否需要更新token,7天有效
- $loginToken = new LoginTokenService($this->siteInfo['token_table']);
- if ($tokenInfo = $loginToken->checkLogin()) {
- //超过 7天 有效期,刷新token
- $limitTime = time() - $tokenInfo->updated_at;
- if ($limitTime > 7 * 24 * 3600) {
- // 需要重新登录,清除登录缓存
- $loginToken->destroyCurrentAccessToken();
- return responseMessage(2002, '登录已过期,请重新登录!');
- } elseif ($limitTime > 3 * 24 * 3600 && $limitTime < 7 * 24 * 3600) {
- // 大于3天 小于7天,则刷新token
- $token = $loginToken->createOnlyOneToken($tokenInfo->user_id, $tokenInfo->name);
- } else {
- $token = $loginToken->getToken();
- }
- return responseMessage(1001, '已登录!', $token);
- } else {
- $token = Cache::get('TOKEN_' . $sessionId);
- if (empty($token)) {
- return responseMessage(2003, '未登录!');
- } else {
- // 如果用户退出登录,而且缓存还没有失效,会出现检测已经登陆,
- // 解决:前端清除token,后端判断是否已经登陆
- if ($loginToken->findToken($token)) {
- return responseMessage(1001, 'success', $token);
- } else {
- //清除多余的缓存
- Cache::forget('TOKEN_' . $sessionId);
- Log::info('清除多余的token缓存 TOKEN_' . $sessionId);
- return responseMessage(2004, '未登录!');
- }
- }
- }
- }
- /**
- * 检测是否已经扫描
- */
- public function checkScan()
- {
- /**
- * 场景值为 productMid:sessionId
- */
- $sceneStr = Request::post('scene_str'); //
- if (empty($sceneStr)) {
- return responseMessage(2001, '参数错误!');
- }
- $isScan = Cache::get("SCAN_" . $sceneStr);
- if ($isScan) {
- // 删除缓存
- Cache::forget("SCAN_" . $sceneStr);
- return responseMessage(1001, '已扫描');
- } else {
- return responseMessage(2003, '等待中');
- }
- }
- }
|