mirror of
https://gitee.com/koogua/course-tencent-cloud.git
synced 2025-06-20 19:07:03 +08:00
!33 开放登录阶段性合并
* Merge remote-tracking branch 'gitee/xiaochong0302/I280IZ' into xiaocho… * 初步完成开放登录,待线上测试7 * Merge branch 'demo' of gitee.com:koogua/course-tencent-cloud into xiao… * 初步完成开放登录,待线上测试6 * !30 开放登录线上测试5 * !29 开放登录线上测试5 * 初步完成开放登录,待线上测试5 * !28 开放登录线上测试4 * 初步完成开放登录,待线上测试4 * !27 开放登录线上测试3 * 初步完成开放登录,待线上测试3 * !26 开放登录线上测试2 * 初步完成开放登录,待线上测试2 * !25 开放登录线上测试 * 初步完成开放登录,待线上测试 * !22 验证更新h5支付 * Merge remote-tracking branch 'remotes/gitee/develop' into demo * !20 验证更新h5支付 * Merge branch 'develop' of https://gitee.com/koogua/course-tencent-clou… * !16 v1.2.0阶段性合并 * 删除调试断点代码 * 删除重复的signature方法 * Merge branch 'develop' of https://gitee.com/koogua/course-tencent-clou… * demo后台增加统计 * !5 更新版本号1.1.0 * !4 v1.1.0版本develop->demo * Merge branch 'develop' into demo * 1.增加changelog.md * Merge branch 'develop' into demo * Merge branch 'develop' into demo * Merge branch 'develop' into demo * !1 精简优化代码 * Merge branch 'develop' into demo * 合并修改
This commit is contained in:
parent
753f2203fa
commit
dd99631f6e
@ -21,6 +21,17 @@ class Controller extends \Phalcon\Mvc\Controller
|
||||
|
||||
public function beforeExecuteRoute(Dispatcher $dispatcher)
|
||||
{
|
||||
/**
|
||||
* demo分支拒绝数据提交
|
||||
*/
|
||||
if ($this->isNotSafeRequest()) {
|
||||
$dispatcher->forward([
|
||||
'controller' => 'public',
|
||||
'action' => 'forbidden',
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isNotSafeRequest()) {
|
||||
$this->checkHttpReferer();
|
||||
$this->checkCsrfToken();
|
||||
|
@ -298,4 +298,33 @@ class SettingController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/oauth", name="admin.setting.oauth")
|
||||
*/
|
||||
public function oauthAction()
|
||||
{
|
||||
$settingService = new SettingService();
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
|
||||
$section = $this->request->getPost('section', 'string');
|
||||
|
||||
$data = $this->request->getPost();
|
||||
|
||||
$settingService->updateSettings($section, $data);
|
||||
|
||||
return $this->jsonSuccess(['msg' => '更新配置成功']);
|
||||
|
||||
} else {
|
||||
|
||||
$qqAuth = $settingService->getQQAuthSettings();
|
||||
$weixinAuth = $settingService->getWeixinAuthSettings();
|
||||
$weiboAuth = $settingService->getWeiboAuthSettings();
|
||||
|
||||
$this->view->setVar('qq_auth', $qqAuth);
|
||||
$this->view->setVar('weixin_auth', $weixinAuth);
|
||||
$this->view->setVar('weibo_auth', $weiboAuth);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -744,6 +744,12 @@ class AuthNode extends Service
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.setting.im',
|
||||
],
|
||||
[
|
||||
'id' => '5-1-12',
|
||||
'title' => '开放登录',
|
||||
'type' => 'menu',
|
||||
'route' => 'admin.setting.oauth',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
@ -9,6 +9,33 @@ use App\Repos\Vip as VipRepo;
|
||||
class Setting extends Service
|
||||
{
|
||||
|
||||
public function getQQAuthSettings()
|
||||
{
|
||||
$oauth = $this->getSettings('oauth.qq');
|
||||
|
||||
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.qq_callback']);
|
||||
|
||||
return $oauth;
|
||||
}
|
||||
|
||||
public function getWeixinAuthSettings()
|
||||
{
|
||||
$oauth = $this->getSettings('oauth.weixin');
|
||||
|
||||
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.weixin_callback']);
|
||||
|
||||
return $oauth;
|
||||
}
|
||||
|
||||
public function getWeiboAuthSettings()
|
||||
{
|
||||
$oauth = $this->getSettings('oauth.weibo');
|
||||
|
||||
$oauth['redirect_uri'] = $oauth['redirect_uri'] ?: kg_full_url(['for' => 'home.oauth.weibo_callback']);
|
||||
|
||||
return $oauth;
|
||||
}
|
||||
|
||||
public function getAlipaySettings()
|
||||
{
|
||||
$alipay = $this->getSettings('pay.alipay');
|
||||
@ -59,8 +86,16 @@ class Setting extends Service
|
||||
|
||||
$result = [];
|
||||
|
||||
/**
|
||||
* demo分支过滤敏感数据
|
||||
*/
|
||||
if ($items->count() > 0) {
|
||||
foreach ($items as $item) {
|
||||
$case1 = preg_match('/(id|auth|key|secret|password|pwd)$/', $item->item_key);
|
||||
$case2 = $this->dispatcher->getControllerName() == 'setting';
|
||||
if ($case1 && $case2) {
|
||||
$item->item_value = '***';
|
||||
}
|
||||
$result[$item->item_key] = $item->item_value;
|
||||
}
|
||||
}
|
||||
|
24
app/Http/Admin/Views/setting/oauth.volt
Normal file
24
app/Http/Admin/Views/setting/oauth.volt
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title kg-tab-title">
|
||||
<li class="layui-this">QQ登录</li>
|
||||
<li>微信登录</li>
|
||||
<li>新浪微博</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
{{ partial('setting/oauth_qq') }}
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
{{ partial('setting/oauth_weixin') }}
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
{{ partial('setting/oauth_weibo') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
35
app/Http/Admin/Views/setting/oauth_qq.volt
Normal file
35
app/Http/Admin/Views/setting/oauth_qq.volt
Normal file
@ -0,0 +1,35 @@
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">开启登录</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="enabled" value="1" title="是" {% if qq_auth.enabled == "1" %}checked="checked"{% endif %}>
|
||||
<input type="radio" name="enabled" value="0" title="否" {% if qq_auth.enabled == "0" %}checked="checked"{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">App ID</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="client_id" value="{{ qq_auth.client_id }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">App Secret</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="client_secret" value="{{ qq_auth.client_secret }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">Redirect Url</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="redirect_uri" value="{{ qq_auth.redirect_uri }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
<input type="hidden" name="section" value="oauth.qq">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
35
app/Http/Admin/Views/setting/oauth_weibo.volt
Normal file
35
app/Http/Admin/Views/setting/oauth_weibo.volt
Normal file
@ -0,0 +1,35 @@
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">开启登录</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="enabled" value="1" title="是" {% if weibo_auth.enabled == "1" %}checked="checked"{% endif %}>
|
||||
<input type="radio" name="enabled" value="0" title="否" {% if weibo_auth.enabled == "0" %}checked="checked"{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">App Key</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="client_id" value="{{ weibo_auth.client_id }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">App Secret</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="client_secret" value="{{ weibo_auth.client_secret }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">Redirect Uri</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="redirect_uri" value="{{ weibo_auth.redirect_uri }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
<input type="hidden" name="section" value="oauth.weibo">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
35
app/Http/Admin/Views/setting/oauth_weixin.volt
Normal file
35
app/Http/Admin/Views/setting/oauth_weixin.volt
Normal file
@ -0,0 +1,35 @@
|
||||
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">开启登录</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="radio" name="enabled" value="1" title="是" {% if weixin_auth.enabled == "1" %}checked="checked"{% endif %}>
|
||||
<input type="radio" name="enabled" value="0" title="否" {% if weixin_auth.enabled == "0" %}checked="checked"{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">App ID</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="client_id" value="{{ weixin_auth.client_id }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">App Secret</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="client_secret" value="{{ weixin_auth.client_secret }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">Redirect Url</label>
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="redirect_uri" value="{{ weixin_auth.redirect_uri }}" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label"></label>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
|
||||
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||
<input type="hidden" name="section" value="oauth.weixin">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -22,5 +22,12 @@
|
||||
|
||||
{% block include_js %}{% endblock %}
|
||||
{% block inline_js %}{% endblock %}
|
||||
|
||||
{% set site = setting('site') %}
|
||||
|
||||
{% if site['analytics_enabled'] == 1 %}
|
||||
{{ site['analytics_script'] }}
|
||||
{% endif %}
|
||||
|
||||
</body>
|
||||
</html>
|
@ -4,6 +4,7 @@ namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Http\Home\Services\Account as AccountService;
|
||||
use App\Services\Logic\Account\EmailUpdate as EmailUpdateService;
|
||||
use App\Services\Logic\Account\OAuthProvider as OAuthProviderService;
|
||||
use App\Services\Logic\Account\PasswordReset as PasswordResetService;
|
||||
use App\Services\Logic\Account\PasswordUpdate as PasswordUpdateService;
|
||||
use App\Services\Logic\Account\PhoneUpdate as PhoneUpdateService;
|
||||
@ -62,8 +63,13 @@ class AccountController extends Controller
|
||||
|
||||
$captcha = $service->getSettings('captcha');
|
||||
|
||||
$service = new OAuthProviderService();
|
||||
|
||||
$oauthProvider = $service->handle();
|
||||
|
||||
$returnUrl = $this->request->getHTTPReferer();
|
||||
|
||||
$this->view->setVar('oauth_provider', $oauthProvider);
|
||||
$this->view->setVar('return_url', $returnUrl);
|
||||
$this->view->setVar('captcha', $captcha);
|
||||
}
|
||||
|
139
app/Http/Home/Controllers/ConnectController.php
Normal file
139
app/Http/Home/Controllers/ConnectController.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Http\Home\Services\Connect as ConnectService;
|
||||
use App\Models\Connect as ConnectModel;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/oauth")
|
||||
*/
|
||||
class ConnectController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/qq", name="home.oauth.qq")
|
||||
*/
|
||||
public function qqAction()
|
||||
{
|
||||
$service = new ConnectService();
|
||||
|
||||
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_QQ);
|
||||
|
||||
return $this->response->redirect($url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/weixin", name="home.oauth.weixin")
|
||||
*/
|
||||
public function weixinAction()
|
||||
{
|
||||
$service = new ConnectService();
|
||||
|
||||
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_WEIXIN);
|
||||
|
||||
return $this->response->redirect($url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/weibo", name="home.oauth.weibo")
|
||||
*/
|
||||
public function weiboAction()
|
||||
{
|
||||
$service = new ConnectService();
|
||||
|
||||
$url = $service->getAuthorizeUrl(ConnectModel::PROVIDER_WEIBO);
|
||||
|
||||
return $this->response->redirect($url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/qq/callback", name="home.oauth.qq_callback")
|
||||
*/
|
||||
public function qqCallbackAction()
|
||||
{
|
||||
$this->handleCallback(ConnectModel::PROVIDER_QQ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/weixin/callback", name="home.oauth.weixin_callback")
|
||||
*/
|
||||
public function weixinCallbackAction()
|
||||
{
|
||||
$this->handleCallback(ConnectModel::PROVIDER_WEIXIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/weibo/callback", name="home.oauth.weibo_callback")
|
||||
*/
|
||||
public function weiboCallbackAction()
|
||||
{
|
||||
$this->handleCallback(ConnectModel::PROVIDER_WEIBO);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/weibo/refuse", name="home.oauth.weibo_refuse")
|
||||
*/
|
||||
public function weiboRefuseAction()
|
||||
{
|
||||
return $this->response->redirect(['for' => 'home.account.login']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/bind/login", name="home.oauth.bind_login")
|
||||
*/
|
||||
public function bindLoginAction()
|
||||
{
|
||||
$service = new ConnectService();
|
||||
|
||||
$service->bindLogin();
|
||||
|
||||
$location = $this->url->get(['for' => 'home.uc.account']);
|
||||
|
||||
return $this->jsonSuccess(['location' => $location]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/bind/register", name="home.oauth.bind_register")
|
||||
*/
|
||||
public function bindRegisterAction()
|
||||
{
|
||||
$service = new ConnectService();
|
||||
|
||||
$service->bindRegister();
|
||||
|
||||
$location = $this->url->get(['for' => 'home.uc.account']);
|
||||
|
||||
return $this->jsonSuccess(['location' => $location]);
|
||||
}
|
||||
|
||||
protected function handleCallback($provider)
|
||||
{
|
||||
$code = $this->request->getQuery('code');
|
||||
$state = $this->request->getQuery('state');
|
||||
|
||||
$service = new ConnectService();
|
||||
|
||||
$openUser = $service->getOpenUserInfo($code, $state, $provider);
|
||||
|
||||
$connect = $service->getConnectRelation($openUser['id'], $provider);
|
||||
|
||||
if ($connect && $connect->deleted == 0) {
|
||||
if ($this->authUser->id > 0) {
|
||||
$service->bindUser($openUser, $provider);
|
||||
return $this->response->redirect(['for' => 'home.uc.account']);
|
||||
} else {
|
||||
$service->authLogin($connect);
|
||||
return $this->response->redirect(['for' => 'home.index']);
|
||||
}
|
||||
}
|
||||
|
||||
$captcha = $service->getSettings('captcha');
|
||||
|
||||
$this->view->pick('connect/bind');
|
||||
$this->view->setVar('captcha', $captcha);
|
||||
$this->view->setVar('provider', $provider);
|
||||
$this->view->setVar('open_user', $openUser);
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ use App\Services\Logic\Order\OrderCancel as OrderCancelService;
|
||||
use App\Services\Logic\Order\OrderConfirm as OrderConfirmService;
|
||||
use App\Services\Logic\Order\OrderCreate as OrderCreateService;
|
||||
use App\Services\Logic\Order\OrderInfo as OrderInfoService;
|
||||
use App\Services\Logic\Order\PayProvider as PayProviderService;
|
||||
use Phalcon\Mvc\Dispatcher;
|
||||
use Phalcon\Mvc\View;
|
||||
|
||||
@ -82,6 +83,10 @@ class OrderController extends Controller
|
||||
{
|
||||
$sn = $this->request->getQuery('sn', 'string');
|
||||
|
||||
$service = new PayProviderService();
|
||||
|
||||
$payProvider = $service->handle();
|
||||
|
||||
$service = new OrderInfoService();
|
||||
|
||||
$order = $service->handle($sn);
|
||||
@ -90,6 +95,7 @@ class OrderController extends Controller
|
||||
$this->response->redirect(['for' => 'home.uc.orders']);
|
||||
}
|
||||
|
||||
$this->view->setVar('pay_provider', $payProvider);
|
||||
$this->view->setVar('order', $order);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
namespace App\Http\Home\Controllers;
|
||||
|
||||
use App\Services\Logic\Account\OAuthProvider as OAuthProviderService;
|
||||
use App\Services\Logic\User\Console\AccountInfo as AccountInfoService;
|
||||
use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService;
|
||||
use App\Services\Logic\User\Console\ConnectList as ConnectListService;
|
||||
use App\Services\Logic\User\Console\ConsultList as ConsultListService;
|
||||
use App\Services\Logic\User\Console\CourseList as CourseListService;
|
||||
use App\Services\Logic\User\Console\FavoriteList as FavoriteListService;
|
||||
@ -59,13 +62,21 @@ class UserConsoleController extends Controller
|
||||
*/
|
||||
public function accountAction()
|
||||
{
|
||||
$type = $this->request->getQuery('type', 'string', 'info');
|
||||
|
||||
$service = new AccountInfoService();
|
||||
|
||||
$captcha = $service->getSettings('captcha');
|
||||
|
||||
$account = $service->handle();
|
||||
|
||||
$type = $this->request->getQuery('type', 'string', 'info');
|
||||
$service = new OAuthProviderService();
|
||||
|
||||
$oauthProvider = $service->handle();
|
||||
|
||||
$service = new ConnectListService();
|
||||
|
||||
$connects = $service->handle();
|
||||
|
||||
if ($type == 'info') {
|
||||
$this->view->pick('user/console/account_info');
|
||||
@ -77,6 +88,8 @@ class UserConsoleController extends Controller
|
||||
$this->view->pick('user/console/account_password');
|
||||
}
|
||||
|
||||
$this->view->setVar('oauth_provider', $oauthProvider);
|
||||
$this->view->setVar('connects', $connects);
|
||||
$this->view->setVar('captcha', $captcha);
|
||||
$this->view->setVar('account', $account);
|
||||
}
|
||||
@ -207,4 +220,23 @@ class UserConsoleController extends Controller
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/connect/{id:[0-9]+}/delete", name="home.uc.unconnect")
|
||||
*/
|
||||
public function deleteConnectAction($id)
|
||||
{
|
||||
$service = new ConnectDeleteService();
|
||||
|
||||
$service->handle($id);
|
||||
|
||||
$location = $this->url->get(['for' => 'home.uc.account']);
|
||||
|
||||
$content = [
|
||||
'location' => $location,
|
||||
'msg' => '解除登录绑定成功',
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($content);
|
||||
}
|
||||
|
||||
}
|
||||
|
208
app/Http/Home/Services/Connect.php
Normal file
208
app/Http/Home/Services/Connect.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Home\Services;
|
||||
|
||||
use App\Models\Connect as ConnectModel;
|
||||
use App\Models\User as UserModel;
|
||||
use App\Repos\Connect as ConnectRepo;
|
||||
use App\Repos\User as UserRepo;
|
||||
use App\Services\Auth\Home as AuthService;
|
||||
use App\Services\Logic\Account\Register as RegisterService;
|
||||
use App\Services\OAuth\QQ as QQAuth;
|
||||
use App\Services\OAuth\WeiBo as WeiBoAuth;
|
||||
use App\Services\OAuth\WeiXin as WeiXinAuth;
|
||||
use App\Validators\Account as AccountValidator;
|
||||
|
||||
class Connect extends Service
|
||||
{
|
||||
|
||||
public function bindLogin()
|
||||
{
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$auth = $this->getConnectAuth($post['provider']);
|
||||
|
||||
$auth->checkState($post['state']);
|
||||
|
||||
$validator = new AccountValidator();
|
||||
|
||||
$user = $validator->checkUserLogin($post['account'], $post['password']);
|
||||
|
||||
$openUser = json_decode($post['open_user'], true);
|
||||
|
||||
$this->handleConnectRelation($user, $openUser, $post['provider']);
|
||||
|
||||
$auth = $this->getAppAuth();
|
||||
|
||||
$auth->saveAuthInfo($user);
|
||||
}
|
||||
|
||||
public function bindRegister()
|
||||
{
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$auth = $this->getConnectAuth($post['provider']);
|
||||
|
||||
$auth->checkState($post['state']);
|
||||
|
||||
$openUser = json_decode($post['open_user'], true);
|
||||
|
||||
$registerService = new RegisterService();
|
||||
|
||||
$account = $registerService->handle();
|
||||
|
||||
$userRepo = new UserRepo();
|
||||
|
||||
$user = $userRepo->findById($account->id);
|
||||
|
||||
$this->handleConnectRelation($user, $openUser, $post['provider']);
|
||||
|
||||
$auth = $this->getAppAuth();
|
||||
|
||||
$auth->saveAuthInfo($user);
|
||||
}
|
||||
|
||||
public function bindUser($openUser, $provider)
|
||||
{
|
||||
$user = $this->getLoginUser();
|
||||
|
||||
$this->handleConnectRelation($user, $openUser, $provider);
|
||||
}
|
||||
|
||||
public function authLogin(ConnectModel $connect)
|
||||
{
|
||||
$userRepo = new UserRepo();
|
||||
|
||||
$user = $userRepo->findById($connect->user_id);
|
||||
|
||||
$auth = $this->getAppAuth();
|
||||
|
||||
$auth->saveAuthInfo($user);
|
||||
}
|
||||
|
||||
public function getAuthorizeUrl($provider)
|
||||
{
|
||||
$auth = $this->getConnectAuth($provider);
|
||||
|
||||
return $auth->getAuthorizeUrl();
|
||||
}
|
||||
|
||||
public function getOpenUserInfo($code, $state, $provider)
|
||||
{
|
||||
$auth = $this->getConnectAuth($provider);
|
||||
|
||||
$auth->checkState($state);
|
||||
|
||||
$token = $auth->getAccessToken($code);
|
||||
|
||||
$openId = $auth->getOpenId($token);
|
||||
|
||||
return $auth->getUserInfo($token, $openId);
|
||||
}
|
||||
|
||||
public function getConnectRelation($openId, $provider)
|
||||
{
|
||||
$connectRepo = new ConnectRepo();
|
||||
|
||||
return $connectRepo->findByOpenId($openId, $provider);
|
||||
}
|
||||
|
||||
public function getConnectAuth($provider)
|
||||
{
|
||||
$auth = null;
|
||||
|
||||
switch ($provider) {
|
||||
case ConnectModel::PROVIDER_QQ:
|
||||
$auth = $this->getQQAuth();
|
||||
break;
|
||||
case ConnectModel::PROVIDER_WEIXIN:
|
||||
$auth = $this->getWeiXinAuth();
|
||||
break;
|
||||
case ConnectModel::PROVIDER_WEIBO:
|
||||
$auth = $this->getWeiBoAuth();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$auth) {
|
||||
throw new \Exception('Invalid OAuth Provider');
|
||||
}
|
||||
|
||||
return $auth;
|
||||
}
|
||||
|
||||
protected function getQQAuth()
|
||||
{
|
||||
$settings = $this->getSettings('oauth.qq');
|
||||
|
||||
return new QQAuth(
|
||||
$settings['client_id'],
|
||||
$settings['client_secret'],
|
||||
$settings['redirect_uri']
|
||||
);
|
||||
}
|
||||
|
||||
protected function getWeiXinAuth()
|
||||
{
|
||||
$settings = $this->getSettings('oauth.weixin');
|
||||
|
||||
return new WeiXinAuth(
|
||||
$settings['client_id'],
|
||||
$settings['client_secret'],
|
||||
$settings['redirect_uri']
|
||||
);
|
||||
}
|
||||
|
||||
protected function getWeiBoAuth()
|
||||
{
|
||||
$settings = $this->getSettings('oauth.weibo');
|
||||
|
||||
return new WeiBoAuth(
|
||||
$settings['client_id'],
|
||||
$settings['client_secret'],
|
||||
$settings['redirect_uri']
|
||||
);
|
||||
}
|
||||
|
||||
protected function getAppAuth()
|
||||
{
|
||||
/**
|
||||
* @var $auth AuthService
|
||||
*/
|
||||
$auth = $this->getDI()->get('auth');
|
||||
|
||||
return $auth;
|
||||
}
|
||||
|
||||
protected function handleConnectRelation(UserModel $user, array $openUser, $provider)
|
||||
{
|
||||
$connectRepo = new ConnectRepo();
|
||||
|
||||
$connect = $connectRepo->findByOpenId($openUser['id'], $provider);
|
||||
|
||||
if ($connect) {
|
||||
|
||||
if (time() - $connect->update_time > 86400) {
|
||||
$connect->open_name = $openUser['name'];
|
||||
$connect->open_avatar = $openUser['avatar'];
|
||||
}
|
||||
|
||||
if ($connect->deleted == 1) {
|
||||
$connect->deleted = 0;
|
||||
$connect->update();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$connect = new ConnectModel();
|
||||
|
||||
$connect->user_id = $user->id;
|
||||
$connect->open_id = $openUser['id'];
|
||||
$connect->open_name = $openUser['name'];
|
||||
$connect->open_avatar = $openUser['avatar'];
|
||||
$connect->provider = $provider;
|
||||
|
||||
$connect->create();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -27,6 +27,17 @@
|
||||
<span class="separator">·</span>
|
||||
<a class="forget-link" href="{{ url({'for':'home.account.forget_pwd'}) }}">忘记密码</a>
|
||||
</div>
|
||||
<div class="oauth">
|
||||
{% if oauth_provider.qq.enabled == 1 %}
|
||||
<a class="layui-icon layui-icon-login-qq login-qq" href="{{ url({'for':'home.oauth.qq'}) }}"></a>
|
||||
{% endif %}
|
||||
{% if oauth_provider.weixin.enabled == 1 %}
|
||||
<a class="layui-icon layui-icon-login-wechat login-wechat" href="{{ url({'for':'home.oauth.weixin'}) }}"></a>
|
||||
{% endif %}
|
||||
{% if oauth_provider.weibo.enabled == 1 %}
|
||||
<a class="layui-icon layui-icon-login-weibo login-weibo" href="{{ url({'for':'home.oauth.weibo'}) }}"></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
34
app/Http/Home/Views/connect/bind.volt
Normal file
34
app/Http/Home/Views/connect/bind.volt
Normal file
@ -0,0 +1,34 @@
|
||||
{% extends 'templates/main.volt' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="layui-breadcrumb breadcrumb">
|
||||
<a href="/">首页</a>
|
||||
<a><cite>登录绑定</cite></a>
|
||||
</div>
|
||||
|
||||
<div class="login-wrap wrap">
|
||||
<div class="layui-tab layui-tab-brief login-tab">
|
||||
<ul class="layui-tab-title login-tab-title">
|
||||
<li class="layui-this">绑定已有帐号</li>
|
||||
<li>注册并绑定帐号</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
{{ partial('connect/bind_login') }}
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
{{ partial('connect/bind_register') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block include_js %}
|
||||
|
||||
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
|
||||
{{ js_include('home/js/captcha.verify.js') }}
|
||||
|
||||
{% endblock %}
|
21
app/Http/Home/Views/connect/bind_login.volt
Normal file
21
app/Http/Home/Views/connect/bind_login.volt
Normal file
@ -0,0 +1,21 @@
|
||||
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_login'}) }}">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button id="submit-btn" class="layui-btn layui-btn-fluid" lay-submit="true" lay-filter="go">登录并绑定已有帐号</button>
|
||||
<input type="hidden" name="provider" value="{{ provider }}">
|
||||
<input type="hidden" name="code" value="{{ request.get('code') }}">
|
||||
<input type="hidden" name="state" value="{{ request.get('state') }}">
|
||||
<input type="hidden" name="open_user" value='{{ open_user|json_encode }}'>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
32
app/Http/Home/Views/connect/bind_register.volt
Normal file
32
app/Http/Home/Views/connect/bind_register.volt
Normal file
@ -0,0 +1,32 @@
|
||||
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_register'}) }}">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<input id="cv-account" class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码(字母数字特殊字符6-16位)" lay-verify="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-inline verify-input-inline">
|
||||
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
|
||||
</div>
|
||||
<div class="layui-input-inline verify-btn-inline">
|
||||
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册并绑定帐号</button>
|
||||
<input type="hidden" name="provider" value="{{ provider }}">
|
||||
<input type="hidden" name="code" value="{{ request.get('code') }}">
|
||||
<input type="hidden" name="state" value="{{ request.get('state') }}">
|
||||
<input type="hidden" name="open_user" value='{{ open_user|json_encode }}'>
|
||||
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
|
||||
<input id="cv-ticket" type="hidden" name="ticket">
|
||||
<input id="cv-rand" type="hidden" name="rand">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -17,8 +17,12 @@
|
||||
支付金额:<span class="amount">{{ '¥%0.2f'|format(order.amount) }}</span>
|
||||
</div>
|
||||
<div class="channel">
|
||||
{% if pay_provider.alipay.enabled == 1 %}
|
||||
<a class="alipay btn-pay" href="javascript:" data-channel="alipay">{{ image('home/img/alipay.png') }}</a>
|
||||
{% endif %}
|
||||
{% if pay_provider.wxpay.enabled == 1 %}
|
||||
<a class="wxpay btn-pay" href="javascript:" data-channel="wxpay">{{ image('home/img/wxpay.png') }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span class="tips">友情提示:请在12小时内完成支付,有问题请联系客服</span>
|
||||
|
@ -2,6 +2,23 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{%- macro connect_provider(item) %}
|
||||
{% if item.provider == 1 %}
|
||||
<i class="layui-icon layui-icon-login-qq login-qq"></i>
|
||||
{% elseif item.provider == 2 %}
|
||||
<i class="layui-icon layui-icon-login-wechat login-wechat"></i>
|
||||
{% elseif item.provider == 3 %}
|
||||
<i class="layui-icon layui-icon-login-weibo login-weibo"></i>
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro connect_user(item) %}
|
||||
{% if item.open_avatar %}
|
||||
<span class="open-avatar"><img src="{{ item.open_avatar }}"></span>
|
||||
{% endif %}
|
||||
<span class="open-name">{{ item.open_name }}</span>
|
||||
{%- endmacro %}
|
||||
|
||||
{% set edit_pwd_url = url({'for':'home.uc.account'},{'type':'password'}) %}
|
||||
{% set edit_phone_url = url({'for':'home.uc.account'},{'type':'phone'}) %}
|
||||
{% set edit_email_url = url({'for':'home.uc.account'},{'type':'email'}) %}
|
||||
@ -43,6 +60,45 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-nav">
|
||||
<span class="title">开放登录</span>
|
||||
</div>
|
||||
{% if connects %}
|
||||
<div class="connect-tips">已经绑定的第三方帐号</div>
|
||||
<div class="connect-list">
|
||||
<table class="layui-table">
|
||||
<tr>
|
||||
<td>序号</td>
|
||||
<td>提供方</td>
|
||||
<td>用户信息</td>
|
||||
<td>创建日期</td>
|
||||
<td width="15%">操作</td>
|
||||
</tr>
|
||||
{% for connect in connects %}
|
||||
{% set url = url({'for':'home.uc.unconnect','id':connect.id}) %}
|
||||
<tr>
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ connect_provider(connect) }}</td>
|
||||
<td>{{ connect_user(connect) }}</td>
|
||||
<td>{{ date('Y-m-d H:i',connect.create_time) }}</td>
|
||||
<td><a class="layui-btn layui-btn-danger layui-btn-sm kg-delete" href="javascript:" data-url="{{ url }}" data-tips="确定要解除绑定吗?">解除绑定</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="connect-tips">支持绑定的第三方帐号</div>
|
||||
<div class="oauth-list">
|
||||
{% if oauth_provider.qq.enabled == 1 %}
|
||||
<a class="layui-icon layui-icon-login-qq login-qq" href="{{ url({'for':'home.oauth.qq'}) }}"></a>
|
||||
{% endif %}
|
||||
{% if oauth_provider.qq.enabled == 1 %}
|
||||
<a class="layui-icon layui-icon-login-wechat login-wechat" href="{{ url({'for':'home.oauth.weixin'}) }}"></a>
|
||||
{% endif %}
|
||||
{% if oauth_provider.qq.enabled == 1 %}
|
||||
<a class="layui-icon layui-icon-login-weibo login-weibo" href="{{ url({'for':'home.oauth.weibo'}) }}"></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library;
|
||||
|
||||
use GuzzleHttp\Client as HttpClient;
|
||||
|
||||
abstract class OAuth
|
||||
{
|
||||
|
||||
protected $appId;
|
||||
protected $appSecret;
|
||||
protected $redirectUri;
|
||||
protected $accessToken;
|
||||
protected $openId;
|
||||
|
||||
public function __construct($appId, $appSecret, $redirectUri)
|
||||
{
|
||||
$this->appId = $appId;
|
||||
$this->appSecret = $appSecret;
|
||||
$this->redirectUri = $redirectUri;
|
||||
}
|
||||
|
||||
public function httpGet($uri, $params = [], $headers = [])
|
||||
{
|
||||
$client = new HttpClient();
|
||||
|
||||
$options = ['query' => $params, 'headers' => $headers];
|
||||
|
||||
$response = $client->get($uri, $options);
|
||||
|
||||
return $response->getBody();
|
||||
}
|
||||
|
||||
public function httpPost($uri, $params = [], $headers = [])
|
||||
{
|
||||
$client = new HttpClient();
|
||||
|
||||
$options = ['query' => $params, 'headers' => $headers];
|
||||
|
||||
$response = $client->post($uri, $options);
|
||||
|
||||
return $response->getBody();
|
||||
}
|
||||
|
||||
abstract public function getAuthorizeUrl();
|
||||
|
||||
abstract public function getAccessToken($code);
|
||||
|
||||
abstract public function getOpenId($accessToken);
|
||||
|
||||
abstract public function getUserInfo($accessToken, $openId);
|
||||
|
||||
}
|
104
app/Models/Connect.php
Normal file
104
app/Models/Connect.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||
|
||||
class Connect extends Model
|
||||
{
|
||||
|
||||
const PROVIDER_QQ = 1; // QQ
|
||||
const PROVIDER_WEIXIN = 2; // 微信
|
||||
const PROVIDER_WEIBO = 3; // 微博
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $user_id;
|
||||
|
||||
/**
|
||||
* 开放ID
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $open_id;
|
||||
|
||||
/**
|
||||
* 开放名称
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $open_name;
|
||||
|
||||
/**
|
||||
* 开放头像
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $open_avatar;
|
||||
|
||||
/**
|
||||
* 提供商
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $provider;
|
||||
|
||||
/**
|
||||
* 删除标识
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $deleted;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $create_time;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $update_time;
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_connect';
|
||||
}
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
$this->addBehavior(
|
||||
new SoftDelete([
|
||||
'field' => 'deleted',
|
||||
'value' => 1,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
||||
{
|
||||
$this->create_time = time();
|
||||
}
|
||||
|
||||
public function beforeUpdate()
|
||||
{
|
||||
$this->update_time = time();
|
||||
}
|
||||
|
||||
}
|
62
app/Repos/Connect.php
Normal file
62
app/Repos/Connect.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repos;
|
||||
|
||||
use App\Models\Connect as ConnectModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class Connect extends Repository
|
||||
{
|
||||
|
||||
/**
|
||||
* @param array $where
|
||||
* @return ResultsetInterface|Resultset|ConnectModel[]
|
||||
*/
|
||||
public function findAll($where = [])
|
||||
{
|
||||
$query = ConnectModel::query();
|
||||
|
||||
$query->where('1 = 1');
|
||||
|
||||
if (isset($where['user_id'])) {
|
||||
$query->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
|
||||
}
|
||||
|
||||
if (isset($where['provider'])) {
|
||||
$query->andWhere('provider = :provider:', ['provider' => $where['provider']]);
|
||||
}
|
||||
|
||||
if (isset($where['deleted'])) {
|
||||
$query->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
|
||||
}
|
||||
|
||||
$query->orderBy('id DESC');
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return ConnectModel|Model|bool
|
||||
*/
|
||||
public function findById($id)
|
||||
{
|
||||
return ConnectModel::findFirst($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $openId
|
||||
* @param int $provider
|
||||
* @return ConnectModel|Model|bool
|
||||
*/
|
||||
public function findByOpenId($openId, $provider)
|
||||
{
|
||||
return ConnectModel::findFirst([
|
||||
'conditions' => 'open_id = ?1 and provider = ?2',
|
||||
'bind' => [1 => $openId, 2 => $provider],
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
23
app/Services/Logic/Account/OAuthProvider.php
Normal file
23
app/Services/Logic/Account/OAuthProvider.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Logic\Account;
|
||||
|
||||
use App\Services\Logic\Service;
|
||||
|
||||
class OAuthProvider extends Service
|
||||
{
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$weixin = $this->getSettings('oauth.weixin');
|
||||
$weibo = $this->getSettings('oauth.weibo');
|
||||
$qq = $this->getSettings('oauth.qq');
|
||||
|
||||
return [
|
||||
'weixin' => ['enabled' => $weixin['enabled']],
|
||||
'weibo' => ['enabled' => $weibo['enabled']],
|
||||
'qq' => ['enabled' => $qq['enabled']],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
21
app/Services/Logic/Order/PayProvider.php
Normal file
21
app/Services/Logic/Order/PayProvider.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Logic\Order;
|
||||
|
||||
use App\Services\Logic\Service;
|
||||
|
||||
class PayProvider extends Service
|
||||
{
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$alipay = $this->getSettings('pay.alipay');
|
||||
$wxpay = $this->getSettings('pay.wxpay');
|
||||
|
||||
return [
|
||||
'alipay' => ['enabled' => $alipay['enabled']],
|
||||
'wxpay' => ['enabled' => $wxpay['enabled']],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
26
app/Services/Logic/User/Console/ConnectDelete.php
Normal file
26
app/Services/Logic/User/Console/ConnectDelete.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Logic\User\Console;
|
||||
|
||||
use App\Services\Logic\Service;
|
||||
use App\Validators\Connect as ConnectValidator;
|
||||
|
||||
class ConnectDelete extends Service
|
||||
{
|
||||
|
||||
public function handle($id)
|
||||
{
|
||||
$user = $this->getLoginUser();
|
||||
|
||||
$validator = new ConnectValidator();
|
||||
|
||||
$connect = $validator->checkConnect($id);
|
||||
|
||||
$validator->checkOwner($user->id, $connect->user_id);
|
||||
|
||||
$connect->deleted = 1;
|
||||
|
||||
$connect->update();
|
||||
}
|
||||
|
||||
}
|
45
app/Services/Logic/User/Console/ConnectList.php
Normal file
45
app/Services/Logic/User/Console/ConnectList.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Logic\User\Console;
|
||||
|
||||
use App\Repos\Connect as ConnectRepo;
|
||||
use App\Services\Logic\Service;
|
||||
|
||||
class ConnectList extends Service
|
||||
{
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$user = $this->getLoginUser();
|
||||
|
||||
$params = [
|
||||
'user_id' => $user->id,
|
||||
'deleted' => 0,
|
||||
];
|
||||
|
||||
$connectRepo = new ConnectRepo();
|
||||
|
||||
$connects = $connectRepo->findAll($params);
|
||||
|
||||
if ($connects->count() == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($connects as $connect) {
|
||||
$items[] = [
|
||||
'id' => $connect->id,
|
||||
'open_id' => $connect->open_id,
|
||||
'open_name' => $connect->open_name,
|
||||
'open_avatar' => $connect->open_avatar,
|
||||
'provider' => $connect->provider,
|
||||
'create_time' => $connect->create_time,
|
||||
'update_time' => $connect->update_time,
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
}
|
107
app/Services/OAuth.php
Normal file
107
app/Services/OAuth.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use GuzzleHttp\Client as HttpClient;
|
||||
use Phalcon\Crypt;
|
||||
use Phalcon\Di;
|
||||
|
||||
abstract class OAuth extends Service
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $clientId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $clientSecret;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectUri;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $accessToken;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $openId;
|
||||
|
||||
public function __construct($clientId, $clientSecret, $redirectUri)
|
||||
{
|
||||
$this->clientId = $clientId;
|
||||
$this->clientSecret = $clientSecret;
|
||||
$this->redirectUri = $redirectUri;
|
||||
}
|
||||
|
||||
public function httpGet($uri, $params = [], $headers = [])
|
||||
{
|
||||
$client = new HttpClient();
|
||||
|
||||
$options = ['query' => $params, 'headers' => $headers];
|
||||
|
||||
$response = $client->get($uri, $options);
|
||||
|
||||
return $response->getBody();
|
||||
}
|
||||
|
||||
public function httpPost($uri, $params = [], $headers = [])
|
||||
{
|
||||
$client = new HttpClient();
|
||||
|
||||
$options = ['query' => $params, 'headers' => $headers];
|
||||
|
||||
$response = $client->post($uri, $options);
|
||||
|
||||
return $response->getBody();
|
||||
}
|
||||
|
||||
public function getState()
|
||||
{
|
||||
/**
|
||||
* @var $crypt Crypt
|
||||
*/
|
||||
$crypt = Di::getDefault()->get('crypt');
|
||||
|
||||
return $crypt->encryptBase64(rand(1000, 9999));
|
||||
}
|
||||
|
||||
public function checkState($state)
|
||||
{
|
||||
/**
|
||||
* 注意事项:
|
||||
* callback中的state参数并未做encode处理,参数中含有"+"
|
||||
* 获取参数的时候却自动做了decode处理,"+"变成了空格
|
||||
*/
|
||||
$state = str_replace(' ', '+', $state);
|
||||
|
||||
/**
|
||||
* @var $crypt Crypt
|
||||
*/
|
||||
$crypt = Di::getDefault()->get('crypt');
|
||||
|
||||
$value = $crypt->decryptBase64($state);
|
||||
|
||||
if ($value < 1000 || $value > 9999) {
|
||||
throw new \Exception('Invalid OAuth State Value');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract public function getAuthorizeUrl();
|
||||
|
||||
abstract public function getAccessToken($code);
|
||||
|
||||
abstract public function getOpenId($accessToken);
|
||||
|
||||
abstract public function getUserInfo($accessToken, $openId);
|
||||
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\OAuth;
|
||||
namespace App\Services\OAuth;
|
||||
|
||||
use App\Library\OAuth;
|
||||
use App\Services\OAuth;
|
||||
|
||||
class QQ extends OAuth
|
||||
{
|
||||
@ -15,10 +15,11 @@ class QQ extends OAuth
|
||||
public function getAuthorizeUrl()
|
||||
{
|
||||
$params = [
|
||||
'client_id' => $this->appId,
|
||||
'client_id' => $this->clientId,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'state' => $this->getState(),
|
||||
'response_type' => 'code',
|
||||
'scope' => '',
|
||||
'scope' => 'get_user_info',
|
||||
];
|
||||
|
||||
return self::AUTHORIZE_URL . '?' . http_build_query($params);
|
||||
@ -28,11 +29,10 @@ class QQ extends OAuth
|
||||
{
|
||||
$params = [
|
||||
'code' => $code,
|
||||
'client_id' => $this->appId,
|
||||
'client_secret' => $this->appSecret,
|
||||
'client_id' => $this->clientId,
|
||||
'client_secret' => $this->clientSecret,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'grant_type' => 'authorization_code',
|
||||
'state' => 'ok',
|
||||
];
|
||||
|
||||
$response = $this->httpPost(self::ACCESS_TOKEN_URL, $params);
|
||||
@ -56,14 +56,14 @@ class QQ extends OAuth
|
||||
public function getUserInfo($accessToken, $openId)
|
||||
{
|
||||
$params = [
|
||||
'oauth_consumer_key' => $this->clientId,
|
||||
'access_token' => $accessToken,
|
||||
'openid' => $openId,
|
||||
'oauth_consumer_key' => $this->appId,
|
||||
];
|
||||
|
||||
$response = $this->httpGet(self::USER_INFO_URL, $params);
|
||||
|
||||
$this->parseUserInfo($response);
|
||||
return $this->parseUserInfo($response);
|
||||
}
|
||||
|
||||
protected function parseAccessToken($response)
|
||||
@ -81,15 +81,15 @@ class QQ extends OAuth
|
||||
|
||||
protected function parseOpenId($response)
|
||||
{
|
||||
$result = $match = [];
|
||||
$result = $matches = [];
|
||||
|
||||
if (!empty($response)) {
|
||||
preg_match('/callback\(\s+(.*?)\s+\)/i', $response, $match);
|
||||
$result = json_decode($match[1], true);
|
||||
preg_match('/callback\(\s+(.*?)\s+\)/i', $response, $matches);
|
||||
$result = json_decode($matches[1], true);
|
||||
}
|
||||
|
||||
if (!isset($result['openid'])) {
|
||||
throw new \Exception("Fetch Openid Failed:{$response}");
|
||||
throw new \Exception("Fetch OpenId Failed:{$response}");
|
||||
}
|
||||
|
||||
return $result['openid'];
|
||||
@ -99,14 +99,13 @@ class QQ extends OAuth
|
||||
{
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($data['ret'] != 0) {
|
||||
throw new \Exception("Fetch User Info Failed:{$data['msg']}");
|
||||
if (isset($data['ret']) && $data['ret'] != 0) {
|
||||
throw new \Exception("Fetch User Info Failed:{$response}");
|
||||
}
|
||||
|
||||
$userInfo['type'] = 'QQ';
|
||||
$userInfo['id'] = $this->openId;
|
||||
$userInfo['name'] = $data['nickname'];
|
||||
$userInfo['nick'] = $data['nickname'];
|
||||
$userInfo['head'] = $data['figureurl_2'];
|
||||
$userInfo['avatar'] = $data['figureurl'];
|
||||
|
||||
return $userInfo;
|
||||
}
|
@ -1,21 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\OAuth;
|
||||
namespace App\Services\OAuth;
|
||||
|
||||
use App\Library\OAuth;
|
||||
use App\Services\OAuth;
|
||||
|
||||
class WeiBo extends OAuth
|
||||
{
|
||||
|
||||
const AUTHORIZE_URL = 'https://api.weibo.com/oauth2/authorize';
|
||||
const ACCESS_TOKEN_URL = 'https://api.weibo.com/oauth2/oauth2/access_token';
|
||||
const ACCESS_TOKEN_URL = 'https://api.weibo.com/oauth2/access_token';
|
||||
const USER_INFO_URL = 'https://api.weibo.com/2/users/show.json';
|
||||
|
||||
public function getAuthorizeUrl()
|
||||
{
|
||||
$params = [
|
||||
'client_id' => $this->appId,
|
||||
'client_id' => $this->clientId,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'state' => $this->getState(),
|
||||
'response_type' => 'code',
|
||||
];
|
||||
|
||||
@ -26,8 +27,8 @@ class WeiBo extends OAuth
|
||||
{
|
||||
$params = [
|
||||
'code' => $code,
|
||||
'client_id' => $this->appId,
|
||||
'client_secret' => $this->appSecret,
|
||||
'client_id' => $this->clientId,
|
||||
'client_secret' => $this->clientSecret,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'grant_type' => 'authorization_code',
|
||||
];
|
||||
@ -60,7 +61,7 @@ class WeiBo extends OAuth
|
||||
{
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!isset($data['access_token']) || isset($data['uid'])) {
|
||||
if (!isset($data['access_token']) || !isset($data['uid'])) {
|
||||
throw new \Exception("Fetch Access Token Failed:{$response}");
|
||||
}
|
||||
|
||||
@ -73,14 +74,13 @@ class WeiBo extends OAuth
|
||||
{
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($data['error_code'] != 0) {
|
||||
throw new \Exception("Fetch User Info Failed:{$data['error']}");
|
||||
if (isset($data['error_code']) && $data['error_code'] != 0) {
|
||||
throw new \Exception("Fetch User Info Failed:{$response}");
|
||||
}
|
||||
|
||||
$userInfo['type'] = 'WEIBO';
|
||||
$userInfo['id'] = $data['id'];
|
||||
$userInfo['name'] = $data['name'];
|
||||
$userInfo['nick'] = $data['screen_name'];
|
||||
$userInfo['head'] = $data['avatar_large'];
|
||||
$userInfo['avatar'] = $data['profile_image_url'];
|
||||
|
||||
return $userInfo;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\OAuth;
|
||||
namespace App\Services\OAuth;
|
||||
|
||||
use App\Library\OAuth;
|
||||
use App\Services\OAuth;
|
||||
|
||||
class WeiXin extends OAuth
|
||||
{
|
||||
@ -14,11 +14,11 @@ class WeiXin extends OAuth
|
||||
public function getAuthorizeUrl()
|
||||
{
|
||||
$params = [
|
||||
'appid' => $this->appId,
|
||||
'appid' => $this->clientId,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'state' => $this->getState(),
|
||||
'response_type' => 'code',
|
||||
'scope' => 'snsapi_login',
|
||||
'state' => 'dev',
|
||||
];
|
||||
|
||||
return self::AUTHORIZE_URL . '?' . http_build_query($params);
|
||||
@ -28,8 +28,8 @@ class WeiXin extends OAuth
|
||||
{
|
||||
$params = [
|
||||
'code' => $code,
|
||||
'appid' => $this->appId,
|
||||
'secret' => $this->appSecret,
|
||||
'appid' => $this->clientId,
|
||||
'secret' => $this->clientSecret,
|
||||
'grant_type' => 'authorization_code',
|
||||
];
|
||||
|
||||
@ -62,7 +62,7 @@ class WeiXin extends OAuth
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (isset($data['errcode']) && $data['errcode'] != 0) {
|
||||
throw new \Exception("Fetch Access Token Failed:{$data['errmsg']}");
|
||||
throw new \Exception("Fetch Access Token Failed:{$response}");
|
||||
}
|
||||
|
||||
$this->openId = $data['openid'];
|
||||
@ -75,13 +75,12 @@ class WeiXin extends OAuth
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (isset($data['errcode']) && $data['errcode'] != 0) {
|
||||
throw new \Exception("Fetch User Info Failed:{$data['errmsg']}");
|
||||
throw new \Exception("Fetch User Info Failed:{$response}");
|
||||
}
|
||||
|
||||
$userInfo['type'] = 'WEIXIN';
|
||||
$userInfo['name'] = $data['name'];
|
||||
$userInfo['nick'] = $data['screen_name'];
|
||||
$userInfo['head'] = $data['avatar_large'];
|
||||
$userInfo['id'] = $data['openid'];
|
||||
$userInfo['name'] = $data['nickname'];
|
||||
$userInfo['avatar'] = $data['headimgurl'];
|
||||
|
||||
return $userInfo;
|
||||
}
|
41
app/Validators/Connect.php
Normal file
41
app/Validators/Connect.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validators;
|
||||
|
||||
use App\Exceptions\BadRequest as BadRequestException;
|
||||
use App\Repos\Connect as ConnectRepo;
|
||||
|
||||
class Connect extends Validator
|
||||
{
|
||||
|
||||
public function checkConnect($id)
|
||||
{
|
||||
return $this->checkConnectById($id);
|
||||
}
|
||||
|
||||
public function checkConnectById($id)
|
||||
{
|
||||
$connectRepo = new ConnectRepo();
|
||||
|
||||
$connect = $connectRepo->findById($id);
|
||||
|
||||
if (!$connect) {
|
||||
throw new BadRequestException('connect.not_found');
|
||||
}
|
||||
|
||||
return $connect;
|
||||
}
|
||||
|
||||
public function checkConnectByOpenId($openId, $provider)
|
||||
{
|
||||
$connectRepo = new ConnectRepo();
|
||||
|
||||
$connect = $connectRepo->findByOpenId($openId, $provider);
|
||||
|
||||
if (!$connect) {
|
||||
throw new BadRequestException('connect.not_found');
|
||||
}
|
||||
|
||||
return $connect;
|
||||
}
|
||||
}
|
92
db/migrations/20201205091213_create_connect_table.php
Normal file
92
db/migrations/20201205091213_create_connect_table.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
use Phinx\Db\Adapter\MysqlAdapter;
|
||||
|
||||
class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$this->table('kg_connect', [
|
||||
'id' => false,
|
||||
'primary_key' => ['id'],
|
||||
'engine' => 'InnoDB',
|
||||
'encoding' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_general_ci',
|
||||
'comment' => '',
|
||||
'row_format' => 'DYNAMIC',
|
||||
])
|
||||
->addColumn('id', 'integer', [
|
||||
'null' => false,
|
||||
'limit' => MysqlAdapter::INT_REGULAR,
|
||||
'identity' => 'enable',
|
||||
'comment' => '主键编号',
|
||||
])
|
||||
->addColumn('user_id', 'integer', [
|
||||
'null' => false,
|
||||
'default' => '0',
|
||||
'limit' => MysqlAdapter::INT_REGULAR,
|
||||
'comment' => '用户编号',
|
||||
'after' => 'id',
|
||||
])
|
||||
->addColumn('open_id', 'string', [
|
||||
'null' => false,
|
||||
'default' => '',
|
||||
'limit' => 50,
|
||||
'collation' => 'utf8mb4_general_ci',
|
||||
'encoding' => 'utf8mb4',
|
||||
'comment' => '开放ID',
|
||||
'after' => 'user_id',
|
||||
])
|
||||
->addColumn('open_name', 'string', [
|
||||
'null' => false,
|
||||
'default' => '',
|
||||
'limit' => 30,
|
||||
'collation' => 'utf8mb4_general_ci',
|
||||
'encoding' => 'utf8mb4',
|
||||
'comment' => '开放名称',
|
||||
'after' => 'open_id',
|
||||
])
|
||||
->addColumn('open_avatar', 'string', [
|
||||
'null' => false,
|
||||
'default' => '',
|
||||
'limit' => 150,
|
||||
'collation' => 'utf8mb4_general_ci',
|
||||
'encoding' => 'utf8mb4',
|
||||
'comment' => '开放头像',
|
||||
'after' => 'open_name',
|
||||
])
|
||||
->addColumn('provider', 'integer', [
|
||||
'null' => false,
|
||||
'default' => '0',
|
||||
'limit' => MysqlAdapter::INT_REGULAR,
|
||||
'comment' => '提供方',
|
||||
'after' => 'open_avatar',
|
||||
])
|
||||
->addColumn('deleted', 'integer', [
|
||||
'null' => false,
|
||||
'default' => '0',
|
||||
'limit' => MysqlAdapter::INT_REGULAR,
|
||||
'comment' => '删除标识',
|
||||
'after' => 'provider',
|
||||
])
|
||||
->addColumn('create_time', 'integer', [
|
||||
'null' => false,
|
||||
'default' => '0',
|
||||
'limit' => MysqlAdapter::INT_REGULAR,
|
||||
'comment' => '创建时间',
|
||||
'after' => 'deleted',
|
||||
])
|
||||
->addColumn('update_time', 'integer', [
|
||||
'null' => false,
|
||||
'default' => '0',
|
||||
'limit' => MysqlAdapter::INT_REGULAR,
|
||||
'comment' => '更新时间',
|
||||
'after' => 'create_time',
|
||||
])
|
||||
->addIndex(['open_id', 'provider'], [
|
||||
'name' => 'openid_provider',
|
||||
'unique' => false,
|
||||
])
|
||||
->create();
|
||||
}
|
||||
}
|
85
db/migrations/20201205112717_insert_oauth_setting_data.php
Normal file
85
db/migrations/20201205112717_insert_oauth_setting_data.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class InsertOauthSettingData extends AbstractMigration
|
||||
{
|
||||
|
||||
public function up()
|
||||
{
|
||||
$rows = [
|
||||
[
|
||||
'section' => 'oauth.qq',
|
||||
'item_key' => 'enabled',
|
||||
'item_value' => '0',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.qq',
|
||||
'item_key' => 'client_id',
|
||||
'item_value' => '',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.qq',
|
||||
'item_key' => 'client_secret',
|
||||
'item_value' => '',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.qq',
|
||||
'item_key' => 'redirect_uri',
|
||||
'item_value' => '',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.weixin',
|
||||
'item_key' => 'enabled',
|
||||
'item_value' => '0',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.weixin',
|
||||
'item_key' => 'client_id',
|
||||
'item_value' => '',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.weixin',
|
||||
'item_key' => 'client_secret',
|
||||
'item_value' => '',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.weixin',
|
||||
'item_key' => 'redirect_uri',
|
||||
'item_value' => '',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.weibo',
|
||||
'item_key' => 'enabled',
|
||||
'item_value' => '0',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.weibo',
|
||||
'item_key' => 'client_id',
|
||||
'item_value' => '',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.weibo',
|
||||
'item_key' => 'client_secret',
|
||||
'item_value' => '',
|
||||
],
|
||||
[
|
||||
'section' => 'oauth.weibo',
|
||||
'item_key' => 'redirect_uri',
|
||||
'item_value' => '',
|
||||
],
|
||||
];
|
||||
|
||||
$this->table('kg_setting')->insert($rows)->save();
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.qq'");
|
||||
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.weixin'");
|
||||
$this->execute("DELETE FROM kg_setting WHERE section = 'oauth.weibo'");
|
||||
}
|
||||
|
||||
}
|
@ -1188,7 +1188,7 @@
|
||||
}
|
||||
|
||||
.login-wrap .link {
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
@ -1202,6 +1202,26 @@
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.login-wrap .oauth {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-wrap .oauth a {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.login-qq {
|
||||
color: dodgerblue;
|
||||
}
|
||||
|
||||
.login-wechat {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.login-weibo {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
position: relative;
|
||||
}
|
||||
@ -1537,9 +1557,12 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.security-item-list {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.security-item {
|
||||
padding: 15px;
|
||||
line-height: 50px;
|
||||
line-height: 80px;
|
||||
border-bottom: 1px dashed #ccc;
|
||||
}
|
||||
|
||||
@ -1563,6 +1586,29 @@
|
||||
float: right;
|
||||
}
|
||||
|
||||
.connect-list {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.open-avatar img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.connect-tips {
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.oauth-list {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.oauth-list a {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.order-filter {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user