1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-26 20:52:44 +08:00

增加限制共享帐号功能

This commit is contained in:
xiaochong0302 2021-01-02 13:31:13 +08:00
parent 15f9869557
commit 26deccc36b
15 changed files with 543 additions and 147 deletions

View File

@ -1,43 +0,0 @@
<?php
namespace App\Console\Tasks;
class CleanSessionTask extends Task
{
public function mainAction()
{
$config = $this->getConfig();
$redis = $this->getRedis();
$redis->select($config->path('session.db'));
$keys = $this->querySessionKeys(10000);
if (count($keys) == 0) return;
$lifetime = $config->path('session.lifetime');
foreach ($keys as $key) {
$ttl = $redis->ttl($key);
$content = $redis->get($key);
if (empty($content) && $ttl < $lifetime * 0.5) {
$redis->del($key);
}
}
}
/**
* 查找待清理会话
*
* @param int $limit
* @return array
*/
protected function querySessionKeys($limit)
{
$cache = $this->getCache();
return $cache->queryKeys('_PHCR', $limit);
}
}

View File

@ -42,13 +42,9 @@ class UpgradeTask extends Task
*/ */
public function resetAnnotationAction() public function resetAnnotationAction()
{ {
$config = $this->getConfig();
$redis = $this->getRedis(); $redis = $this->getRedis();
$dbIndex = $config->path('annotation.db'); $statsKey = '_ANNOTATION_';
$statsKey = $config->path('annotation.statsKey');
$redis->select($dbIndex);
$keys = $redis->sMembers($statsKey); $keys = $redis->sMembers($statsKey);
@ -70,13 +66,9 @@ class UpgradeTask extends Task
*/ */
public function resetMetadataAction() public function resetMetadataAction()
{ {
$config = $this->getConfig();
$redis = $this->getRedis(); $redis = $this->getRedis();
$dbIndex = $config->path('metadata.db'); $statsKey = '_METADATA_';
$statsKey = $config->path('metadata.statsKey');
$redis->select($dbIndex);
$keys = $redis->sMembers($statsKey); $keys = $redis->sMembers($statsKey);

View File

@ -19,13 +19,6 @@ class Online extends Model
*/ */
public $user_id; public $user_id;
/**
* 计划编号
*
* @var string
*/
public $date;
/** /**
* 客户端类型 * 客户端类型
* *

View File

@ -0,0 +1,93 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class UserSession extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 会话编号
*
* @var string
*/
public $session_id;
/**
* 终端类型
*
* @var int
*/
public $client_type;
/**
* 终端IP
*
* @var string
*/
public $client_ip;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_user_session';
}
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();
}
}

93
app/Models/UserToken.php Normal file
View File

@ -0,0 +1,93 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class UserToken extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 令牌
*
* @var string
*/
public $token;
/**
* 终端类型
*
* @var int
*/
public $client_type;
/**
* 终端IP
*
* @var string
*/
public $client_ip;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_user_token';
}
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();
}
}

View File

@ -23,13 +23,14 @@ class Annotation extends Provider
if ($config->get('env') == ENV_DEV) { if ($config->get('env') == ENV_DEV) {
$annotations = new MemoryAnnotations(); $annotations = new MemoryAnnotations();
} else { } else {
$statsKey = '_ANNOTATION_';
$annotations = new RedisAnnotations([ $annotations = new RedisAnnotations([
'host' => $config->path('redis.host'), 'host' => $config->path('redis.host'),
'port' => $config->path('redis.port'), 'port' => $config->path('redis.port'),
'auth' => $config->path('redis.auth'), 'auth' => $config->path('redis.auth'),
'index' => $config->path('annotation.db'), 'lifetime' => $config->path('annotation.lifetime') ?: 30 * 86400,
'lifetime' => $config->path('annotation.lifetime'), 'prefix' => $statsKey . ':',
'statsKey' => $config->path('annotation.statsKey'), 'statsKey' => $statsKey,
]); ]);
} }

View File

@ -23,13 +23,14 @@ class MetaData extends Provider
if ($config->get('env') == ENV_DEV) { if ($config->get('env') == ENV_DEV) {
$metaData = new MemoryMetaData(); $metaData = new MemoryMetaData();
} else { } else {
$statsKey = '_METADATA_';
$metaData = new RedisMetaData([ $metaData = new RedisMetaData([
'host' => $config->path('redis.host'), 'host' => $config->path('redis.host'),
'port' => $config->path('redis.port'), 'port' => $config->path('redis.port'),
'auth' => $config->path('redis.auth'), 'auth' => $config->path('redis.auth'),
'index' => $config->path('metadata.db'), 'lifetime' => $config->path('metadata.lifetime') ?: 30 * 86400,
'statsKey' => $config->path('metadata.statsKey'), 'prefix' => $statsKey . ':',
'lifetime' => $config->path('metadata.lifetime'), 'statsKey' => $statsKey,
]); ]);
} }

View File

@ -23,8 +23,8 @@ class Session extends Provider
'host' => $config->path('redis.host'), 'host' => $config->path('redis.host'),
'port' => $config->path('redis.port'), 'port' => $config->path('redis.port'),
'auth' => $config->path('redis.auth'), 'auth' => $config->path('redis.auth'),
'index' => $config->path('session.db'), 'lifetime' => $config->path('session.lifetime') ?: 24 * 3600,
'lifetime' => $config->path('session.lifetime'), 'prefix' => '_SESSION_:',
]); ]);
$session->start(); $session->start();

24
app/Repos/UserSession.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Repos;
use App\Models\UserSession as UserSessionModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class UserSession extends Repository
{
/**
* @param int $userId
* @return ResultsetInterface|Resultset|UserSessionModel[]
*/
public function findByUserId($userId)
{
return UserSessionModel::query()
->where('user_id = :user_id:', ['user_id' => $userId])
->andWhere('deleted = 0')
->execute();
}
}

24
app/Repos/UserToken.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Repos;
use App\Models\UserToken as UserTokenModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class UserToken extends Repository
{
/**
* @param int $userId
* @return ResultsetInterface|Resultset|UserTokenModel[]
*/
public function findByUserId($userId)
{
return UserTokenModel::query()
->where('user_id = :user_id:', ['user_id' => $userId])
->andWhere('deleted = 0')
->execute();
}
}

View File

@ -3,70 +3,114 @@
namespace App\Services\Auth; namespace App\Services\Auth;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Models\UserToken as UserTokenModel;
use App\Repos\UserToken as UserTokenRepo;
use App\Services\Auth as AuthService; use App\Services\Auth as AuthService;
use Lcobucci\JWT\Builder as JwtBuilder; use App\Traits\Client as ClientTrait;
use Lcobucci\JWT\Parser as JwtParser;
use Lcobucci\JWT\Signer\Hmac\Sha256 as JwtSingerSha256;
use Lcobucci\JWT\Signer\Key as JwtSingerKey;
use Lcobucci\JWT\ValidationData as JwtValidationData;
class Api extends AuthService class Api extends AuthService
{ {
use ClientTrait;
public function saveAuthInfo(UserModel $user) public function saveAuthInfo(UserModel $user)
{ {
$builder = new JwtBuilder(); $token = $this->generateToken($user->id);
$this->logoutOtherClients($user->id);
$this->createUserToken($user->id, $token);
$cache = $this->getCache();
$key = $this->getTokenCacheKey($token);
$authInfo = [
'id' => $user->id,
'name' => $user->name,
];
$config = $this->getConfig(); $config = $this->getConfig();
$expireTime = time() + $config->path('jwt.lifetime'); $lifetime = $config->path('token.lifetime') ?: 7 * 86400;
$builder->expiresAt($expireTime); $cache->save($key, $authInfo, $lifetime);
$builder->withClaim('user_id', $user->id);
$builder->withClaim('user_name', $user->name);
$singer = new JwtSingerSha256(); return $token;
$key = new JwtSingerKey($config->path('jwt.key'));
$token = $builder->getToken($singer, $key);
return $token->__toString();
} }
public function clearAuthInfo() public function clearAuthInfo()
{ {
$token = $this->request->getHeader('X-Token');
if (empty($token)) return null;
$cache = $this->getCache();
$key = $this->getTokenCacheKey($token);
$cache->delete($key);
} }
public function getAuthInfo() public function getAuthInfo()
{ {
$authToken = $this->request->getHeader('X-Token'); $token = $this->request->getHeader('X-Token');
if (!$authToken) return null; if (empty($token)) return null;
$config = $this->getConfig(); $cache = $this->getCache();
$parser = new JWTParser(); $key = $this->getTokenCacheKey($token);
$token = $parser->parse($authToken); $authInfo = $cache->get($key);
$data = new JWTValidationData(time(), $config->path('jwt.leeway')); return $authInfo ?: null;
}
if (!$token->validate($data)) { protected function createUserToken($userId, $token)
return null; {
$userToken = new UserTokenModel();
$userToken->user_id = $userId;
$userToken->token = $token;
$userToken->client_type = $this->getClientType();
$userToken->client_ip = $this->getClientIp();
$userToken->create();
}
protected function logoutOtherClients($userId)
{
$repo = new UserTokenRepo();
$records = $repo->findByUserId($userId);
$cache = $this->getCache();
$clientType = $this->getClientType();
if ($records->count() == 0) {
return;
} }
$singer = new JwtSingerSha256(); foreach ($records as $record) {
if ($record->client_type == $clientType) {
if (!$token->verify($singer, $config->path('jwt.key'))) { $record->deleted = 1;
return null; $record->update();
$key = $this->getTokenCacheKey($record->token);
$cache->delete($key);
}
} }
}
return [ protected function generateToken($userId)
'id' => $token->getClaim('user_id'), {
'name' => $token->getClaim('user_name'), return md5(uniqid() . time() . $userId);
]; }
protected function getTokenCacheKey($token)
{
return "_PHCR_TOKEN_:{$token}";
} }
} }

View File

@ -3,13 +3,24 @@
namespace App\Services\Auth; namespace App\Services\Auth;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Models\UserSession as UserSessionModel;
use App\Repos\UserSession as UserSessionRepo;
use App\Services\Auth as AuthService; use App\Services\Auth as AuthService;
use App\Traits\Client as ClientTrait;
class Home extends AuthService class Home extends AuthService
{ {
use ClientTrait;
public function saveAuthInfo(UserModel $user) public function saveAuthInfo(UserModel $user)
{ {
$sessionId = $this->session->getId();
$this->logoutOtherClients($user->id);
$this->createUserSession($user->id, $sessionId);
$authKey = $this->getAuthKey(); $authKey = $this->getAuthKey();
$authInfo = [ $authInfo = [
@ -41,4 +52,41 @@ class Home extends AuthService
return 'home_auth_info'; return 'home_auth_info';
} }
protected function createUserSession($userId, $sessionId)
{
$userSession = new UserSessionModel();
$userSession->user_id = $userId;
$userSession->session_id = $sessionId;
$userSession->client_type = $this->getClientType();
$userSession->client_ip = $this->getClientIp();
$userSession->create();
}
protected function logoutOtherClients($userId)
{
$cache = $this->getCache();
$repo = new UserSessionRepo();
$records = $repo->findByUserId($userId);
if ($records->count() == 0) {
return;
}
foreach ($records as $record) {
$record->deleted = 1;
$record->update();
$key = $this->getSessionCacheKey($record->session_id);
$cache->delete($key);
}
}
protected function getSessionCacheKey($sessionId)
{
return "_PHCR_SESSION_:{$sessionId}";
}
} }

View File

@ -27,6 +27,14 @@ trait Client
*/ */
$request = Di::getDefault()->get('request'); $request = Di::getDefault()->get('request');
$platform = $request->getHeader('X-Platform');
$types = array_flip(ClientModel::types());
if (!empty($platform) && isset($types[$platform])) {
return $types[$platform];
}
$userAgent = $request->getServer('HTTP_USER_AGENT'); $userAgent = $request->getServer('HTTP_USER_AGENT');
$result = new BrowserParser($userAgent); $result = new BrowserParser($userAgent);

View File

@ -83,70 +83,30 @@ $config['redis']['port'] = 6379;
$config['redis']['auth'] = '1qaz2wsx3edc'; $config['redis']['auth'] = '1qaz2wsx3edc';
/** /**
* redis库编号 * 缓存有效期(秒)
*/
$config['cache']['db'] = 0;
/**
* 有效期(秒)
*/ */
$config['cache']['lifetime'] = 24 * 3600; $config['cache']['lifetime'] = 24 * 3600;
/** /**
* redis库编号 * 会话有效期(秒)
*/
$config['session']['db'] = 1;
/**
* 有效期(秒)
*/ */
$config['session']['lifetime'] = 24 * 3600; $config['session']['lifetime'] = 24 * 3600;
/** /**
* redis库编号 * 令牌有效期(秒)
*/ */
$config['metadata']['db'] = 2; $config['token']['lifetime'] = 7 * 86400;
/** /**
* 有效期(秒) * 元数据有效期(秒)
*/ */
$config['metadata']['lifetime'] = 7 * 86400; $config['metadata']['lifetime'] = 7 * 86400;
/** /**
* statsKey * 注解有效期(秒)
*/
$config['metadata']['statsKey'] = '_METADATA_';
/**
* redis库编号
*/
$config['annotation']['db'] = 2;
/**
* 有效期(秒)
*/ */
$config['annotation']['lifetime'] = 7 * 86400; $config['annotation']['lifetime'] = 7 * 86400;
/**
* statsKey
*/
$config['annotation']['statsKey'] = '_ANNOTATION_';
/**
* 密钥
*/
$config['jwt']['key'] = 'fu6ckEc8pv8k5K7m';
/**
* 有效期(秒)
*/
$config['jwt']['lifetime'] = 7 * 86400;
/**
* 回旋时间(秒)
*/
$config['jwt']['leeway'] = 30;
/** /**
* 允许跨域 * 允许跨域
*/ */

View File

@ -0,0 +1,158 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class Schema202101021220 extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('kg_user_session', [
'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('session_id', 'string', [
'null' => false,
'default' => '',
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '会话编号',
'after' => 'user_id',
])
->addColumn('client_type', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '终端类型',
'after' => 'session_id',
])
->addColumn('client_ip', 'string', [
'null' => false,
'default' => '',
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '终端IP',
'after' => 'client_type',
])
->addColumn('deleted', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '删除标识',
'after' => 'client_ip',
])
->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(['user_id'], [
'name' => 'user_id',
'unique' => false,
])
->create();
$this->table('kg_user_token', [
'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('token', 'string', [
'null' => false,
'default' => '',
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '身份令牌',
'after' => 'user_id',
])
->addColumn('client_type', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '终端类型',
'after' => 'token',
])
->addColumn('client_ip', 'string', [
'null' => false,
'default' => '',
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '终端IP',
'after' => 'client_type',
])
->addColumn('deleted', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '删除标识',
'after' => 'client_ip',
])
->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(['user_id'], [
'name' => 'user_id',
'unique' => false,
])
->create();
}
}