12 changed files with 693 additions and 11 deletions
@ -0,0 +1,113 @@ |
|||
<?php |
|||
declare (strict_types=1); |
|||
/** |
|||
* 工程基类 |
|||
* @since 2017/02/28 创建 |
|||
* @author zhaoxiang <zhaoxiang051405@gmail.com> |
|||
*/ |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
<?php |
|||
|
|||
namespace app\controller\wechat; |
|||
|
|||
use app\service\user\LoginService; |
|||
use think\App; |
|||
use think\facade\Request; |
|||
|
|||
class Login extends Base |
|||
{ |
|||
/** |
|||
* @var |
|||
*/ |
|||
public $auth; |
|||
protected $valid = \app\validate\Login::class; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$app = new App(); |
|||
parent::__construct($app); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @title 登录凭证校验 |
|||
* @return \think\Response|void |
|||
*/ |
|||
public function code2session(){ |
|||
|
|||
try { |
|||
$code = $this->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() |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
<?php |
|||
|
|||
namespace app\controller\wechat; |
|||
|
|||
|
|||
use app\service\wechat\WechatService; |
|||
use think\Request; |
|||
class Wechat extends Base |
|||
{ |
|||
|
|||
public function index(Request $request) |
|||
{ |
|||
|
|||
$param = $request->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,'微信认证失败!'); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
<?php |
|||
|
|||
namespace app\model; |
|||
|
|||
class WechatUser extends Base |
|||
{ |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
<?php |
|||
declare (strict_types = 1); |
|||
|
|||
namespace app\service; |
|||
|
|||
use think\facade\Request; |
|||
|
|||
class BaseService |
|||
{ |
|||
/** @var mixed|null 后台用户id */ |
|||
protected $admin_id = null; |
|||
/** @var mixed|null 后台用户信息 */ |
|||
protected $admin = null; |
|||
/** @var mixed|null 用户信息 */ |
|||
protected $user = null; |
|||
/** @var mixed|null 用户id */ |
|||
protected $user_id = null; |
|||
protected $is_admin = false; |
|||
protected $is_user = false; |
|||
/** @var string 用户身份 admin, user */ |
|||
protected $identity; |
|||
|
|||
public function __construct() { |
|||
|
|||
$this->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']; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,227 @@ |
|||
<?php |
|||
declare (strict_types = 1); |
|||
|
|||
namespace app\service\user; |
|||
|
|||
|
|||
use app\model\WechatUser; |
|||
use app\service\BaseService; |
|||
use fast\Http; |
|||
use think\facade\Cookie; |
|||
use think\facade\Session; |
|||
|
|||
class LoginService extends BaseService |
|||
{ |
|||
|
|||
/** |
|||
* 判断登录状态 |
|||
* @return bool |
|||
*/ |
|||
public function isLogin() { |
|||
if (!$this->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; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,139 @@ |
|||
<?php |
|||
declare (strict_types = 1); |
|||
|
|||
namespace app\service\wechat; |
|||
|
|||
use app\model\WechatUser; |
|||
use app\service\BaseService; |
|||
use fast\Http; |
|||
|
|||
class WechatService extends BaseService |
|||
{ |
|||
protected $token = ""; |
|||
protected $appid = ""; |
|||
protected $appsecret = ""; |
|||
|
|||
protected $redis = null; |
|||
|
|||
public function __construct() |
|||
{ |
|||
parent::__construct(); |
|||
|
|||
if($token = env('wechat.token')){ |
|||
$this->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(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
<?php |
|||
|
|||
namespace app\validate; |
|||
|
|||
use think\Validate; |
|||
|
|||
class Login extends Validate |
|||
{ |
|||
/** |
|||
* 验证规则. |
|||
*/ |
|||
protected $rule = [ |
|||
|
|||
'code|登录凭证' => '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'] |
|||
]; |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
// wxOpenConfig |
|||
'APP_ID' => env('wx.app_id'), |
|||
'APP_SECRET' => env('wx.app_secret'), |
|||
|
|||
//回调url |
|||
'REDIRECT_URI' => env('wx.redirect_uri'), |
|||
]; |
|||
@ -0,0 +1,14 @@ |
|||
<?php |
|||
/** |
|||
* wechat路由 |
|||
*/ |
|||
|
|||
use think\facade\Route; |
|||
|
|||
Route::group('wechat', function() { |
|||
// 微信验证路由地址 |
|||
Route::rule('Login/code2session', 'wechat.Login/code2session', 'post'); |
|||
Route::rule('Login/login', 'wechat.Login/login', 'post'); |
|||
Route::rule('Wechat/index', 'wechat.Wechat/index', 'get'); |
|||
|
|||
}); |
|||
Loading…
Reference in new issue