From b88a2ca63226c4ddbd6dc7b81e28803c26d78689 Mon Sep 17 00:00:00 2001 From: wanghongjun <1445693971@qq,com> Date: Thu, 25 Jul 2024 15:47:19 +0800 Subject: [PATCH] =?UTF-8?q?=E9=AA=8C=E8=AF=81xml=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E3=80=81=E5=BE=AE=E4=BF=A1=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E8=AE=A4=E8=AF=812?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 6 + app/controller/wechat/Base.php | 113 +++++++++++++ app/controller/wechat/Login.php | 64 ++++++++ app/controller/wechat/Wechat.php | 31 ++++ app/middleware.php | 3 +- app/model/WechatUser.php | 10 ++ app/service/BaseService.php | 46 ++++++ app/service/user/LoginService.php | 227 +++++++++++++++++++++++++++ app/service/wechat/WechatService.php | 139 ++++++++++++++++ app/validate/Login.php | 41 +++++ config/wx.php | 10 -- route/wechatRoute.php | 14 ++ 12 files changed, 693 insertions(+), 11 deletions(-) create mode 100644 app/controller/wechat/Base.php create mode 100644 app/controller/wechat/Login.php create mode 100644 app/controller/wechat/Wechat.php create mode 100644 app/model/WechatUser.php create mode 100644 app/service/BaseService.php create mode 100644 app/service/user/LoginService.php create mode 100644 app/service/wechat/WechatService.php create mode 100644 app/validate/Login.php delete mode 100644 config/wx.php create mode 100644 route/wechatRoute.php diff --git a/.env b/.env index ba5b796..97890c4 100644 --- a/.env +++ b/.env @@ -15,3 +15,9 @@ DEBUG = false [LANG] default_lang = zh-cn + +[WECHAT] +TOKEN = invoiceApi +APPID = wx95f03b3c0c33335f +APPSECRET = 117f446ea2bfdd7ef28e131faafd248b +REDIRECT_URI = https://intp.xingtongworld.com/admin/Login/wx \ No newline at end of file diff --git a/app/controller/wechat/Base.php b/app/controller/wechat/Base.php new file mode 100644 index 0000000..9047062 --- /dev/null +++ b/app/controller/wechat/Base.php @@ -0,0 +1,113 @@ + + */ + +namespace app\controller\wechat; + +use app\BaseController; +use app\service\user\LoginService; +use app\util\ReturnCode; +use fast\FuncException; +use think\App; +use think\facade\Env; +use think\facade\Request; +use think\facade\Session; +use think\Response; + +class Base extends BaseController { + + private $debug = []; + protected $noNeedLogin = []; + protected $user = []; + public $user_id = ''; + +// public function __construct() +// { +// $app = new App(); +// parent::__construct($app); +// +// try { +// if(!$this->user){ +// $bool = (new LoginService())->userAutologin(); +// if($bool){ +// $this->user = session('user'); +// } +// } +// if ($this->user){ +// $this->user_id = $this->user['id']; +// } +//// //需要登录接口进行校验 +// if (!$this->match($this->noNeedLogin)){ +// $this->checklogin(); +// } +// } catch (\Exception $e) { +// return $this->buildFailed($e->getCode() ?: 400,$e->getMessage()); +// } +// } + + /** + * 关联检测是否包含该请求是否包含该方法 + * @param $arr + * @return bool + */ + public function match($arr = []) { + $request = Request::instance(); + $arr = is_array($arr) ? $arr : explode(',', $arr); + if (! $arr) { + return false; + } + $arr = array_map('strtolower', $arr); + // 是否存在 + if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr)) { + return true; + } + // 没找到匹配 + return false; + } + + /** + * 用户登录检测 + */ + public function checkLogin() { + $login = new LoginService(); + if (!$login->isLogin()){ + throw new FuncException('用户未登录',302); + } + } + + public function buildSuccess(array $data = [], string $msg = '操作成功', int $code = ReturnCode::SUCCESS): Response { + $return = [ + 'code' => $code, + 'msg' => $msg, + 'data' => $data + ]; + if (Env::get('APP_DEBUG') && $this->debug) { + $return['debug'] = $this->debug; + } + + return json($return); + } + + public function buildFailed(int $code, string $msg = '操作失败', array $data = []): Response { + $return = [ + 'code' => $code, + 'msg' => $msg, + 'data' => $data + ]; + if (Env::get('APP_DEBUG') && $this->debug) { + $return['debug'] = $this->debug; + } + + return json($return); + } + + protected function debug($data): void { + if ($data) { + $this->debug[] = $data; + } + } +} diff --git a/app/controller/wechat/Login.php b/app/controller/wechat/Login.php new file mode 100644 index 0000000..db5b546 --- /dev/null +++ b/app/controller/wechat/Login.php @@ -0,0 +1,64 @@ +request->param('code'); + $iv = $this->request->param('iv'); + $encryptedData = $this->request->param('encryptedData'); + $loginService = new LoginService(); + $user = $loginService->code2session($code, $iv, $encryptedData); + return $this->buildSuccess($user); + } catch (\Exception $e) { //错误消息 $e->getMessage() + return $this->buildFailed($e->getCode() ?: 400,$e->getMessage()); + } + } + + /** + * @title 登录 + * @return \think\Response|void + */ + public function login(){ + try { + validate($this->valid)->scene('login')->check(Request::post()); + $phone = $this->request->post('phone'); + $openid = $this->request->post('openid'); + $unionid = $this->request->post('unionid'); + $loginService = new LoginService(); + $user = $loginService->userLogin($phone, $openid, $unionid); + return $this->buildSuccess($user); + } catch (\Exception $e) { //错误消息 $e->getMessage() + return $this->buildFailed($e->getCode() ?: 400,$e->getMessage()); + } + } + + public function userLogout() + { + + } +} \ No newline at end of file diff --git a/app/controller/wechat/Wechat.php b/app/controller/wechat/Wechat.php new file mode 100644 index 0000000..9ca1f7a --- /dev/null +++ b/app/controller/wechat/Wechat.php @@ -0,0 +1,31 @@ +param(); + + $signature = $param['signature'] ?? '';// 签名 + $timestamp = $param['timestamp'] ?? '';// 时间戳 + $nonce = $param['nonce'] ?? '';// 随机数 + $echostr = $param['echostr'] ?? '0'; // 随机字符串 + + $checkRes = (new WechatService())->wechatChekToken($signature,$timestamp,$nonce); + + $param['res_error'] = $checkRes; + + if ($checkRes) { + echo $echostr;die; + } + + return $this->buildFailed(400,'微信认证失败!'); + } +} \ No newline at end of file diff --git a/app/middleware.php b/app/middleware.php index d2c3fda..9971bdf 100644 --- a/app/middleware.php +++ b/app/middleware.php @@ -6,5 +6,6 @@ return [ // 多语言加载 // \think\middleware\LoadLangPack::class, // Session初始化 - // \think\middleware\SessionInit::class + \think\middleware\SessionInit::class, + \think\middleware\AllowCrossDomain::class, ]; diff --git a/app/model/WechatUser.php b/app/model/WechatUser.php new file mode 100644 index 0000000..2e4eb6a --- /dev/null +++ b/app/model/WechatUser.php @@ -0,0 +1,10 @@ +user = session('user'); + + $baseUrl = Request::baseUrl(); + //判断来自后台或用户 + if (preg_match('/^\/admin\//', $baseUrl)) { + $this->identity = 'admin'; + $this->is_admin = true; + $this->is_user = false; + } elseif (preg_match('/^\/wechat\//', $baseUrl)) { + $this->identity = 'user'; + $this->is_admin = false; + $this->is_user = true; + } + + if ($this->is_admin && $this->admin) { + $this->admin_id = $this->admin['id']; + } elseif ($this->is_user && $this->user) { + $this->user_id = $this->user['id']; + } + + } +} diff --git a/app/service/user/LoginService.php b/app/service/user/LoginService.php new file mode 100644 index 0000000..ea440cf --- /dev/null +++ b/app/service/user/LoginService.php @@ -0,0 +1,227 @@ +user) { + return false; + } + return true; + } + + /** + * 登录凭证校验 + * @param $code + * @param $iv + * @param $encryptedData + * @return array + * @throws \fast\FuncException + */ + public function code2session($code, $iv, $encryptedData){ + + $http = new Http(); + $url = "https://api.weixin.qq.com/sns/jscode2session?appid=".env("app.appid")."&secret=".env("app.appsecret")."&js_code={$code}&grant_type=authorization_code"; + $res = $http::get($url); + if($res['code'] != 200){ + throw new \fast\FuncException($res['msg']); + } + $res['data'] = json_decode($res['data'], true); + if(isset($res['data']['errcode'])){ + throw new \fast\FuncException($res['data']['errmsg']); + } + session('app_openid', $res['data']['openid']); + session('app_session_key', $res['data']['session_key']); + $res['userInfo'] = json_decode($this->decodeWechatIv($iv, $encryptedData), true); + + $result = []; + $result['openid'] = $res['data']['openid']; + if (isset($res['data']['unionid'])) $result['unionid'] = $res['data']['unionid']; + $result['phone'] = $res['userInfo']['phoneNumber']; + + return $result; + } + + /** + * 解密微信的iv和encryptedData + * @param $iv + * @param $encryptedData + * @return false|string + * @throws \fast\FuncException + */ + public function decodeWechatIv($iv, $encryptedData){ + $openid = session('app_openid'); + $session_key = session('app_session_key'); + if(!$openid || !$session_key){ + throw new \fast\FuncException('缺少主要参数'); + } + if (strlen($session_key) != 24) { + throw new \fast\FuncException('sessionkey长度错误'); + } + if (strlen($iv) != 24) { + throw new \fast\FuncException('iv长度错误'); + } + $aesKey=base64_decode($session_key); + $aesIV=base64_decode($iv); + $aesCipher=base64_decode($encryptedData); + $result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV); + $dataObj=json_decode($result); + if( $dataObj == NULL ) { + throw new \fast\FuncException('登录失败,请稍候再试'); + } + if( $dataObj->watermark->appid != env("app.appid") ) { + throw new \fast\FuncException('小程序appid不一致,登录失败'); + } + return $result; + + + } + + /** + * 用户端登录 + * @param $phone + * @param $openid + * @param $unionid + * @return WechatUser|mixed + * @throws \fast\FuncException + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function userLogin($phone, $openid, $unionid) { + $field = 'id,openid,phone,nickname,sex,headimgurl'; + $user = WechatUser::where('phone', $phone)->where('openid', $openid)->where('delete_time', 0)->field($field)->find(); + if($user){ + return $this->userSuccess($user); + } + return $this->register($phone, $openid, $unionid); + + } + + /** + * 用户登录成功 + * @param WechatUser $user + * @return array + */ + public function userSuccess(WechatUser $user) { + session('user', $user->toArray()); + $this->userKeeplogin($user->id,$user->openid,3600 * 24 * 7); +// $user->visible(['id', 'name', 'logo']); + return $user->toArray(); + } + + /** + * 保持登录 + * @param $user_id + * @param $token + * @param $keeptime + * @return bool + */ + protected function userKeeplogin($user_id,$token,$keeptime = 0) { + if ($keeptime) { + $expiretime = time() + $keeptime; + + $key = md5(md5(strval($user_id)) . md5(strval($keeptime)) . md5(strval($expiretime)) . $token); + error_reporting(E_ALL); + ini_set('display_errors','1'); + $data = [$user_id, $keeptime, $expiretime, $key]; + + Cookie::set('userKeeplogin', implode('|', $data), 86400 * 30); + return true; + } + return false; + } + + /** + * 用户端注册 + * @param $phone + * @param $openid + * @param $unionid + * @return WechatUser|mixed + * @throws \fast\FuncException + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function register($phone, $openid, $unionid) { + + $add = [ + 'phone' => $phone, + 'openid' => $openid, + 'nickname' => '微信用户', + 'unionid' => $unionid ?? '', + ]; + $id = (new WechatUser())->insertGetId($add); + + if(!$id){ + throw new \fast\FuncException('注册失败,请稍候再试'); + } + $user = WechatUser::where('id', $id)->find(); + return $this->userSuccess($user); + + } + + /** + * 自动登录 + * @return WechatUser|array|false|mixed|\think\Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function userAutologin() { + $keeplogin = Cookie::get('userKeeplogin'); + if (!$keeplogin) { + return false; + } + [$id, $keeptime, $expiretime, $key] = explode('|', $keeplogin); + if ($id && $keeptime && $expiretime && $key && $expiretime > time()) { + $user = WechatUser::where('id', $id)->find(); + if (!$user || !$user->token) { + return false; + } + unset($user->password); + //token有变更 + if ($key != md5(md5($id) . md5($keeptime) . md5($expiretime) . $user->token)) { + return false; + } + Session::set('user', $user->toArray()); + //刷新自动登录的时效 + $this->userKeeplogin($id,$user->token,$keeptime); + return $user; + } else { + return false; + } + } + + /** + * 登出 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function userLogout() { + $user = WechatUser::find($this->user_id); + if ($user) { + $user->token = ''; + $user->save(); + } + Session::delete('user'); + Cookie::delete('userKeeplogin'); + return true; + } +} + diff --git a/app/service/wechat/WechatService.php b/app/service/wechat/WechatService.php new file mode 100644 index 0000000..770c260 --- /dev/null +++ b/app/service/wechat/WechatService.php @@ -0,0 +1,139 @@ +token = $token; + } + + $this->appid = env('wechat.appid'); + $this->appsecret = env('wechat.appsecret'); + + $this->redis = new \Redis(); + $this->redis->connect('127.0.0.1', 6379); + } + + public function wechatChekToken($signature, $timestamp, $nonce){ + + $data = array($this->token, $timestamp, $nonce); + sort($data, SORT_STRING); + $str = sha1(implode( $data )); + + if($str == $signature){ + return true; + } + return false; + } + + /** + * 获取accessToken + * @return mixed|\Redis|string + * @throws \fast\FuncException + */ + public function getAccessToken(){ + $access_token = $this->redis->get('wechat_access_token'); + if($access_token){ + return $access_token; + } + $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appid}&secret={$this->appsecret}"; + $http = new Http(); + $res = $http::get($url); + if($res['code'] != 200){ + throw new \fast\FuncException($res['msg']); + } + $data = json_decode($res['data'], true); + if(isset($data['errcode']) && $data['errcode'] != 0){ + throw new \fast\FuncException($data['errmsg']); + } + $this->redis->set('wechat_access_token', $data['access_token'], 7200); + return $data['access_token']; + + } + + + public function wechatEvent(){ + + $data = file_get_contents("php://input"); + $obj = simplexml_load_string($data,"SimpleXMLElement", LIBXML_NOCDATA); + $this->log->info('消息', $obj->FromUserName); + + $event = $obj->MsgType; + switch ($event){ + case 'event': + $event = $obj->Event.'_'.$obj->MsgType; + $this->$event($obj); + break; + } + + + } + + + public function subscribe_event($obj){ + $url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=".$this->getAccessToken()."&openid=".$obj->FromUserName."&lang=zh_CN"; + $http = new Http(); + $res = $http::get($url); + if($res['code'] != 200){ + throw new \fast\FuncException($res['msg']); + } + $data = json_decode($res['data'], true); + if(isset($data['errcode']) && $data['errcode'] != 0){ + throw new \fast\FuncException($data['errmsg']); + } + + $user = WechatUser::where('openid', $data['openid'])->where('unionid', $data['unionid'])->where('is_deleted', 0)->find(); + if($user){ + $user->subscribe = 1; + $user->subscribe_time = date('Y-m-d H:i:s', $data['subscribe_time']); + $user->save(); + return; + } + + $user = [ + 'nickname' => $data['nickname'], + 'headimgurl' => $data['headimgurl'], + 'openid' => $data['openid'], + 'unionid' => $data['unionid'], + 'subscribe_time' => date('Y-m-d H:i:s', $data['subscribe_time']), + 'subscribe' => 1, + ]; + (new WechatUser())->save($user); + } + + + public function unsubscribe_event($obj){ + $url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=".$this->getAccessToken()."&openid=".$obj->FromUserName."&lang=zh_CN"; + $http = new Http(); + $res = $http::get($url); + if($res['code'] != 200){ + throw new \fast\FuncException($res['msg']); + } + $data = json_decode($res['data'], true); + if(isset($data['errcode']) && $data['errcode'] != 0){ + throw new \fast\FuncException($data['errmsg']); + } + + $user = WechatUser::where('openid', $data['openid'])->where('unionid', $data['unionid'])->where('is_deleted', 0)->find(); + if($user){ + $user->subscribe = 0; + $user->save(); + } + } +} diff --git a/app/validate/Login.php b/app/validate/Login.php new file mode 100644 index 0000000..e9f0ec2 --- /dev/null +++ b/app/validate/Login.php @@ -0,0 +1,41 @@ + 'require', + 'phone|手机号' => 'require|mobile', + 'openid|openid' => 'require', + 'iv|iv' => 'require', + 'encryptedData|encryptedData' => 'require', + #'unionid' => 'require', + ]; + + /** + * 提示消息. + */ + protected $message = [ + ]; + + /** + * 字段描述. + */ + protected $field = [ + ]; + + /** + * 验证场景. + */ + protected $scene = [ + 'code2session' => ['code', 'iv', 'encryptedData'], + 'login' => ['phone', 'openid', 'unionid'] + ]; +} \ No newline at end of file diff --git a/config/wx.php b/config/wx.php deleted file mode 100644 index c2a9cd2..0000000 --- a/config/wx.php +++ /dev/null @@ -1,10 +0,0 @@ - env('wx.app_id'), - 'APP_SECRET' => env('wx.app_secret'), - - //回调url - 'REDIRECT_URI' => env('wx.redirect_uri'), -]; \ No newline at end of file diff --git a/route/wechatRoute.php b/route/wechatRoute.php new file mode 100644 index 0000000..9f883b9 --- /dev/null +++ b/route/wechatRoute.php @@ -0,0 +1,14 @@ +