1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-07-03 23:46:49 +08:00

增加限流

This commit is contained in:
xiaochong0302 2020-04-09 18:44:46 +08:00
parent 24f25c8867
commit c7b2fdac66
18 changed files with 244 additions and 78 deletions

View File

@ -35,13 +35,13 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-inline"> <div class="layui-inline">
<label class="layui-form-label">ICP备案号</label> <label class="layui-form-label">ICP备案号</label>
<div class="layui-input-inline"> <div class="kg-input-inline">
<input class="layui-input" type="text" name="icp_sn" value="{{ site.icp_sn }}"> <input class="layui-input" type="text" name="icp_sn" value="{{ site.icp_sn }}">
</div> </div>
</div> </div>
<div class="layui-inline"> <div class="layui-inline">
<label class="layui-form-label">备案链接</label> <label class="layui-form-label">备案链接</label>
<div class="layui-input-inline"> <div class="kg-input-inline">
<input class="layui-input" type="text" name="icp_link" value="{{ site.icp_link }}"> <input class="layui-input" type="text" name="icp_link" value="{{ site.icp_link }}">
</div> </div>
</div> </div>
@ -50,13 +50,13 @@
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-inline"> <div class="layui-inline">
<label class="layui-form-label">公安备案号</label> <label class="layui-form-label">公安备案号</label>
<div class="layui-input-inline"> <div class="kg-input-inline">
<input class="layui-input" type="text" name="police_sn" value="{{ site.police_sn }}"> <input class="layui-input" type="text" name="police_sn" value="{{ site.police_sn }}">
</div> </div>
</div> </div>
<div class="layui-inline"> <div class="layui-inline">
<label class="layui-form-label">备案链接</label> <label class="layui-form-label">备案链接</label>
<div class="layui-input-inline"> <div class="kg-input-inline">
<input class="layui-input" type="text" name="police_link" value="{{ site.police_link }}"> <input class="layui-input" type="text" name="police_link" value="{{ site.police_link }}">
</div> </div>
</div> </div>

View File

@ -3,15 +3,23 @@
namespace App\Http\Api\Controllers; namespace App\Http\Api\Controllers;
use App\Traits\Response as ResponseTrait; use App\Traits\Response as ResponseTrait;
use App\Traits\Security as SecurityTrait;
use Phalcon\Mvc\Dispatcher;
class Controller extends \Phalcon\Mvc\Controller class Controller extends \Phalcon\Mvc\Controller
{ {
use ResponseTrait; use ResponseTrait, SecurityTrait;
public function initialize() public function beforeExecuteRoute(Dispatcher $dispatcher)
{ {
if (!$this->checkRateLimit()) {
$dispatcher->forward([
'controller' => 'public',
'action' => 'throttle',
]);
return false;
}
} }
} }

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Api\Controllers;
use App\Traits\Response as ResponseTrait;
/**
* @RoutePrefix("/api")
*/
class PublicController extends \Phalcon\Mvc\Controller
{
use ResponseTrait;
/**
* @Get("/throttle", name="api.throttle")
*/
public function throttleAction()
{
return $this->jsonError(['msg' => '请求过于频繁']);
}
}

View File

@ -3,15 +3,23 @@
namespace App\Http\Html5\Controllers; namespace App\Http\Html5\Controllers;
use App\Traits\Response as ResponseTrait; use App\Traits\Response as ResponseTrait;
use App\Traits\Security as SecurityTrait;
use Phalcon\Mvc\Dispatcher;
class Controller extends \Phalcon\Mvc\Controller class Controller extends \Phalcon\Mvc\Controller
{ {
use ResponseTrait; use ResponseTrait, SecurityTrait;
public function initialize() public function beforeExecuteRoute(Dispatcher $dispatcher)
{ {
if (!$this->checkRateLimit()) {
$dispatcher->forward([
'controller' => 'public',
'action' => 'throttle',
]);
return false;
}
} }
} }

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Html5\Controllers;
use App\Traits\Response as ResponseTrait;
/**
* @RoutePrefix("/html5")
*/
class PublicController extends \Phalcon\Mvc\Controller
{
use ResponseTrait;
/**
* @Get("/throttle", name="html5.throttle")
*/
public function throttleAction()
{
return $this->jsonError(['msg' => '请求过于频繁']);
}
}

View File

@ -20,6 +20,14 @@ class Controller extends \Phalcon\Mvc\Controller
public function beforeExecuteRoute(Dispatcher $dispatcher) public function beforeExecuteRoute(Dispatcher $dispatcher)
{ {
if (!$this->checkRateLimit()) {
$dispatcher->forward([
'controller' => 'public',
'action' => 'throttle',
]);
return false;
}
if ($this->isNotSafeRequest()) { if ($this->isNotSafeRequest()) {
if (!$this->checkHttpReferer() || !$this->checkCsrfToken()) { if (!$this->checkHttpReferer() || !$this->checkCsrfToken()) {
$dispatcher->forward([ $dispatcher->forward([

View File

@ -50,6 +50,18 @@ class PublicController extends \Phalcon\Mvc\Controller
} }
} }
/**
* @Get("/throttle", name="web.throttle")
*/
public function throttleAction()
{
$isAjaxRequest = is_ajax_request();
if ($isAjaxRequest) {
return $this->jsonError(['msg' => 'web请求过于频繁']);
}
}
/** /**
* @Get("/content/img/{id:[0-9]+}", name="web.content.img") * @Get("/content/img/{id:[0-9]+}", name="web.content.img")
*/ */

View File

@ -55,7 +55,7 @@ class AccessToken extends Model
public function beforeCreate() public function beforeCreate()
{ {
$this->id = $this->getRandId($this->user_id); $this->id = $this->getRandId($this->user_id);
$this->expiry_time = strtotime('+2 hours');
$this->create_time = time(); $this->create_time = time();
} }

View File

@ -55,7 +55,7 @@ class RefreshToken extends Model
public function beforeCreate() public function beforeCreate()
{ {
$this->id = $this->getRandId($this->user_id); $this->id = $this->getRandId($this->user_id);
$this->expiry_time = strtotime('+30 days');
$this->create_time = time(); $this->create_time = time();
} }

View File

@ -14,12 +14,16 @@ class Api extends AuthService
public function saveAuthInfo(UserModel $user) public function saveAuthInfo(UserModel $user)
{ {
$config = $this->getDI()->get('config');
$accessToken = new AccessTokenModel(); $accessToken = new AccessTokenModel();
$accessToken->user_id = $user->id; $accessToken->user_id = $user->id;
$accessToken->expiry_time = time() + $config->access_token->lifetime;
$accessToken->create(); $accessToken->create();
$refreshToken = new RefreshTokenModel(); $refreshToken = new RefreshTokenModel();
$refreshToken->user_id = $user->id; $refreshToken->user_id = $user->id;
$refreshToken->expiry_time = time() + $config->refresh_token->lifetime;
$refreshToken->create(); $refreshToken->create();
$authInfo = [ $authInfo = [
@ -31,7 +35,7 @@ class Api extends AuthService
$key = $this->getCacheKey($accessToken->id); $key = $this->getCacheKey($accessToken->id);
$cache->save($key, $authInfo, 2 * 3600); $cache->save($key, $authInfo, $config->access_token->lifetime);
return new Collection([ return new Collection([
'access_token' => $accessToken->id, 'access_token' => $accessToken->id,

View File

@ -2,59 +2,11 @@
namespace App\Services\Frontend; namespace App\Services\Frontend;
use App\Models\User as UserModel; use App\Traits\Auth as AuthTrait;
use App\Repos\User as UserRepo;
use App\Services\Auth as AuthService;
use App\Validators\Validator as AppValidator;
use Phalcon\Mvc\User\Component; use Phalcon\Mvc\User\Component;
class Service extends Component class Service extends Component
{ {
public function getCurrentUser() use AuthTrait;
{
$authUser = $this->getAuthUser();
if (!$authUser) {
return $this->getGuestUser();
}
$userRepo = new UserRepo();
return $userRepo->findById($authUser['id']);
}
public function getLoginUser()
{
$authUser = $this->getAuthUser();
$validator = new AppValidator();
$validator->checkAuthUser($authUser);
$userRepo = new UserRepo();
return $userRepo->findById($authUser['id']);
}
public function getAuthUser()
{
/**
* @var AuthService $auth
*/
$auth = $this->getDI()->get('auth');
return $auth->getAuthInfo();
}
public function getGuestUser()
{
$user = new UserModel();
$user->id = 0;
$user->name = 'guest';
return $user;
}
} }

View File

@ -4,12 +4,15 @@ namespace App\Services;
use App\Caches\SectionConfig as SectionConfigCache; use App\Caches\SectionConfig as SectionConfigCache;
use App\Library\Logger as AppLogger; use App\Library\Logger as AppLogger;
use App\Traits\Auth as AuthTrait;
use Phalcon\Logger\Adapter\File as FileLogger; use Phalcon\Logger\Adapter\File as FileLogger;
use Phalcon\Mvc\User\Component; use Phalcon\Mvc\User\Component;
class Service extends Component class Service extends Component
{ {
use AuthTrait;
/** /**
* 获取Logger * 获取Logger
* *

65
app/Services/Throttle.php Normal file
View File

@ -0,0 +1,65 @@
<?php
namespace App\Services;
use Phalcon\Cache\Backend\Redis as RedisCache;
class Throttle extends Service
{
public function checkRateLimit()
{
$config = $this->getDI()->get('config');
if ($config->throttle->enabled == false) {
return true;
}
/**
* @var RedisCache $cache
*/
$cache = $this->getDI()->get('cache');
$sign = $this->getRequestSignature();
$cacheKey = $this->getCacheKey($sign);
$rateLimit = $cache->get($cacheKey);
if ($rateLimit) {
if ($rateLimit >= $config->throttle->rate_limit) {
return false;
} else {
$cache->increment($cacheKey, 1);
}
} else {
$cache->save($cacheKey, 1, $config->throttle->lifetime);
}
return true;
}
protected function getRequestSignature()
{
$authUser = $this->getAuthUser();
if (!empty($authUser->id)) {
return md5($authUser->id);
}
$httpHost = $this->request->getHttpHost();
$clientAddress = $this->request->getClientAddress();
if ($httpHost && $clientAddress) {
return md5($httpHost . '|' . $clientAddress);
}
throw new \RuntimeException('Unable to generate the request signature.');
}
protected function getCacheKey($sign)
{
return "throttle:{$sign}";
}
}

View File

@ -2,15 +2,20 @@
namespace App\Traits; namespace App\Traits;
use App\Exceptions\Unauthorized as UnauthorizedException;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Repos\User as UserRepo; use App\Repos\User as UserRepo;
use App\Services\Auth as AuthService; use App\Services\Auth as AuthService;
use App\Validators\Validator as AppValidator; use App\Validators\Validator as AppValidator;
use Phalcon\Di; use Phalcon\Di;
use Yansongda\Supports\Collection;
trait Auth trait Auth
{ {
/**
* @return UserModel
*/
public function getCurrentUser() public function getCurrentUser()
{ {
$authUser = $this->getAuthUser(); $authUser = $this->getAuthUser();
@ -24,6 +29,10 @@ trait Auth
return $userRepo->findById($authUser->id); return $userRepo->findById($authUser->id);
} }
/**
* @return UserModel
* @throws UnauthorizedException
*/
public function getLoginUser() public function getLoginUser()
{ {
$authUser = $this->getAuthUser(); $authUser = $this->getAuthUser();
@ -37,6 +46,9 @@ trait Auth
return $userRepo->findById($authUser->id); return $userRepo->findById($authUser->id);
} }
/**
* @return UserModel
*/
public function getGuestUser() public function getGuestUser()
{ {
$user = new UserModel(); $user = new UserModel();
@ -47,6 +59,9 @@ trait Auth
return $user; return $user;
} }
/**
* @return Collection|null
*/
public function getAuthUser() public function getAuthUser()
{ {
/** /**

View File

@ -2,6 +2,7 @@
namespace App\Traits; namespace App\Traits;
use App\Services\Throttle;
use Phalcon\Di; use Phalcon\Di;
use Phalcon\Http\Request; use Phalcon\Http\Request;
@ -38,6 +39,13 @@ trait Security
return $httpHost == $request->getHttpHost(); return $httpHost == $request->getHttpHost();
} }
public function checkRateLimit()
{
$throttle = new Throttle();
return $throttle->checkRateLimit();
}
public function isNotSafeRequest() public function isNotSafeRequest()
{ {
/** /**

View File

@ -85,13 +85,38 @@ $config['redis']['index'] = 0;
/** /**
* 缓存有效期(秒) * 缓存有效期(秒)
*/ */
$config['redis']['lifetime'] = 24 * 3600; $config['redis']['lifetime'] = 2 * 86400;
/** /**
* 会话有效期(秒) * 会话有效期(秒)
*/ */
$config['session']['lifetime'] = 2 * 3600; $config['session']['lifetime'] = 2 * 3600;
/**
* 访问令牌有效期(秒)
*/
$config['access_token']['lifetime'] = 2 * 3600;
/**
* 刷新令牌有效期(秒)
*/
$config['refresh_token']['lifetime'] = 30 * 86400;
/**
* 限流开启
*/
$config['throttle']['enabled'] = true;
/**
* 限流有效期(秒)
*/
$config['throttle']['lifetime'] = 60;
/**
* 限流频率
*/
$config['throttle']['rate_limit'] = 60;
/** /**
* 日志级别 * 日志级别
*/ */

View File

@ -28,6 +28,12 @@
width: 30px; width: 30px;
} }
.kg-input-inline {
float: left;
width: 250px;
margin-right: 10px;
}
.kg-btn-verify { .kg-btn-verify {
color: green; color: green;
} }

View File

@ -35,44 +35,50 @@ function xmCourse(data, url) {
table.render({ table.render({
id: 'course-table', id: 'course-table',
elem: '#course-table', elem: '#course-table',
width: 900,
url: url, url: url,
page: true, page: true,
cols: [[ cols: [[
{field: 'id', title: '编号', width: 40}, {field: 'id', title: '编号', width: 50},
{field: 'title', title: '标题', width: 340}, {field: 'title', title: '标题', width: 390},
{ {
field: 'model', title: '类型', width: 40, templet: function (d) { field: 'model', title: '类型', width: 50, templet: function (d) {
if (d.model === 1) { if (d.model === 'vod') {
return '点播'; return '点播';
} else if (d.model === 2) { } else if (d.model === 'live') {
return '直播'; return '直播';
} else if (d.model === 3) { } else if (d.model === 'read') {
return '图文'; return '图文';
} }
} }
}, },
{ {
field: 'level', title: '难度', width: 40, templet: function (d) { field: 'level', title: '难度', width: 50, templet: function (d) {
if (d.level === 1) { if (d.level === 'entry') {
return '入门'; return '入门';
} else if (d.level === 2) { } else if (d.level === 'junior') {
return '初级'; return '初级';
} else if (d.level === 3) { } else if (d.level === 'medium') {
return '中级'; return '中级';
} else if (d.level === 4) { } else if (d.level === 'senior') {
return '高级'; return '高级';
} }
} }
}, },
{ {
field: 'user_count', title: '用户', width: 40, templet: function (d) { field: 'user_count', title: '用户', width: 50, templet: function (d) {
return d.user_count; return d.user_count;
} }
}, },
{ {
field: 'market_price', title: '价格', width: 40, templet: function (d) { field: 'market_price', title: '市场价', width: 50, templet: function (d) {
return '¥' + d.market_price; return '¥' + d.market_price;
} }
},
{
field: 'vip_price', title: '会员价', width: 50, templet: function (d) {
return '¥' + d.vip_price;
}
} }
]] ]]
}); });