1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-28 21:31:37 +08:00

Merge branch 'koogua/v1.6.5' into demo

# Conflicts:
#	app/Library/AppInfo.php
This commit is contained in:
xiaochong0302 2023-06-25 19:05:23 +08:00
commit cd05619876
47 changed files with 482 additions and 684 deletions

View File

@ -1,4 +1,10 @@
### [v1.6.4](https://gitee.com/koogua/course-tencent-cloud/releases/v1.6.3)(2023-06-15) ### [v1.6.5](https://gitee.com/koogua/course-tencent-cloud/releases/v1.6.5)(2023-07-15)
- 升级layui-v2.8.8
- 使用本地图像验证码
- 清理无用的计划任务
### [v1.6.4](https://gitee.com/koogua/course-tencent-cloud/releases/v1.6.4)(2023-06-15)
- 增加推荐课程等Widget - 增加推荐课程等Widget
- 更新Composer包 - 更新Composer包

View File

@ -55,7 +55,7 @@ Tips: 请用手机注册一个新账号,用户中心 -> 关注订阅,扫码
### 项目组件 ### 项目组件
- 后台框架:[phalcon 3.4.5](https://phalcon.io) - 后台框架:[phalcon 3.4.5](https://phalcon.io)
- 前端框架:[layui 2.8.2](https://layui.com) - 前端框架:[layui 2.8.8](https://layui.com)
- 全文检索:[xunsearch 1.4.9](http://www.xunsearch.com) - 全文检索:[xunsearch 1.4.9](http://www.xunsearch.com)
- 即时通讯:[workerman 3.5.22](https://workerman.net) - 即时通讯:[workerman 3.5.22](https://workerman.net)
- 基础依赖:[php7.3](https://php.net) [mysql5.7](https://mysql.com) [redis5.0](https://redis.io) - 基础依赖:[php7.3](https://php.net) [mysql5.7](https://mysql.com) [redis5.0](https://redis.io)

View File

@ -223,36 +223,6 @@ class SettingController extends Controller
} }
} }
/**
* @Route("/captcha", name="admin.setting.captcha")
*/
public function captchaAction()
{
$section = 'captcha';
$settingService = new SettingService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$settingService->updateSettings($section, $data);
$content = [
'location' => $this->request->getHTTPReferer(),
'msg' => '更新配置成功',
];
return $this->jsonSuccess($content);
} else {
$captcha = $settingService->getSettings($section);
$this->view->setVar('captcha', $captcha);
}
}
/** /**
* @Route("/point", name="admin.setting.point") * @Route("/point", name="admin.setting.point")
*/ */

View File

@ -1200,12 +1200,6 @@ class AuthNode extends Service
'type' => 'menu', 'type' => 'menu',
'route' => 'admin.setting.sms', 'route' => 'admin.setting.sms',
], ],
[
'id' => '5-2-3',
'title' => '验证码设置',
'type' => 'menu',
'route' => 'admin.setting.captcha',
],
[ [
'id' => '5-2-4', 'id' => '5-2-4',
'title' => '存储设置', 'title' => '存储设置',

View File

@ -1,124 +0,0 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set captcha_display = captcha.enabled == 1 ? 'display:block' : 'display:none' %}
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.captcha'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>验证码配置</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">开启服务</label>
<div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" lay-filter="captcha_enabled" {% if captcha.enabled == 1 %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" lay-filter="captcha_enabled" {% if captcha.enabled == 0 %}checked="checked"{% endif %}>
</div>
</div>
<div id="captcha-block" style="{{ captcha_display }}">
<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="app_id" value="{{ captcha.app_id }}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Secret Key</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="secret_key" value="{{ captcha.secret_key }}">
</div>
</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>
</div>
</div>
</form>
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.test.captcha'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>验证码测试</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label"><i class="layui-icon layui-icon-vercode"></i></label>
<div class="layui-input-inline" style="width:200px;">
<button type="button" id="front-captcha-btn" class="layui-btn layui-btn-primary layui-btn-fluid" data-app-id="{{ captcha.app_id }}">前台验证</button>
<button type="button" id="front-verify-tips" class="kg-verify-btn layui-btn layui-btn-primary layui-btn-fluid layui-btn-disabled layui-hide"><i class="layui-icon layui-icon-ok"></i>前台验证成功</button>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i class="layui-icon layui-icon-vercode"></i></label>
<div class="layui-input-inline" style="width:200px;">
<button type="button" id="back-verify-btn" class="layui-btn layui-btn-primary layui-btn-fluid" disabled="disabled" lay-submit="true" lay-filter="back_verify">后台验证</button>
<button type="button" id="back-verify-tips" class="kg-verify-btn layui-btn layui-btn-primary layui-btn-fluid layui-btn-disabled layui-hide"><i class="layui-icon layui-icon-ok"></i>后台验证成功</button>
<input type="hidden" name="ticket">
<input type="hidden" name="rand">
</div>
</div>
</form>
{% endblock %}
{% block inline_js %}
<script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>
<script>
layui.use(['jquery', 'form', 'layer'], function () {
var $ = layui.jquery;
var form = layui.form;
var layer = layui.layer;
var captcha = new TencentCaptcha(
$('#front-captcha-btn')[0],
$('#front-captcha-btn').data('app-id'),
function (res) {
if (res.ret === 0) {
$('input[name=ticket]').val(res.ticket);
$('input[name=rand]').val(res.randstr);
$('#front-captcha-btn').remove();
$('#back-verify-btn').removeAttr('disabled');
$('#front-verify-tips').removeClass('layui-hide');
}
}
);
form.on('radio(captcha_enabled)', function (data) {
var block = $('#captcha-block');
if (data.value === '1') {
block.show();
} else {
block.hide();
}
});
form.on('submit(back_verify)', function (data) {
$.ajax({
type: 'POST',
url: data.form.action,
data: data.field,
success: function (res) {
if (res.code === 0) {
$('#back-verify-btn').remove();
$('#back-verify-tips').removeClass('layui-hide');
}
layer.msg(res.msg, {icon: 1});
},
error: function (xhr) {
var json = JSON.parse(xhr.responseText);
layer.msg(json.msg, {icon: 2});
}
});
return false;
});
});
</script>
{% endblock %}

View File

@ -16,7 +16,7 @@
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">Webhook</label> <label class="layui-form-label">Webhook</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input class="layui-input" type="text" name="app_token" value="{{ robot.webhook_url }}" lay-verify="required"> <input class="layui-input" type="text" name="webhook_url" value="{{ robot.webhook_url }}" lay-verify="required">
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">

View File

@ -7,9 +7,10 @@
namespace App\Http\Api\Controllers; namespace App\Http\Api\Controllers;
use App\Services\Logic\Verify\MailCode as MailCodeService; use App\Services\Logic\Verify\Captcha as VerifyCaptchaService;
use App\Services\Logic\Verify\SmsCode as SmsCodeService; use App\Services\Logic\Verify\Code as VerifyCodeService;
use App\Services\Logic\Verify\Ticket as TicketService; use App\Services\Logic\Verify\MailCode as VerifyMailCodeService;
use App\Services\Logic\Verify\SmsCode as VerifySmsCodeService;
/** /**
* @RoutePrefix("/api/verify") * @RoutePrefix("/api/verify")
@ -18,15 +19,27 @@ class VerifyController extends Controller
{ {
/** /**
* @Post("/ticket", name="api.verify.ticket") * @Get("/captcha", name="api.verify.captcha")
*/ */
public function ticketAction() public function captchaAction()
{ {
$service = new TicketService(); $service = new VerifyCaptchaService();
$ticket = $service->handle(); $captcha = $service->handle();
return $this->jsonSuccess(['ticket' => $ticket]); return $this->jsonSuccess(['captcha' => $captcha]);
}
/**
* @Post("/code", name="api.verify.code")
*/
public function codeAction()
{
$service = new VerifyCodeService();
$service->handle();
return $this->jsonSuccess();
} }
/** /**
@ -34,7 +47,7 @@ class VerifyController extends Controller
*/ */
public function smsCodeAction() public function smsCodeAction()
{ {
$service = new SmsCodeService(); $service = new VerifySmsCodeService();
$service->handle(); $service->handle();
@ -46,7 +59,7 @@ class VerifyController extends Controller
*/ */
public function mailCodeAction() public function mailCodeAction()
{ {
$service = new MailCodeService(); $service = new VerifyMailCodeService();
$service->handle(); $service->handle();

View File

@ -15,6 +15,9 @@ use App\Services\Logic\Account\PasswordReset as PasswordResetService;
use App\Services\Logic\Account\PasswordUpdate as PasswordUpdateService; use App\Services\Logic\Account\PasswordUpdate as PasswordUpdateService;
use App\Services\Logic\Account\PhoneUpdate as PhoneUpdateService; use App\Services\Logic\Account\PhoneUpdate as PhoneUpdateService;
/**
* @RoutePrefix("/account")
*/
class AccountController extends Controller class AccountController extends Controller
{ {
@ -40,15 +43,10 @@ class AccountController extends Controller
$oauthProvider = $service->handle(); $oauthProvider = $service->handle();
$service = new AccountService();
$captcha = $service->getSettings('captcha');
$this->seo->prependTitle('用户注册'); $this->seo->prependTitle('用户注册');
$this->view->setVar('return_url', $returnUrl); $this->view->setVar('return_url', $returnUrl);
$this->view->setVar('local_oauth', $oauthProvider['local']); $this->view->setVar('local_oauth', $oauthProvider['local']);
$this->view->setVar('captcha', $captcha);
} }
/** /**
@ -67,10 +65,6 @@ class AccountController extends Controller
return $this->response->redirect('/'); return $this->response->redirect('/');
} }
$service = new AccountService();
$captcha = $service->getSettings('captcha');
$service = new OAuthProviderService(); $service = new OAuthProviderService();
$oauthProvider = $service->handle(); $oauthProvider = $service->handle();
@ -81,7 +75,6 @@ class AccountController extends Controller
$this->view->setVar('oauth_provider', $oauthProvider); $this->view->setVar('oauth_provider', $oauthProvider);
$this->view->setVar('return_url', $returnUrl); $this->view->setVar('return_url', $returnUrl);
$this->view->setVar('captcha', $captcha);
} }
/** /**
@ -112,13 +105,7 @@ class AccountController extends Controller
return $this->response->redirect(['for' => 'home.index']); return $this->response->redirect(['for' => 'home.index']);
} }
$service = new AccountService();
$captcha = $service->getSettings('captcha');
$this->seo->prependTitle('重置密码'); $this->seo->prependTitle('重置密码');
$this->view->setVar('captcha', $captcha);
} }
/** /**

View File

@ -7,40 +7,22 @@
namespace App\Http\Home\Controllers; namespace App\Http\Home\Controllers;
use App\Services\Logic\Verify\MailCode as MailCodeService;
use App\Services\Logic\Verify\SmsCode as SmsCodeService;
use App\Traits\Response as ResponseTrait; use App\Traits\Response as ResponseTrait;
/** /**
* @RoutePrefix("/verify") * @RoutePrefix("/verify")
*/ */
class VerifyController extends \Phalcon\Mvc\Controller class VerifyController extends Controller
{ {
use ResponseTrait; use ResponseTrait;
/** /**
* @Post("/sms/code", name="home.verify.sms_code") * @Get("/captcha", name="home.verify.captcha")
*/ */
public function smsCodeAction() public function captchaAction()
{ {
$service = new SmsCodeService(); $this->view->pick('verify/captcha');
$service->handle();
return $this->jsonSuccess();
}
/**
* @Post("/mail/code", name="home.verify.mail_code")
*/
public function mailCodeAction()
{
$service = new MailCodeService();
$service->handle();
return $this->jsonSuccess();
} }
} }

View File

@ -62,18 +62,6 @@ class Account extends Service
$validator->checkIfAllowLogin($user); $validator->checkIfAllowLogin($user);
$captcha = $this->getSettings('captcha');
/**
* 验证码是一次性的,放到最后检查,减少第三方调用
*/
if ($captcha['enabled'] == 1) {
$validator = new CaptchaValidator();
$validator->checkCode($post['captcha']['ticket'], $post['captcha']['rand']);
}
$this->auth->saveAuthInfo($user); $this->auth->saveAuthInfo($user);
$this->eventsManager->fire('Account:afterLogin', $this, $user); $this->eventsManager->fire('Account:afterLogin', $this, $user);

View File

@ -35,7 +35,6 @@
{% block include_js %} {% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.phone.js') }} {{ js_include('home/js/captcha.verify.phone.js') }}
{{ js_include('home/js/captcha.verify.email.js') }} {{ js_include('home/js/captcha.verify.email.js') }}

View File

@ -5,7 +5,7 @@
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-icon layui-icon-password"></label> <label class="layui-icon layui-icon-password"></label>
<input class="layui-input" type="password" name="new_password" autocomplete="off" placeholder="新密码字母数字特殊字符6-16位" lay-verify="required"> <input class="layui-input" type="password" name="new_password" autocomplete="off" placeholder="新密码" lay-verify="required">
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-inline verify-input-inline"> <div class="layui-input-inline verify-input-inline">
@ -19,8 +19,6 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<button id="cv-email-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">重置密码</button> <button id="cv-email-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">重置密码</button>
<input id="cv-email-captcha-enabled" type="hidden" value="{{ captcha.enabled }}">
<input id="cv-email-captcha-appId" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-email-captcha-ticket" type="hidden" name="captcha[ticket]"> <input id="cv-email-captcha-ticket" type="hidden" name="captcha[ticket]">
<input id="cv-email-captcha-rand" type="hidden" name="captcha[rand]"> <input id="cv-email-captcha-rand" type="hidden" name="captcha[rand]">
</div> </div>

View File

@ -5,7 +5,7 @@
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-icon layui-icon-password"></label> <label class="layui-icon layui-icon-password"></label>
<input class="layui-input" type="password" name="new_password" autocomplete="off" placeholder="新密码字母数字特殊字符6-16位" lay-verify="required"> <input class="layui-input" type="password" name="new_password" autocomplete="off" placeholder="新密码" lay-verify="required">
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-inline verify-input-inline"> <div class="layui-input-inline verify-input-inline">
@ -19,8 +19,6 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<button id="cv-phone-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">重置密码</button> <button id="cv-phone-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">重置密码</button>
<input id="cv-phone-captcha-enabled" type="hidden" value="{{ captcha.enabled }}">
<input id="cv-phone-captcha-appId" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-phone-captcha-ticket" type="hidden" name="captcha[ticket]"> <input id="cv-phone-captcha-ticket" type="hidden" name="captcha[ticket]">
<input id="cv-phone-captcha-rand" type="hidden" name="captcha[rand]"> <input id="cv-phone-captcha-rand" type="hidden" name="captcha[rand]">
</div> </div>

View File

@ -44,8 +44,6 @@
{% block include_js %} {% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.login.js') }}
{{ js_include('home/js/captcha.verify.js') }} {{ js_include('home/js/captcha.verify.js') }}
{% endblock %} {% endblock %}

View File

@ -1,6 +1,3 @@
{% set disabled_submit = captcha.enabled == 1 ? 'disabled="disabled"' : '' %}
{% set disabled_class = captcha.enabled == 1 ? 'layui-btn-disabled' : '' %}
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.account.pwd_login'}) }}"> <form class="layui-form account-form" method="POST" action="{{ url({'for':'home.account.pwd_login'}) }}">
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-icon layui-icon-username"></label> <label class="layui-icon layui-icon-username"></label>
@ -10,21 +7,10 @@
<label class="layui-icon layui-icon-password"></label> <label class="layui-icon layui-icon-password"></label>
<input class="layui-input" type="password" name="password" value="123456" autocomplete="off" placeholder="密码" lay-verify="required"> <input class="layui-input" type="password" name="password" value="123456" autocomplete="off" placeholder="密码" lay-verify="required">
</div> </div>
{% if captcha.enabled == 1 %}
<div id="captcha-block" class="layui-form-item">
<div class="layui-input-block">
<button id="cl-emit-btn" class="layui-btn layui-btn-fluid" type="button">点击完成验证</button>
</div>
</div>
{% endif %}
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<button id="cl-submit-btn" class="layui-btn layui-btn-fluid {{ disabled_class }}" {{ disabled_submit }} lay-submit="true" lay-filter="go">立即登录</button> <button id="cl-submit-btn" class="layui-btn layui-btn-fluid" lay-submit="true" lay-filter="go">立即登录</button>
<input type="hidden" name="return_url" value="{{ return_url }}"> <input type="hidden" name="return_url" value="{{ return_url }}">
<input id="cl-captcha-enabled" type="hidden" value="{{ captcha.enabled }}">
<input id="cl-captcha-appId" type="hidden" value="{{ captcha.app_id }}">
<input id="cl-captcha-ticket" type="hidden" name="captcha[ticket]">
<input id="cl-captcha-rand" type="hidden" name="captcha[rand]">
</div> </div>
</div> </div>
</form> </form>

View File

@ -15,11 +15,9 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <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> <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="return_url" value="{{ return_url }}">
<input id="cv-captcha-enabled" type="hidden" value="{{ captcha.enabled }}">
<input id="cv-captcha-appId" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-captcha-ticket" type="hidden" name="captcha[ticket]"> <input id="cv-captcha-ticket" type="hidden" name="captcha[ticket]">
<input id="cv-captcha-rand" type="hidden" name="captcha[rand]"> <input id="cv-captcha-rand" type="hidden" name="captcha[rand]">
<input type="hidden" name="return_url" value="{{ return_url }}">
</div> </div>
</div> </div>
</form> </form>

View File

@ -39,7 +39,6 @@
{% block include_js %} {% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/account.register.js') }} {{ js_include('home/js/account.register.js') }}
{{ js_include('home/js/captcha.verify.phone.js') }} {{ js_include('home/js/captcha.verify.phone.js') }}
{{ js_include('home/js/captcha.verify.email.js') }} {{ js_include('home/js/captcha.verify.email.js') }}

View File

@ -2,11 +2,11 @@
<form class="layui-form account-form" method="POST" action="{{ action_url }}"> <form class="layui-form account-form" method="POST" action="{{ action_url }}">
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-icon layui-icon-email"></label> <label class="layui-icon layui-icon-email"></label>
<input id="cv-email" class="layui-input" type="text" name="email" autocomplete="off" placeholder="邮箱" lay-verify="email"> <input id="cv-email" class="layui-input" type="text" name="email" autocomplete="off" placeholder="邮箱" lay-verify="required|email">
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-icon layui-icon-password"></label> <label class="layui-icon layui-icon-password"></label>
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码字母数字特殊字符6-16位" lay-verify="required"> <input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-inline verify-input-inline"> <div class="layui-input-inline verify-input-inline">
@ -20,7 +20,7 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<div class="agree"> <div class="agree">
<div class="left"><input id="cv-email-agree" type="checkbox" name="agree" lay-skin="primary"></div> <div class="left"><input id="cv-email-agree" type="checkbox" name="agree" checked="checked" lay-skin="primary"></div>
<div class="right">我已阅读并同意<a href="{{ terms_url }}" target="_blank">《用户协议》</a>和<a href="{{ privacy_url }}" target="_blank">《隐私政策》</a></div> <div class="right">我已阅读并同意<a href="{{ terms_url }}" target="_blank">《用户协议》</a>和<a href="{{ privacy_url }}" target="_blank">《隐私政策》</a></div>
</div> </div>
</div> </div>
@ -28,11 +28,9 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<button id="cv-email-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册帐号</button> <button id="cv-email-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册帐号</button>
<input type="hidden" name="return_url" value="{{ return_url }}">
<input id="cv-email-captcha-enabled" type="hidden" value="{{ captcha.enabled }}">
<input id="cv-email-captcha-appId" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-email-captcha-ticket" type="hidden" name="captcha[ticket]"> <input id="cv-email-captcha-ticket" type="hidden" name="captcha[ticket]">
<input id="cv-email-captcha-rand" type="hidden" name="captcha[rand]"> <input id="cv-email-captcha-rand" type="hidden" name="captcha[rand]">
<input type="hidden" name="return_url" value="{{ return_url }}">
</div> </div>
</div> </div>
</form> </form>

View File

@ -2,11 +2,11 @@
<form class="layui-form account-form" method="POST" action="{{ action_url }}"> <form class="layui-form account-form" method="POST" action="{{ action_url }}">
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-icon layui-icon-cellphone"></label> <label class="layui-icon layui-icon-cellphone"></label>
<input id="cv-phone" class="layui-input" type="text" name="phone" autocomplete="off" placeholder="手机" lay-verify="phone"> <input id="cv-phone" class="layui-input" type="text" name="phone" autocomplete="off" placeholder="手机" lay-verify="required|phone">
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-icon layui-icon-password"></label> <label class="layui-icon layui-icon-password"></label>
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码字母数字特殊字符6-16位" lay-verify="required"> <input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-inline verify-input-inline"> <div class="layui-input-inline verify-input-inline">
@ -20,7 +20,7 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<div class="agree"> <div class="agree">
<div class="left"><input id="cv-phone-agree" type="checkbox" name="agree" lay-skin="primary"></div> <div class="left"><input id="cv-phone-agree" type="checkbox" name="agree" checked="checked" lay-skin="primary"></div>
<div class="right">我已阅读并同意<a href="{{ terms_url }}" target="_blank">《用户协议》</a>和<a href="{{ privacy_url }}" target="_blank">《隐私政策》</a></div> <div class="right">我已阅读并同意<a href="{{ terms_url }}" target="_blank">《用户协议》</a>和<a href="{{ privacy_url }}" target="_blank">《隐私政策》</a></div>
</div> </div>
</div> </div>
@ -28,11 +28,9 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<button id="cv-phone-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册帐号</button> <button id="cv-phone-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册帐号</button>
<input type="hidden" name="return_url" value="{{ return_url }}">
<input id="cv-phone-captcha-enabled" type="hidden" value="{{ captcha.enabled }}">
<input id="cv-phone-captcha-appId" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-phone-captcha-ticket" type="hidden" name="captcha[ticket]"> <input id="cv-phone-captcha-ticket" type="hidden" name="captcha[ticket]">
<input id="cv-phone-captcha-rand" type="hidden" name="captcha[rand]"> <input id="cv-phone-captcha-rand" type="hidden" name="captcha[rand]">
<input type="hidden" name="return_url" value="{{ return_url }}">
</div> </div>
</div> </div>
</form> </form>

View File

@ -2,7 +2,7 @@
{% block content %} {% block content %}
{% set share_url = full_url('chapter',chapter.id,auth_user.id) %} {% set share_url = share_url('chapter',chapter.id,auth_user.id) %}
{% set qrcode_url = url({'for':'home.qrcode'},{'text':share_url}) %} {% set qrcode_url = url({'for':'home.qrcode'},{'text':share_url}) %}
{% set course_url = url({'for':'home.course.show','id':chapter.course.id}) %} {% set course_url = url({'for':'home.course.show','id':chapter.course.id}) %}
{% set learning_url = url({'for':'home.chapter.learning','id':chapter.id}) %} {% set learning_url = url({'for':'home.chapter.learning','id':chapter.id}) %}

View File

@ -31,7 +31,6 @@
{% block include_js %} {% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/connect.bind.js') }} {{ js_include('home/js/connect.bind.js') }}
{{ js_include('home/js/captcha.verify.js') }} {{ js_include('home/js/captcha.verify.js') }}

View File

@ -1,18 +1,16 @@
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_login'}) }}"> <form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_login'}) }}">
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <label class="layui-icon layui-icon-username"></label>
<input class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required"> <input class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
</div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <label class="layui-icon layui-icon-password"></label>
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required"> <input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">
</div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<div class="agree"> <div class="agree">
<div class="left"><input id="login-agree" type="checkbox" name="agree" lay-skin="primary"></div> <div class="left"><input id="login-agree" type="checkbox" name="agree" checked="checked" lay-skin="primary"></div>
<div class="right">我已阅读并同意<a href="{{ terms_url }}" target="_blank">《用户协议》</a>和<a href="{{ privacy_url }}" target="_blank">《隐私政策》</a></div> <div class="right">我已阅读并同意<a href="{{ terms_url }}" target="_blank">《用户协议》</a>和<a href="{{ privacy_url }}" target="_blank">《隐私政策》</a></div>
</div> </div>
</div> </div>

View File

@ -1,16 +1,15 @@
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_register'}) }}"> <form class="layui-form account-form" method="POST" action="{{ url({'for':'home.oauth.bind_register'}) }}">
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <label class="layui-icon layui-icon-username"></label>
<input id="cv-account" class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required"> <input id="cv-account" class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
</div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <label class="layui-icon layui-icon-password"></label>
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码字母数字特殊字符6-16位" lay-verify="required"> <input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">
</div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-inline verify-input-inline"> <div class="layui-input-inline verify-input-inline">
<label class="layui-icon layui-icon-vercode"></label>
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required"> <input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
</div> </div>
<div class="layui-input-inline verify-btn-inline"> <div class="layui-input-inline verify-btn-inline">
@ -20,7 +19,7 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<div class="agree"> <div class="agree">
<div class="left"><input id="register-agree" type="checkbox" name="agree" lay-skin="primary"></div> <div class="left"><input id="register-agree" type="checkbox" name="agree" checked="checked" lay-skin="primary"></div>
<div class="right">我已阅读并同意<a href="{{ terms_url }}" target="_blank">《用户协议》</a>和<a href="{{ privacy_url }}" target="_blank">《隐私政策》</a></div> <div class="right">我已阅读并同意<a href="{{ terms_url }}" target="_blank">《用户协议》</a>和<a href="{{ privacy_url }}" target="_blank">《隐私政策》</a></div>
</div> </div>
</div> </div>
@ -28,14 +27,12 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <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> <button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册并绑定帐号</button>
<input id="cv-captcha-ticket" type="hidden" name="ticket">
<input id="cv-captcha-rand" type="hidden" name="rand">
<input type="hidden" name="provider" value="{{ provider }}"> <input type="hidden" name="provider" value="{{ provider }}">
<input type="hidden" name="code" value="{{ request.get('code') }}"> <input type="hidden" name="code" value="{{ request.get('code') }}">
<input type="hidden" name="state" value="{{ request.get('state') }}"> <input type="hidden" name="state" value="{{ request.get('state') }}">
<input type="hidden" name="open_user" value='{{ open_user|json_encode }}'> <input type="hidden" name="open_user" value='{{ open_user|json_encode }}'>
<input id="cv-enabled" type="hidden" value="{{ captcha.enabled }}">
<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>
</div> </div>
</form> </form>

View File

@ -10,16 +10,18 @@
<span class="title">账号安全 - 修改邮箱</span> <span class="title">账号安全 - 修改邮箱</span>
</div> </div>
<form class="layui-form security-form" method="POST" action="{{ url({'for':'home.account.update_email'}) }}"> <form class="layui-form security-form" method="POST" action="{{ url({'for':'home.account.update_email'}) }}">
<div class="layui-form-item"> {% if account.password|length > 0 %}
<label class="layui-form-label">登录密码</label> <div class="layui-form-item">
<div class="layui-input-block"> <label class="layui-form-label">登录密码</label>
<input class="layui-input" type="password" name="login_password" autocomplete="off" lay-verify="required"> <div class="layui-input-block">
<input class="layui-input" type="password" name="login_password" autocomplete="off" placeholder="请输入当前登录密码" lay-verify="required">
</div>
</div> </div>
</div> {% endif %}
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">邮箱地址</label> <label class="layui-form-label">邮箱地址</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input id="cv-email" class="layui-input" type="text" name="email" lay-verify="required"> <input id="cv-email" class="layui-input" type="text" name="email" placeholder="请输入新设邮箱地址" lay-verify="required|email">
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
@ -34,8 +36,6 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<button id="cv-email-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">提交修改</button> <button id="cv-email-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">提交修改</button>
<input id="cv-email-captcha-enabled" type="hidden" value="{{ captcha.enabled }}">
<input id="cv-email-captcha-appId" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-email-captcha-ticket" type="hidden" name="captcha[ticket]"> <input id="cv-email-captcha-ticket" type="hidden" name="captcha[ticket]">
<input id="cv-email-captcha-rand" type="hidden" name="captcha[rand]"> <input id="cv-email-captcha-rand" type="hidden" name="captcha[rand]">
</div> </div>
@ -49,7 +49,6 @@
{% block include_js %} {% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.email.js') }} {{ js_include('home/js/captcha.verify.email.js') }}
{% endblock %} {% endblock %}

View File

@ -10,16 +10,18 @@
<span class="title">账号安全 - 修改手机</span> <span class="title">账号安全 - 修改手机</span>
</div> </div>
<form class="layui-form security-form" method="POST" action="{{ url({'for':'home.account.update_phone'}) }}"> <form class="layui-form security-form" method="POST" action="{{ url({'for':'home.account.update_phone'}) }}">
<div class="layui-form-item"> {% if account.password|length > 0 %}
<label class="layui-form-label">登录密码</label> <div class="layui-form-item">
<div class="layui-input-block"> <label class="layui-form-label">登录密码</label>
<input class="layui-input" type="password" name="login_password" autocomplete="off" lay-verify="required"> <div class="layui-input-block">
<input class="layui-input" type="password" name="login_password" autocomplete="off" placeholder="请输入当前登录密码" lay-verify="required">
</div>
</div> </div>
</div> {% endif %}
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">手机号码</label> <label class="layui-form-label">手机号码</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input id="cv-phone" class="layui-input" type="text" name="phone" lay-verify="required"> <input id="cv-phone" class="layui-input" type="text" name="phone" placeholder="请输入新设手机号码" lay-verify="required|phone">
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
@ -34,8 +36,6 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-input-block"> <div class="layui-input-block">
<button id="cv-phone-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">提交修改</button> <button id="cv-phone-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">提交修改</button>
<input id="cv-phone-captcha-enabled" type="hidden" value="{{ captcha.enabled }}">
<input id="cv-phone-captcha-appId" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-phone-captcha-ticket" type="hidden" name="captcha[ticket]"> <input id="cv-phone-captcha-ticket" type="hidden" name="captcha[ticket]">
<input id="cv-phone-captcha-rand" type="hidden" name="captcha[rand]"> <input id="cv-phone-captcha-rand" type="hidden" name="captcha[rand]">
</div> </div>
@ -49,7 +49,6 @@
{% block include_js %} {% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.phone.js') }} {{ js_include('home/js/captcha.verify.phone.js') }}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,105 @@
{% extends 'templates/layer.volt' %}
{% block content %}
<form class="layui-form">
<div class="layui-form-item">
<label class="layui-form-label">验证算式</label>
<div class="layui-input-block">
<img id="img-captcha" class="pointer" title="刷新表达式" alt="验证表达式" width="200" height="50">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">计算结果</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="rand">
</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="captcha">提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
<div class="layui-hide">
<input type="hidden" name="account" value="{{ request.get('account') }}">
<input type="hidden" name="type" value="{{ request.get('type') }}">
<input type="hidden" name="ticket">
</div>
{% endblock %}
{% block include_js %}
<script>
layui.use(['jquery', 'form', 'layer', 'helper'], function () {
var $ = layui.jquery;
var form = layui.form;
var layer = layui.layer;
var index = parent.layer.getFrameIndex(window.name);
var verify = {
account: $('input[name=account]').val(),
type: $('input[name=type]').val(),
}
var showCaptchaImage = function () {
$.get('/api/verify/captcha', function (res) {
$('#img-captcha').attr('src', res.captcha.content);
$('input[name=ticket]').val(res.captcha.ticket);
});
};
$('#img-captcha').on('click', function () {
showCaptchaImage();
});
form.on('submit(captcha)', function (data) {
var submit = $(this);
var account = $('input[name=account]').val();
var ticket = $('input[name=ticket]').val();
var rand = $('input[name=rand]').val();
submit.attr('disabled', 'disabled').addClass('layui-btn-disabled');
if (verify.type === 'phone') {
parent.layui.$('#cv-phone-submit-btn').removeAttr('disabled').removeClass('layui-btn-disabled');
parent.layui.$('#cv-phone-captcha-ticket').val(ticket);
parent.layui.$('#cv-phone-captcha-rand').val(rand);
} else if (verify.type === 'mail') {
parent.layui.$('#cv-mail-submit-btn').removeAttr('disabled').removeClass('layui-btn-disabled');
parent.layui.$('#cv-mail-captcha-ticket').val(ticket);
parent.layui.$('#cv-mail-captcha-rand').val(rand);
} else {
parent.layui.$('#cv-submit-btn').removeAttr('disabled').removeClass('layui-btn-disabled');
parent.layui.$('#cv-captcha-ticket').val(ticket);
parent.layui.$('#cv-captcha-rand').val(rand);
}
$.ajax({
type: 'POST',
url: '/api/verify/code',
data: {
account: account,
ticket: ticket,
rand: rand,
},
success: function () {
layer.msg('发送验证码成功', {icon: 1});
setTimeout(function () {
parent.layer.close(index);
}, 1500);
}, error: function () {
submit.removeAttr('disabled').removeClass('layui-btn-disabled');
}
});
return false;
});
showCaptchaImage();
});
</script>
{% endblock %}

View File

@ -16,7 +16,7 @@ class AppInfo
protected $link = 'https://www.koogua.com'; protected $link = 'https://www.koogua.com';
protected $version = '1.6.4'; protected $version = '1.6.5';
public function __get($name) public function __get($name)
{ {

139
app/Library/Captcha.php Normal file
View File

@ -0,0 +1,139 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Library;
use Phalcon\Di;
use Phalcon\Text;
class Captcha
{
/**
* @var Cache\Backend\Redis
*/
protected $cache;
public function __construct()
{
$this->cache = Di::getDefault()->get('cache');
}
public function generate()
{
$ticket = $this->getRandTicket();
$cacheKey = $this->getCacheKey($ticket);
$expression = $this->getExpression();
$this->cache->save($cacheKey, $expression['result'], 600);
$width = 100;
$height = 25;
$im = imagecreate($width, $height);
$white = imagecolorallocate($im, 255, 255, 255);
$gray = imagecolorallocate($im, 118, 151, 199);
$bgColor = imagecolorallocate($im, rand(0, 100), rand(0, 100), rand(0, 100));
imagefilledrectangle($im, 0, 0, $width, $height, $bgColor);
for ($i = 0; $i < 200; $i++) {
imagesetpixel($im, rand(0, $width), rand(0, $height), $gray);
}
imagestring($im, 5, 5, 4, $expression['num1'], $white);
imagestring($im, 5, 30, 3, $expression['operator'], $white);
imagestring($im, 5, 45, 4, $expression['num2'], $white);
imagestring($im, 5, 70, 3, '=', $white);
imagestring($im, 5, 85, 3, '?', $white);
ob_start();
imagepng($im);
$content = ob_get_clean();
imagedestroy($im);
return [
'ticket' => $ticket,
'content' => $this->base64Encode($content),
];
}
public function check($ticket, $rand)
{
if (!$ticket) return false;
if (!$rand) return false;
$key = $this->getCacheKey($ticket);
$content = $this->cache->get($key);
return $content == $rand;
}
protected function getExpression()
{
$operators = ['+', '-', '*', '/'];
$index = array_rand($operators);
$operator = $operators[$index];
switch ($operator) {
case '+':
$num1 = rand(10, 50);
$num2 = rand(10, 50);
$result = $num1 + $num2;
break;
case '-':
$num1 = rand(50, 100);
$num2 = rand(10, 50);
$result = $num1 - $num2;
break;
case '*':
$num1 = rand(1, 10);
$num2 = rand(1, 10);
$result = $num1 * $num2;
break;
default:
$multiple = rand(2, 10);
$num1 = $multiple * rand(1, 10);
$num2 = $multiple;
$result = $num1 / $num2;
break;
}
return [
'num1' => $num1,
'num2' => $num2,
'operator' => $operator,
'result' => $result,
];
}
protected function base64Encode($content)
{
return sprintf('data:image/png;base64,%s', base64_encode($content));
}
protected function getRandTicket()
{
return Text::random(Text::RANDOM_ALNUM, 16);
}
protected function getCacheKey($key)
{
return "captcha:{$key}";
}
}

View File

@ -1,131 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Services;
use Phalcon\Logger\Adapter\File as FileLogger;
use TencentCloud\Captcha\V20190722\CaptchaClient;
use TencentCloud\Captcha\V20190722\Models\DescribeCaptchaResultRequest;
use TencentCloud\Common\Credential;
use TencentCloud\Common\Exception\TencentCloudSDKException;
use TencentCloud\Common\Profile\ClientProfile;
use TencentCloud\Common\Profile\HttpProfile;
class Captcha extends Service
{
const END_POINT = 'captcha.tencentcloudapi.com';
/**
* @var array
*/
protected $settings;
/**
* @var FileLogger
*/
protected $logger;
/**
* @var CaptchaClient
*/
protected $client;
public function __construct()
{
$this->settings = $this->getSettings('captcha');
$this->logger = $this->getLogger('captcha');
$this->client = $this->getCaptchaClient();
}
/**
* 校验验证码
*
* @param string $ticket
* @param string $rand
* @return bool
*/
function verify($ticket, $rand)
{
$userIp = $this->request->getClientAddress();
$appId = $this->settings['app_id'];
$secretKey = $this->settings['secret_key'];
$captchaType = 9;
try {
$request = new DescribeCaptchaResultRequest();
/**
* 注意CaptchaType和CaptchaAppId强类型要求
*/
$params = json_encode([
'Ticket' => $ticket,
'Randstr' => $rand,
'UserIp' => $userIp,
'CaptchaType' => (int)$captchaType,
'CaptchaAppId' => (int)$appId,
'AppSecretKey' => $secretKey,
]);
$request->fromJsonString($params);
$this->logger->debug('Describe Captcha Result Request ' . $params);
$response = $this->client->DescribeCaptchaResult($request);
$this->logger->debug('Describe Captcha Result Response ' . $response->toJsonString());
$data = json_decode($response->toJsonString(), true);
$result = $data['CaptchaCode'] == 1;
} catch (TencentCloudSDKException $e) {
$this->logger->error('Describe Captcha Result Exception ' . kg_json_encode([
'code' => $e->getErrorCode(),
'message' => $e->getMessage(),
'requestId' => $e->getRequestId(),
]));
$result = false;
}
return $result;
}
/**
* 获取CaptchaClient
*
* @return CaptchaClient
*/
public function getCaptchaClient()
{
$secret = $this->getSettings('secret');
$secretId = $secret['secret_id'];
$secretKey = $secret['secret_key'];
$region = $this->settings['region'] ?? 'ap-guangzhou';
$credential = new Credential($secretId, $secretKey);
$httpProfile = new HttpProfile();
$httpProfile->setEndpoint(self::END_POINT);
$clientProfile = new ClientProfile();
$clientProfile->setHttpProfile($httpProfile);
return new CaptchaClient($credential, $region, $clientProfile);
}
}

View File

@ -33,7 +33,12 @@ class EmailUpdate extends LogicService
$accountValidator->checkIfEmailTaken($post['email']); $accountValidator->checkIfEmailTaken($post['email']);
} }
$accountValidator->checkLoginPassword($account, $post['login_password']); /**
* 未设置过密码不检查原密码
*/
if (!empty($account->password)) {
$accountValidator->checkLoginPassword($account, $post['login_password']);
}
$verifyValidator = new VerifyValidator(); $verifyValidator = new VerifyValidator();

View File

@ -33,7 +33,12 @@ class PhoneUpdate extends LogicService
$accountValidator->checkIfPhoneTaken($post['phone']); $accountValidator->checkIfPhoneTaken($post['phone']);
} }
$accountValidator->checkLoginPassword($account, $post['login_password']); /**
* 未设置过密码不检查原密码
*/
if (!empty($account->password)) {
$accountValidator->checkLoginPassword($account, $post['login_password']);
}
$verifyValidator = new VerifyValidator(); $verifyValidator = new VerifyValidator();

View File

@ -0,0 +1,23 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Services\Logic\Verify;
use App\Library\Captcha as AppCaptcha;
use App\Services\Logic\Service as LogicService;
class Captcha extends LogicService
{
public function handle()
{
$captcha = new AppCaptcha();
return $captcha->generate();
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Services\Logic\Verify;
use App\Library\Validators\Common as CommonValidator;
use App\Services\Logic\Notice\External\Mail\Verify as MailVerifyService;
use App\Services\Logic\Notice\External\Sms\Verify as SmsVerifyService;
use App\Services\Logic\Service as LogicService;
use App\Validators\Captcha as CaptchaValidator;
use App\Validators\Verify as VerifyValidator;
class Code extends LogicService
{
public function handle()
{
$post = $this->request->getPost();
$verifyValidator = new VerifyValidator();
$captchaValidator = new CaptchaValidator();
$captchaValidator->checkCode($post['ticket'], $post['rand']);
$isMail = CommonValidator::email($post['account']);
if ($isMail) {
$account = $verifyValidator->checkEmail($post['account']);
$service = new MailVerifyService();
} else {
$account = $verifyValidator->checkPhone($post['account']);
$service = new SmsVerifyService();
}
$service->handle($account);
}
}

View File

@ -21,20 +21,15 @@ class MailCode extends LogicService
$validator = new VerifyValidator(); $validator = new VerifyValidator();
$post['email'] = $validator->checkEmail($post['email']); $email = $validator->checkEmail($post['email']);
$captcha = $this->getSettings('captcha'); $validator = new CaptchaValidator();
if ($captcha['enabled'] == 1) { $validator->checkCode($post['ticket'], $post['rand']);
$validator = new CaptchaValidator();
$validator->checkCode($post['captcha']['ticket'], $post['captcha']['rand']);
}
$service = new MailVerifyService(); $service = new MailVerifyService();
$service->handle($post['email']); $service->handle($email);
} }
} }

View File

@ -21,20 +21,15 @@ class SmsCode extends LogicService
$validator = new VerifyValidator(); $validator = new VerifyValidator();
$post['phone'] = $validator->checkPhone($post['phone']); $phone = $validator->checkPhone($post['phone']);
$captcha = $this->getSettings('captcha'); $validator = new CaptchaValidator();
if ($captcha['enabled'] == 1) { $validator->checkCode($post['ticket'], $post['rand']);
$validator = new CaptchaValidator();
$validator->checkCode($post['captcha']['ticket'], $post['captcha']['rand']);
}
$service = new SmsVerifyService(); $service = new SmsVerifyService();
$service->handle($post['phone']); $service->handle($phone);
} }
} }

View File

@ -8,16 +8,16 @@
namespace App\Validators; namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException; use App\Exceptions\BadRequest as BadRequestException;
use App\Services\Captcha as CaptchaService; use App\Library\Captcha as ImageCaptcha;
class Captcha extends Validator class Captcha extends Validator
{ {
public function checkCode($ticket, $rand) public function checkCode($ticket, $rand)
{ {
$service = new CaptchaService(); $captcha = new ImageCaptcha();
$result = $service->verify($ticket, $rand); $result = $captcha->check($ticket, $rand);
if (!$result) { if (!$result) {
throw new BadRequestException('captcha.invalid_code'); throw new BadRequestException('captcha.invalid_code');

View File

@ -5,6 +5,7 @@
"ext-pdo": "*", "ext-pdo": "*",
"ext-json": "*", "ext-json": "*",
"ext-fileinfo": "*", "ext-fileinfo": "*",
"ext-gd": "*",
"phalcon/incubator": "^3.4", "phalcon/incubator": "^3.4",
"guzzlehttp/guzzle": "^6.5", "guzzlehttp/guzzle": "^6.5",
"swiftmailer/swiftmailer": "^6.0", "swiftmailer/swiftmailer": "^6.0",

View File

@ -0,0 +1,26 @@
<?php
/**
* @copyright Copyright (c) 2023 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
use Phinx\Migration\AbstractMigration;
final class V20230625182830 extends AbstractMigration
{
public function up()
{
$this->deleteCaptchaSettings();
}
protected function deleteCaptchaSettings()
{
$this->getQueryBuilder()
->delete('kg_setting')
->where(['section' => 'captcha'])
->execute();
}
}

View File

@ -1,7 +1,6 @@
layui.config({ layui.extend({
base: '/static/lib/layui/extends/' kgDropdown: '/static/lib/layui/extends/kg-dropdown',
}).extend({ helper: '/static/lib/layui/extends/helper',
kgDropdown: 'kg-dropdown'
}); });
layui.use(['jquery', 'form', 'element', 'layer', 'kgDropdown'], function () { layui.use(['jquery', 'form', 'element', 'layer', 'kgDropdown'], function () {

View File

@ -1,20 +0,0 @@
layui.use(['jquery'], function () {
var $ = layui.jquery;
if ($('#cl-captcha-enabled').val() === '1') {
var captcha = new TencentCaptcha(
$('#cl-emit-btn')[0],
$('#cl-captcha-appId').val(),
function (res) {
if (res.ret === 0) {
$('#cl-captcha-ticket').val(res.ticket);
$('#cl-captcha-rand').val(res.randstr);
$('#cl-submit-btn').removeClass('layui-btn-disabled').removeAttr('disabled');
$('#captcha-block').hide();
}
}
);
}
});

View File

@ -1,80 +1,30 @@
layui.use(['jquery', 'layer', 'util', 'helper'], function () { layui.use(['jquery', 'layer', 'helper'], function () {
var $ = layui.jquery; var $ = layui.jquery;
var layer = layui.layer; var layer = layui.layer;
var util = layui.util;
var helper = layui.helper; var helper = layui.helper;
var timeCounting = false;
var $account = $('#cv-email'); var $account = $('#cv-email');
var $emit = $('#cv-email-emit-btn'); var $emit = $('#cv-email-emit-btn');
var $submit = $('#cv-email-submit-btn');
if ($('#cv-email-captcha-enabled').val() === '1') { $emit.on('click', function () {
var captcha = new TencentCaptcha( var url = '/verify/captcha?type=mail&account=' + $account.val();
$emit[0], layer.open({
$('#cv-email-captcha-appId').val(), type: 2,
function (res) { title: '获取验证码',
if (res.ret === 0) { area: ['500px', '250px'],
$('#cv-email-captcha-ticket').val(res.ticket); content: [url, 'no'],
$('#cv-email-captcha-rand').val(res.randstr);
sendVerifyCode();
}
}
);
} else {
$emit.on('click', function () {
sendVerifyCode();
}); });
} });
$account.on('keyup', function () { $account.on('keyup', function () {
var account = $(this).val(); var account = $(this).val();
var accountOk = helper.isEmail(account); var accountOk = helper.isEmail(account);
if (accountOk && !timeCounting) { if (accountOk) {
$emit.removeClass('layui-btn-disabled').removeAttr('disabled'); $emit.removeClass('layui-btn-disabled').removeAttr('disabled');
} else { } else {
$emit.addClass('layui-btn-disabled').attr('disabled', 'disabled'); $emit.addClass('layui-btn-disabled').attr('disabled', 'disabled');
} }
}); });
function sendVerifyCode() {
if (helper.isEmail($account.val())) {
var postUrl = '/verify/mail/code';
var postData = {
email: $account.val(),
captcha: {
ticket: $('#cv-email-captcha-ticket').val(),
rand: $('#cv-email-captcha-rand').val(),
}
};
$.ajax({
type: 'POST',
url: postUrl,
data: postData,
success: function () {
layer.msg('发送验证码成功', {icon: 1});
}
});
$submit.removeClass('layui-btn-disabled').removeAttr('disabled');
$emit.addClass('layui-btn-disabled').attr('disabled', 'disabled');
showCountDown($emit);
}
}
function showCountDown() {
var serverTime = new Date().getTime();
var endTime = serverTime + 60 * 1000;
util.countdown(endTime, serverTime, function (date, serverTime, timer) {
var left = date[0] * 86400 + date[1] * 3600 + date[2] * 60 + date[3];
$emit.text(left + '秒');
if (left === 0) {
$emit.removeClass('layui-btn-disabled').removeAttr('disabled').text('重新发送');
clearInterval(timer);
timeCounting = false;
}
});
timeCounting = true;
}
}); });

View File

@ -1,86 +1,30 @@
layui.use(['jquery', 'layer', 'util', 'helper'], function () { layui.use(['jquery', 'layer', 'helper'], function () {
var $ = layui.jquery; var $ = layui.jquery;
var layer = layui.layer; var layer = layui.layer;
var util = layui.util;
var helper = layui.helper; var helper = layui.helper;
var timeCounting = false;
var $account = $('#cv-account'); var $account = $('#cv-account');
var $emit = $('#cv-emit-btn'); var $emit = $('#cv-emit-btn');
var $submit = $('#cv-submit-btn');
if ($('#cv-captcha-enabled').val() === '1') { $emit.on('click', function () {
var captcha = new TencentCaptcha( var url = '/verify/captcha?type=all&account=' + $account.val();
$emit[0], layer.open({
$('#cv-captcha-appId').val(), type: 2,
function (res) { title: '获取验证码',
if (res.ret === 0) { area: ['500px', '250px'],
$('#cv-captcha-ticket').val(res.ticket); content: [url, 'no'],
$('#cv-captcha-rand').val(res.randstr);
sendVerifyCode();
}
}
);
} else {
$emit.on('click', function () {
sendVerifyCode();
}); });
} });
$account.on('keyup', function () { $account.on('keyup', function () {
var account = $(this).val(); var account = $(this).val();
var accountOk = helper.isPhone(account) || helper.isEmail(account); var accountOk = helper.isPhone(account) || helper.isEmail(account);
if (accountOk && !timeCounting) { if (accountOk) {
$emit.removeClass('layui-btn-disabled').removeAttr('disabled'); $emit.removeClass('layui-btn-disabled').removeAttr('disabled');
} else { } else {
$emit.addClass('layui-btn-disabled').attr('disabled', 'disabled'); $emit.addClass('layui-btn-disabled').attr('disabled', 'disabled');
} }
}); });
function sendVerifyCode() {
if (helper.isEmail($account.val()) || helper.isPhone($account.val())) {
var postUrl;
var postData = {
captcha: {
ticket: $('#cv-captcha-ticket').val(),
rand: $('#cv-captcha-rand').val(),
}
};
if (helper.isPhone($account.val())) {
postData.phone = $account.val();
postUrl = '/verify/sms/code';
} else if (helper.isEmail($account.val())) {
postData.email = $account.val();
postUrl = '/verify/mail/code';
}
$.ajax({
type: 'POST',
url: postUrl,
data: postData,
success: function () {
layer.msg('发送验证码成功', {icon: 1});
}
});
$submit.removeClass('layui-btn-disabled').removeAttr('disabled');
$emit.addClass('layui-btn-disabled').attr('disabled', 'disabled');
showCountDown($emit);
}
}
function showCountDown() {
var serverTime = new Date().getTime();
var endTime = serverTime + 60 * 1000;
util.countdown(endTime, serverTime, function (date, serverTime, timer) {
var left = date[0] * 86400 + date[1] * 3600 + date[2] * 60 + date[3];
$emit.text(left + '秒');
if (left === 0) {
$emit.removeClass('layui-btn-disabled').removeAttr('disabled').text('重新发送');
clearInterval(timer);
timeCounting = false;
}
});
timeCounting = true;
}
}); });

View File

@ -1,80 +1,30 @@
layui.use(['jquery', 'layer', 'util', 'helper'], function () { layui.use(['jquery', 'layer', 'helper'], function () {
var $ = layui.jquery; var $ = layui.jquery;
var layer = layui.layer; var layer = layui.layer;
var util = layui.util;
var helper = layui.helper; var helper = layui.helper;
var timeCounting = false;
var $account = $('#cv-phone'); var $account = $('#cv-phone');
var $emit = $('#cv-phone-emit-btn'); var $emit = $('#cv-phone-emit-btn');
var $submit = $('#cv-phone-submit-btn');
if ($('#cv-phone-captcha-enabled').val() === '1') { $emit.on('click', function () {
var captcha = new TencentCaptcha( var url = '/verify/captcha?type=phone&account=' + $account.val();
$emit[0], layer.open({
$('#cv-phone-captcha-appId').val(), type: 2,
function (res) { title: '获取验证码',
if (res.ret === 0) { area: ['500px', '250px'],
$('#cv-phone-captcha-ticket').val(res.ticket); content: [url, 'no'],
$('#cv-phone-captcha-rand').val(res.randstr);
sendVerifyCode();
}
}
);
} else {
$emit.on('click', function () {
sendVerifyCode();
}); });
} });
$account.on('keyup', function () { $account.on('keyup', function () {
var account = $(this).val(); var account = $(this).val();
var accountOk = helper.isPhone(account); var accountOk = helper.isPhone(account);
if (accountOk && !timeCounting) { if (accountOk) {
$emit.removeClass('layui-btn-disabled').removeAttr('disabled'); $emit.removeClass('layui-btn-disabled').removeAttr('disabled');
} else { } else {
$emit.addClass('layui-btn-disabled').attr('disabled', 'disabled'); $emit.addClass('layui-btn-disabled').attr('disabled', 'disabled');
} }
}); });
function sendVerifyCode() {
if (helper.isPhone($account.val())) {
var postUrl = '/verify/sms/code';
var postData = {
phone: $account.val(),
captcha: {
ticket: $('#cv-phone-captcha-ticket').val(),
rand: $('#cv-phone-captcha-rand').val(),
}
};
$.ajax({
type: 'POST',
url: postUrl,
data: postData,
success: function () {
layer.msg('发送验证码成功', {icon: 1});
}
});
$submit.removeClass('layui-btn-disabled').removeAttr('disabled');
$emit.addClass('layui-btn-disabled').attr('disabled', 'disabled');
showCountDown($emit);
}
}
function showCountDown() {
var serverTime = new Date().getTime();
var endTime = serverTime + 60 * 1000;
util.countdown(endTime, serverTime, function (date, serverTime, timer) {
var left = date[0] * 86400 + date[1] * 3600 + date[2] * 60 + date[3];
$emit.text(left + '秒');
if (left === 0) {
$emit.removeClass('layui-btn-disabled').removeAttr('disabled').text('重新发送');
clearInterval(timer);
timeCounting = false;
}
});
timeCounting = true;
}
}); });

View File

@ -1,8 +1,4 @@
layui.config({ layui.extend({
layimPath: '/static/lib/layui/extends/layim/',
layimAssetsPath: '/static/lib/layui/extends/layim/assets/',
}).extend({
layim: layui.cache.layimPath + 'layim',
layarea: '/static/lib/layui/extends/layarea', layarea: '/static/lib/layui/extends/layarea',
helper: '/static/lib/layui/extends/helper', helper: '/static/lib/layui/extends/helper',
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -51,26 +51,20 @@ $scheduler->php($script, $bin, ['--task' => 'refund', '--action' => 'main'])
$scheduler->php($script, $bin, ['--task' => 'sync_course_index', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'sync_course_index', '--action' => 'main'])
->hourly(11); ->hourly(11);
$scheduler->php($script, $bin, ['--task' => 'sync_group_index', '--action' => 'main'])
->hourly(17);
$scheduler->php($script, $bin, ['--task' => 'sync_user_index', '--action' => 'main'])
->hourly(23);
$scheduler->php($script, $bin, ['--task' => 'sync_article_index', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'sync_article_index', '--action' => 'main'])
->hourly(27); ->hourly(13);
$scheduler->php($script, $bin, ['--task' => 'sync_question_index', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'sync_question_index', '--action' => 'main'])
->hourly(29); ->hourly(17);
$scheduler->php($script, $bin, ['--task' => 'sync_course_score', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'sync_course_score', '--action' => 'main'])
->hourly(31); ->hourly(19);
$scheduler->php($script, $bin, ['--task' => 'sync_article_score', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'sync_article_score', '--action' => 'main'])
->hourly(33); ->hourly(23);
$scheduler->php($script, $bin, ['--task' => 'sync_question_score', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'sync_question_score', '--action' => 'main'])
->hourly(37); ->hourly(29);
$scheduler->php($script, $bin, ['--task' => 'reset_demo_account', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'reset_demo_account', '--action' => 'main'])
->hourly(59); ->hourly(59);