1 redis单据锁
<?php/*** 关键点:set()设置nx模式:单据key已存在时不再添加,返回false;* 生成唯一的value:保证在处理业务期间到提交事务,都是同一次单据();* 监听:其他连接源修改了该key(可能是并发,然后后者会提交,前者会丢弃),被修改了则丢弃事务,没有则提交。* Class LockService*/class LockService{// 单据锁redis key模板(起始就是带前缀的键)const REDIS_LOCK_KEY_TEMPLATE = 'order_lock_%s';const REDIS_LOCK_DEFAULT_EXPIRE_TIME = 86400;const REDIS_CONFIG_HOST = '127.0.0.1';const REDIS_CONFIG_PORT = 6379;const REDIS_PASSWORD = 'root';//用于生成唯一的值(可理解为处理的某一类业务单据;用于处理多种类别时,应该区分开来。)const REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id';public static function getRedisConn($strIp = self::REDIS_CONFIG_HOST, $intPort = self::REDIS_CONFIG_PORT, $password = self::REDIS_PASSWORD){$objRedis = new \Redis();$objRedis->connect($strIp, $intPort);// 我竟然不需要密码,本地的redis是配了密码的// $objRedis->auth('root');return $objRedis;}public static function generateUniqueLockId(){// 让该key上的值自增1;如果key不存在,如果key不存在,则先设定0,再自增1。所以,它是简易的获取唯一值return self::getRedisConn()->incr(self::REDIS_LOCK_UNIQUE_ID_KEY);}public static function addLock($intOrderId, $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME){// 参数错误,加锁不成功if (empty($intOrderId || $intExpireTime <= 0)) {return false;}// 获取连接$objRedis = self::getRedisConn();//生成唯一锁ID,解锁需持有此ID$intUniqueLockId = self::generateUniqueLockId();//根据模板,结合单据ID,生成唯一Redis key(一般来说,单据ID在业务中系统中唯一的)$strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);//加锁(通过Redis setnx指令实现,从Redis 2.6.12开始,通过set指令可选参数也可以实现setnx,同时可原子化地设置超时时间)// 第三个参数数值时,为过期时间;数组时,可以设置 赋值模式,过期时间;当前是不存在时才新建key和value$bolRes = $objRedis->set($strKey, $intUniqueLockId, ['nx', 'ex'=>$intExpireTime]);//加锁成功返回锁ID,加锁失败返回falsereturn $bolRes ? $intUniqueLockId : $bolRes;}public static function releaseLock($intOrderId, $intLockId){//参数校验if (empty($intOrderId) || empty($intLockId)) {return false;}//获取Redis连接$objRedisConn = self::getRedisConn();//生成Redis key$strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);//监听Redis key防止在【比对lock id】与【解锁事务执行过程中】被修改或删除,提交事务后会自动取消监控,其他情况需手动解除监控// 且监控中的key,在事务执行期间,被其他connect修改了,则事务提交时会中断,不执行事务中的命令。$objRedisConn->watch($strKey);if ($intLockId == $objRedisConn->get($strKey)) {// multi开启事务(有序的执行命令的,Pipleline是无序的),exec提交事务,discard放弃事务$objRedisConn->multi()->del($strKey)->exec();return true;}// 解除监控$objRedisConn->unwatch();return false;}}//test$res1 = LockService::addLock('666666');var_dump($res1);//返回lock id,加锁成功$res2 = LockService::addLock('666666');var_dump($res2);//false,加锁失败$res3 = LockService::releaseLock('666666', $res1);var_dump($res3);//true,解锁成功$res4 = LockService::releaseLock('666666', $res1);var_dump($res4);//false,解锁失败
- 可以在会产生多次回调到业务中考虑此方法。
2 JWT的生成与验证
<?php
class JWTHepler {
private $header = [
'typ' => 'JWT', // 标识整个token是一个jwt字符串
'alg' => 'HS256', // 代表签名和摘要算法
];
private $payload = [
'sub' => '',
'name' => '',
'admin' => '',
];
private $salt = "";
public function setSalt(string $salt)
{
if(!isset($salt)) exit('please enter a salt');
$this->salt = $salt;
return $this;
}
public function setHeader(array $header)
{
if(!isset($header['typ'])) exit('please set the alg of header');
$this->header = $header;
return $this;
}
public function setPayload(array $payload)
{
$this->payload = $payload;
return $this;
}
public function encript()
{
$base64Header = base64_encode(json_encode($this->header));
$base64Payload = base64_encode(json_encode($this->payload));
$secrity = "";
switch(($this->header)['alg']) {
case 'HS256':
$secrity = hash_hmac('sha256', $base64Header . '.' . $base64Payload, $this->salt);
echo $secrity . "\n";
break;
default: exit('there is not a func to create the secrity');break;
}
$result = $base64Header . '.' . $base64Payload . '.' . $secrity;
return $result;
}
public function checkToken($token)
{
list($base64Header, $base64Payload, $secrity) = explode('.', $token);
$header = json_decode(base64_decode($base64Header), true);
$payload = json_decode(base64_decode($base64Payload), true);
$tmpSecrity = "";
switch($header['alg']) {
case 'HS256':
$tmpSecrity = hash_hmac('sha256', $base64Header . '.' . $base64Payload, $this->salt);
break;
default: exit('there is not a func to create the secrity');break;
}
if($tmpSecrity == $secrity) {
echo "true\n";
return [true, $header, $payload];
} else {
echo "false\n";
return [false, [], []];
}
}
public static function createSalt()
{
$str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
$salt = substr(str_shuffle($str), 26, rand(2, count_chars($str)));
return $salt;
}
}
$jwt = new JWTHepler();
$header = [
'typ' => 'JWT',
'alg' => 'HS256',
];
$data = [
'sub' => uniqid("ABC"),
'name' => 'test',
'admin' => true,
];
// 生成token
$salt = JWTHepler::createSalt();
$token = $jwt->setHeader($header)->setPayload($data)->setSalt($salt)->encript();
echo $token . "\n";
// 验证token
$result = $jwt->checkToken($token);
var_dump($result);
- 使用JWT可以解决分布式下,多个节点如何共享用户信息的问题。
- 避免了对session、cookie的依赖。
- 使用的时候,将JWT放在header里面:每次请求时,都携带它,后端验证正确后再执行后续操作。
- 此处仅是一个简单的例子帮助理解,更好的操作方式可以下载专业的第三方依赖库。
- token可以很容易的跨服务器,只要不同服务器实现相同解密算法即可;而cookie/.session是存在于某一台服务器上。
- 参考各个主流网站的token过期时间,一般不超过1h.
token过期,就要重新获取。那么重新获取有两种方式,一是重复第一次获取token的过程(比如登录,扫描授权等),这样做的缺点是用户体验不好。第二种方法,主动去刷新token.主动刷新token的凭证是refresh_token,也是加密字符串,并且和token是相关联的。相比获取各种资源的token,refresh_token的作用仅仅是获取新的token,因此其作用和安全性要求都大为降低,所以其过期时间也可以设置得长一些。
3 桶算法限流
```php <?php class Bucket { static $limits = []; // url => 次/分钟 static $times = []; // url => [窗口启动时间, 次/分钟]
public static function setUrlLimit($url, $limit) {
if(empty($url) || empty($limit)) { return false; } self::$limits[$url] = $limit; return true;}
public static function removeUrlLimit($url) {
unset($url); return true;}
public static function checkRequest($url) {
$limit = self::$limits[$url]; if (!$limit) return true; // 0次或无设定,为无限制 $nowTime= time(); $times =self::$times[$url]; if($times) { $startTime = $times['start_time']; $count = $times['count']; $interval = ($nowTime - $startTime) / 60; if($interval <= 1 && $count > 0) { self::$times[$url] = [ 'start_time' => $nowTime, 'count' => $count - 1, ]; return true; } else if($interval <= 1 && $count <= 0) { return false; } } // 还没记录过或重新补充令牌 self::$times[$url] = [ 'start_time' => $nowTime, 'count' => $limit - 1, ]; return true;} }
Bucket::setUrlLimit(‘test/aa’, 5); Bucket::setUrlLimit(‘aa/bb’, 10);
for($i = 0; $i < 11; $i++) { $result1 = Bucket::checkRequest(“test/aa”); if($result1) { echo date(‘Y-m-d H:i:s’) . “|test/aa:do it” . “\n”; } else { echo date(‘Y-m-d H:i:s’) . “|test/aa:do not it” . “\n”; }
$result2 = Bucket::checkRequest("aa/bb");
if($result2) {
echo date('Y-m-d H:i:s') . "|aa/bb:do it" . "\n";
} else {
echo date('Y-m-d H:i:s') . "|aa/bb:do not it" . "\n";
}
}
sleep(61); echo “\n第二阶段\n”; for($i = 0; $i < 11; $i++) { $result1 = Bucket::checkRequest(“test/aa”); if($result1) { echo date(‘Y-m-d H:i:s’) . “|test/aa:do it” . “\n”; } else { echo date(‘Y-m-d H:i:s’) . “|test/aa:do not it” . “\n”; }
$result2 = Bucket::checkRequest("aa/bb");
if($result2) {
echo date('Y-m-d H:i:s') . "|aa/bb:do it" . "\n";
} else {
echo date('Y-m-d H:i:s') . "|aa/bb:do not it" . "\n";
}
} ```
