在接口开发中免不了参数校验,利用tp中间件可以更好的解耦参数校验。平时开发中初级写法如下:
<?php
/**
*初级写法
**/
namespace app\index\user;
public function register()
{
$request = request();
$params = $request->param();
if (empty($params['user_name'])) {
return json(['code' => -1, 'msg' => '请输入账号']);
}
if (!preg_match('/^[a-z0-9]$/i', $params['user_name'])) {
return json(['code' => -1, 'msg' => '账号只能是英文和数字']);
}
if (empty($params['password'])) {
return json(['code' => -1, 'msg' => '请输入密码']);
}
//todo ...各种判断通过后 再进行实际的业务逻辑
}
这种写法不灵活,万一修改为xml返回,则需要劳师动众,下面利用TP验证器来简化代码
namespace app\test\validate;
use think\Validate;
class User extends Validate
protected $rule = [
'user_name' => 'require|alphaNum',
'password' => 'require|checkPwd',
];
protected $message = [
'user_name.require' => '请输入账号',
'user_name.alphaNum' => '账号只能是数字和字母',
'password.require' => '请输入密码',
];
protected $scene = [
'login' => ['user_name', 'password'],
'register' => ['user_name', 'password'],
];
/**
*自定义校验方法,不能为private
*/
protected function checkPwd($value)
{
var_dump($value);
return true;
}
<?php
/**
*中级写法
**/
namespace app\test\user;
public function register()
{
$request = request();
$params = $request->param();
$validate = new \app\test\validate\User;
if (!$validate->scene('register')->check($params)) {
return json(['code' => -1, 'msg' => $validate->getError()]);
}
//todo ...再进行实际的业务逻辑
}
这样在controller层就减少一大片各种判断,当接口越来越多的时候,代码可能会如下
<?php
namespace app\test\user;
public function register()
{
$request = request();
$params = $request->param();
$validate = new \app\test\validate\User;
if (!$validate->scene('register')->check($params)) {
return json(['code' => -1, 'msg' => $validate->getError()]);
}
//todo ...再进行实际的业务逻辑
}
public function login()
{
$request = request();
$params = $request->param();
$validate = new \app\test\validate\User;
if (!$validate->scene('login')->check($params)) {
return json(['code' => -1, 'msg' => $validate->getError()]);
}
//todo ...再进行实际的业务逻辑
}
//还有无数个接口
这样看起来不是很优雅,要是有100个接口岂不每个方法面前都是类似的代码。利用TP中间件能够在执行每个方法之前都自动判断参数和规则
//首先我们在 application/test/middleware 目录中创建 中间件文件 Validate.php
<?php
namespace app\test\middleware;
use think\Controller;
class Validate extends Controller
{
/**
* 默认返回资源类型
* @var \think\Request $request
* @var mixed $next
* @var string $name
* @throws \Exception
* @return mixed
*/
public function handle($request, \Closure $next, $name)
{
//获取当前参数
$params = $request->param();
//获取访问模块
$module = $request->module();
//获取访问控制器
$controller = ucfirst($request->controller());
//获取操作名,用于验证场景scene
$scene = $request->action();
$validate = "app\\" . $module . "\\validate\\" . $controller;
//仅当验证器存在时 进行校验
if (class_exists($validate)) {
$v = $this->app->validate($validate);
if ($v->hasScene($scene)) {
//仅当存在验证场景才校验
$result = $this->validate($params, $validate . '.' . $scene);
if (true !== $result) {
//校验不通过则抛出异常,留后面自定义异常获取
throw new ValidateException($result);
}
}
}
return $next($request);
}
}
然后在application\test 目录下 新建middleware.php
<?php
// 中间件扩展定义文件
return [
'validate' => app\test\middleware\Validate::class
];
最终,在控制器中的代码我们可以简化成如下:
<?php
/**
*高级写法
**/
namespace app\test\user;
public function register()
{
$request = request();
$params = $request->param();
//todo ...此时校验已经通过,可直接进行实际的业务逻辑
}
public function login()
{
$request = request();
$params = $request->param();
//todo ...此时校验已经通过,可直接进行实际的业务逻辑
}
//更多其他接口
在上述校验过程中校验失败会抛出异常,此时我们采用自定义异常处理
管理命令:
如:docker container ls 显示所有容器
普通命令:
如:docker images 显示所有镜像
1、下载镜像
$ docker pull centos
2、查看已经下载的镜像
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 49f7960eb7e4 6 weeks ago 200MB
hello-world latest f2a91732366c 8 months ago 1.85kB
3、运行一个镜像并生成容器 //运行一个centos镜像,并执行/bin/bash命令
$ docker run centos /bin/bash
输入命令docker ps或者docker container ls查看运行中的容器,发现列表为空,原因是容器在执行完成命令后会自动退出,下面介绍让让容器停留在后台的方法
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
使用命令docker ps -a或者docker container ls -a 查看所用容器,并显示了容器的状态
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5525373371f3 centos "/bin/bash" 7 minutes ago Exited (0) 7 minutes ago nifty_rosalind
1、启动一个容器并后台运行
docker run命令 通过增加-i -t参数可以让容器并进入容器
docker run -i -t centos /bin/bash
按 Ctrl + P + Q 退出容器,再用 docker ps 进行查看
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
61b041e4e063 centos "/bin/bash" 3 minutes ago Up 3 minutes nervous_saha
状态显示仍在运行中
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
36afde543eb5 mysql:5.7 "docker-entrypoint.s…" About an hour ago Up About an hour 0.0.0.0:3306->3306/tcp mymysql
$ docker exec -it 36afde543eb5 /bin/bash
-i 保持STDIN打开 -t 分配一个虚拟TTY窗口
1、首先到https://hub.docker.com/注册一个账号,保存下用户名密码 2.控制台登陆dockerhub账户
$ docker login
输入刚注册的用户名密码
2、查看镜像
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest f06a5773f01e 2 days ago 83.4MB
3、选择需要上传的镜像,重命名为指定的格式
$ docker tag redis username/myredis:v1
username:为自己注册的用户名
myredis:为自己为镜像取的名字
v1:为任意设置的版本号
4、完成上述操作后,即可提交镜像到自己的仓库
docker push username/myredis:v1
//将宿主机的81端口映射到容器的80端口 //将宿主机的/develop/data卷,映射到容器的/data卷
$ docker run -i -t -p 81:80 -v /develop/data:/data centos /bin/bash
-p:映射端口号 -v:磁盘目录映射
运行中的容器无法映射新的端口号,也无法更改端口号映射,但可以通过两种方法解决
1、iptable转发端口
//查看容器ip
$ docker inspect 36afde543eb5 | grep IPAddress
"IPAddress": "172.17.0.2"
//将主机的8081端口映射到宿主机的8080端口
$ iptables -t nat -A DOCKER -p tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:8080
2、先提交容器为镜像,再运行这个容器,同时指定新的端口映射
//提交容器为镜像
$ docker commit 9995ffa15f46 mycentos:0.1
//停止旧的容器
$ docker stop 9995ffa15f46
//重新从旧的镜像启动容器
$ docker run -i -t -p 8081:8080 mycentos:0.1
从DockerFile创建镜像
$ docker build -t myimage:v1 .
-t :指定镜像名称和标签,格式为'name:tag' .: 最后一个点代表当前目录,也可以换成其它的路径
最近有这样的需求:
在知道 Redis 从2.8.0版本后,推出 Keyspace Notifications 特性后,对Key过期事件的处理,有了可能。
这里需要配置 notify-keyspace-events 的参数为 “Ex”。x 代表了过期事件。
notify-keyspace-events "Ex"
保存配置后,重启Redis服务,使配置生效。
[root@chokingwin etc]# service redis-server restart /usr/local/redis/etc/redis.conf
Stopping redis-server: [ OK ]
Starting redis-server: [ OK ]
开启一个终端,redis-cli 进入 redis 。开始订阅所有操作,等待接收消息。
127.0.0.1:6379> psubscribe __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
再开启一个终端,redis-cli 进入 redis,新增一个 10秒过期的键 name
127.0.0.1:6379> setex name 10 chokingwin
OK
10秒过期时间到时,另外一边执行了阻塞订阅操作后的终端,有如下信息输出
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "name"
顺利获取到过期键 name,说明对过期Key信息的订阅是成功的。
phpredis 是 php 的一个扩展,使代码内可像客户端操作redis那样操作方便。 下面是对 phpredis 封装的一个类 redis.class.php
class MyRedis{
private $redis;
/**
* 构造函数
*
* @param string $host 主机号
* @param int $port 端口号
*/
public function __construct($host='127.0.0.1',$port=6379){
$this->redis = new redis();
$this->redis->connect($host,$port);
}
public function expire($key=null,$time=0){
return $this->redis->expire($key,$time);
}
public function psubscribe($patterns=array(),$callback){
$this->redis->psubscribe($patterns,$callback);
}
}// End Class
下面对上面的类进行一些实例化,以期达到过期事件的订阅。脚本 testRedisNotify.php 如下
<?
require_once './redis.class.php';
$redis = new MyRedis();
$redis->psubscribe(array('__keyevent@0__:expired'),'psCallback');
// 回调函数,这里写处理逻辑
function psCallback($redis, $pattern, $chan, $msg){
echo "Pattern: $pattern\n";
echo "Channel: $chan\n";
echo "Payload: $msg\n\n";
}
说明:psCallback 函数为订阅事件后的回调函数。$redis, $pattern, $chan, $msg 四个参数为回调时返回的参数。 详细说明见下面 Redis 官方文档对 psubscribe 的说明。
/**
* Subscribe to channels by pattern
*
* @param array $patterns The number of elements removed from the set.
* @param stringarray $callback Either a string or an array with an object and method.
* The callback will get four arguments ($redis, $pattern, $channel, $message)
* @link http://redis.io/commands/psubscribe
* @example
* <pre>
* function psubscribe($redis, $pattern, $chan, $msg) {
* echo "Pattern: $pattern\n";
* echo "Channel: $chan\n";
* echo "Payload: $msg\n";
* }
* </pre>
*/
public function psubscribe( $patterns, $callback ) {}
因为订阅事件启动后是阻塞执行的,所以我们尝试在终端执行 testRedisNotify.php 这个脚本。
[root@chokingwin etc]# php testRedisNotify.php
新开一个终端,在 redis 里 新增一个 10秒过期 的键 chokingwin
127.0.0.1:6379> setex name 10 chokingwin
OK
10秒过期时间到时,另外一边执行了脚本被阻塞的终端,有如下信息输出
Pattern: __keyevent@0__:expired
Channel: __keyevent@0__:expired
Payload: name
说明利用 phpredis 对过期事件的订阅,是成功的。
redis的客户端读取超时原因导致。 那么对 redis客户端进行一些参数设置,使读取超时参数 为 -1,表示不超时。 在 redis.class.php 里新增一个函数
public function setOption(){
$this->redis->setOption(Redis::OPT_READ_TIMEOUT,-1);
}
然后在脚本里调用这个 setOption() 函数。
<?
require_once './redis.class.php';
$redis = new MyRedis();
$redis->setOption();
$redis->psubscribe(array('__keyevent@0__:expired'),'psCallback');
// 回调函数,这里写处理逻辑
function psCallback($redis, $pattern, $chan, $msg){
echo "Pattern: $pattern\n";
echo "Channel: $chan\n";
echo "Payload: $msg\n\n";
}
做到这一步,利用 phpredis 扩展,成功在代码里实现对过期 Key 的监听,并在 psCallback()里进行回调处理。 开头提出的两个需求已经实现。 似乎一切都很顺利。 可是这里有个问题:redis 在执行完订阅操作后,终端进入阻塞状态,需要一直挂在那。且此订阅脚本需要人为在命令行执行,不符合实际需求。 实际上,我们对过期监听回调的需求,是希望它像守护进程一样,在后台运行,当有过期事件的消息时,触发回调函数。
希望像守护进程一样在后台一样,我是这样实现的。
Linux中有一个nohup命令。功能就是不挂断地运行命令。 同时nohup把脚本程序的所有输出,都放到当前目录的nohup.out文件中,如果文件不可写,则放到<用户主目录>/nohup.out 文件中。那么有了这个命令以后,不管我们终端窗口是否关闭,都能够让我们的php脚本一直运行。 来看操作。 我们新建一个 nohupRedisNotify.php。用户主目录>
vim nohupRedisNotify.php
编写脚本同 testRedisNotify.php 类似。
<?
#! /usr/local/php/bin/php
require_once './redis.class.php';
$redis = new MyRedis();
$redis->setOption();
$redis->psubscribe(array('__keyevent@0__:expired'),'psCallback');
// 回调函数,这里写处理逻辑
function psCallback($redis, $pattern, $chan, $msg){
echo "Pattern: $pattern\n";
echo "Channel: $chan\n";
echo "Payload: $msg\n\n";
}
?>
不过我们在开头,需要申明 php 编译器的路径:#! /usr/local/php/bin/php 。 这是执行 php 脚本所必须的。
然后,nohup 不挂起执行 nohupRedisNotify.php,注意 末尾的 &
[root@chokingwin HiGirl]# nohup ./nohupRedisNotify.php &
[1] 4456
nohup: ignoring input and appending output to `nohup.out'
ps 确认一下脚本是否已在后台运行。查看进程结果如下:
[root@chokingwin HiGirl]# ps
PID TTY TIME CMD
3943 pts/2 00:00:00 bash
4456 pts/2 00:00:00 nohupRedisNotif
4480 pts/2 00:00:00 ps
脚本确实已经在 4456 号进程上跑起来。
最后在查看下nohup.out cat 一下 nohuo.out,看下是否有过期输出。
[root@chokingwin HiGirl]# cat nohup.out
[root@chokingwin HiGirl]#
并没有。我们还是老样子,新增一个10秒过期的的键 name。10秒后,我们再 cat 一次。
[root@chokingwin HiGirl]# cat nohup.out
Pattern: __keyevent@0__:expired
Channel: __keyevent@0__:expired
Payload: name
说明监听过期事件并回调成功。
数据条数
分页查询当天订单信息,并按下单时间倒序
为什么多添加了用户条件,查询反而变慢好多
在不添加任何索引的情况下,我们order by pay_applydate
本文记录工作中遇到的特殊情况,有更优的办法,还望指点
Redis字段类型丰富,介绍PHP如何操作Redis
$redis->set('key', 'val');
//设置有效期为5秒的键值
$redis->setex('key', 5, 'val');
$redis->psetex('key', 5000, 'val'); //设置有效期为5000毫秒(同5秒)的键值
$redis->setnx('key', 'val'); //若键值存在返回false 不存在返回true
$redis->delete('key');//删除键值 可以传入数组 array('key1', 'key2')删除多个键
$redis->getSet('key', 'XK'); //将键key的值设置为XK, 并返回这个键值原来的值val
//批量事务处理
$ret = $redis->multi()
->set('key1', 'val1')
->get('key1')
->setnx('key', 'val2')
->get('key2')
->exec();
// 监控键key 是否被其他客户端修改,如果KEY在调用watch()和exec()之间被修改,exec失败
$redis->watch('key');
//频道订阅
function f($redis, $chan, $msg)
{
switch ($chan) {
case 'chan-1':
echo $msg;
break;
case 'chan-2':
echo $msg;
break;
case 'chan-2':
echo $msg;
break;
}
}
$redis->subscribe(array('chan-1', 'chan-2', 'chan-3'), 'f'); // subscribe to 3 chans
$redis->publish('chan-1', 'hello, world!'); // send message.
$redis->exists('key'); //验证键是否存在,存在返回true
$redis->incr('number'); //键值加1
$redis->incrby('number', -10); //键值加减10
$redis->incrByFloat('number', +/-1.5); //键值加减小数
$redis->decr('number'); // 键值减1
$redis->decrBy('number', 10); // 键值减10
$mget = $redis->mget(array('number', 'key')); // 批量获取键值,返回一个数组
$redis->mset(array('key0' => 'value0', 'key1' => 'value1')); // 批量设置键值
$redis->msetnx(array('key0' => 'value0', 'key1' => 'value1'));
// 批量设置键值,类似将setnx()方法批量操作
$redis->append('key', '-Smudge'); //原键值TK,将值追加到键值后面,键值为TK-Smudge
$redis->getRange('key', 0, 5); // 键值截取从0位置开始到5位置结束
$redis->getRange('key', -6, -1); // 字符串截取从-6(倒数第6位置)开始到-1(倒数第1位置)结束
$redis->setRange('key', 0, 'Smudge');// 键值中替换字符串,0表示从0位置开始,有多少个字符替换多少位置,其中汉字占2个位置
$redis->strlen('key'); //键值长度
$redis->getBit('key');
$redis->setBit('key');
$redis->delete('list-key'); // 删除链表
$redis->lPush('list-key', 'A'); //插入链表头部/左侧,返回链表长度
$redis->rPush('list-key', 'B'); //插入链表尾部/右侧,返回链表长度
$redis->lPushx('list-key', 'C');// 插入链表头部/左侧,链表不存在返回0,存在即插入成功,返回当前链表长度
$redis->rPushx('list-key', 'C');// 插入链表尾部/右侧,链表不存在返回0,存在即插入成功,返回当前链表长度
$redis->lPop('list-key'); //返回LIST顶部(左侧)的VALUE ,后入先出(栈)
$redis->rPop('list-key'); //返回LIST尾部(右侧)的VALUE ,先入先出(队列)
$redis->blPop();
$redis->brPop();
$redis->lSize('list-key');// 如果是链表则返回链表长度,空链表返回0,若不是链表或者不为空,则返回false ,判断非链表 " === false "
$redis->lGet('list-key', -1); // 通过索引获取链表元素 0获取左侧一个 -1获取最后一个
$redis->lSet('list-key', 0, 'X'); //0位置元素替换为 X
$redis->lRange('list-key', 0, 3);//链表截取 从0开始 3位置结束 ,结束位置为-1 获取开始位置之后的全部
$redis->lTrim('list-key', 0, 1); // 截取链表(不可逆) 从0索引开始 1索引结束
$redis->lRem('list-key', 'C', 2); //链表从左开始删除元素2个C
$redis->lInsert('list-key', Redis::BEFORE, 'C', 'X');// 在C元素前面插入X , Redis::AfTER(表示后面插入)链表不存在则插入失败 返回0 若元素不存在返回 - 1
$redis->rpoplpush('list-key', 'list-key2');//从源LIST的最后弹出一个元素,并且把这个元素从目标LIST的顶部(左侧)压入目标LIST。
$redis->brpoplpush();//rpoplpush的阻塞版本,这个版本有第三个参数用于设置阻塞时间,即如果源LIST为空,那么可以阻塞监听timeout的时间,如果有元素了则执行操作。
//set无序集合 不允许出现重复的元素 服务端可以实现多个 集合操作
$redis->sMembers('key'); //获取容器key中所有元素
$redis->sAdd('key', 'TK');// (从左侧插入,最后插入的元素在0位置),集合中已经存在TK 则返回false,不存在添加成功 返回true
$redis->sRem('key', 'TK'); // 移除容器中的TK
$redis->sMove('key', 'key1', 'TK'); //将容易key中的元素TK 移动到容器key1 操作成功返回TRUE
$redis->sIsMember('key', 'TK'); //检查VALUE是否是SET容器中的成员
$redis->sCard('key'); //返回SET容器的成员数
$redis->sPop('key'); //随机返回容器中一个元素,并移除该元素
$redis->sRandMember('key');//随机返回容器中一个元素,不移除该元素
$redis->sInter('key', 'key1');// 返回两个集合的交集 没有交集返回一个空数组,若参数只有一个集合,则返回集合对应的完整的数组
$redis->sInterStore('store', 'key', 'key1'); //将集合key和集合key1的交集 存入容器store 成功返回1
$redis->sUnion('key', 'key1'); //集合key和集合key1的并集 注意即使多个集合有相同元素 只保留一个
$redis->sUnionStore('store', 'key', 'key1');//集合key和集合key1的并集保存在集合store中, 注意即使多个集合有相同元素 只保留一个
$redis->sDiff('key', 'key1', 'key2'); //返回数组,该数组元素是存在于key集合而不存在于集合key1 key2
(stored set) 和 set 一样是字符串的集合,不同的是每个元素都会关联一个 double 类型的 score redis的list类型其实就是一个每个子元素都是string类型的双向链表。
$redis->zAdd('tkey', 1, 'A');// 插入集合tkey中,A元素关联一个分数,插入成功返回1,同时集合元素不可以重复, 如果元素已经存在返回 0
$redis->zRange('tkey', 0, -1); // 获取集合元素,从0位置 到 -1 位置
$redis->zRange('tkey', 0, -1, true);// 获取集合元素,从0位置 到 -1 位置, 返回一个关联数组 带分数array([A] => 0.01,[B] => 0.02,[D] => 0.03) 其中小数来自zAdd方法第二个参数
$redis->zDelete('tkey', 'B'); // 移除集合tkey中元素B 成功返回1 失败返回 0
$redis->zRevRange('tkey', 0, -1); // 获取集合元素,从0位置 到 -1 位置,数组按照score降序处理
$redis->zRevRange('tkey', 0, -1, true);// 获取集合元素,从0位置 到 -1 位置,数组按照score降序处理 返回score关联数组
$redis->zRangeByScore('tkey', 0, 0.2, array('withscores' => true));//获取几个tkey中score在区间[0,0.2]元素 ,score由低到高排序,元素具有相同的score,那么会按照字典顺序排列 , withscores 控制返回关联数组
$redis->zRangeByScore('tkey', 0.1, 0.36, array('withscores' => TRUE, 'limit' => array(0, 1)));//其中limit中 0和1 表示取符合条件集合中 从0位置开始,向后扫描1个 返回关联数组
$redis->zCount('tkey', 2, 10); // 获取tkey中score在区间[2, 10]元素的个数
$redis->zRemRangeByScore('tkey', 1, 3); // 移除tkey中score在区间[1, 3](含边界)的元素
$redis->zRemRangeByRank('tkey', 0, 1);//默认元素score是递增的,移除tkey中元素 从0开始到-1位置结束
$redis->zSize('tkey'); //返回存储在key对应的有序集合中的元素的个数
$redis->zScore('tkey', 'A'); //返回集合tkey中元素A的score值
$redis->zRank('tkey', 'A');// 返回集合tkey中元素A的索引值,z集合中元素按照score从低到高进行排列 ,即最低的score index索引为0
$redis->zIncrBy('tkey', 2.5, 'A'); // 将集合tkey中元素A的score值 加 2.5
$redis->zUnion('union', array('tkey', 'tkey1'));//将集合tkey和集合tkey1元素合并于集合union , 并且新集合中元素不能重复,返回新集合的元素个数, 如果元素A在tkey和tkey1都存在,则合并后的元素A的score相加
$redis->zUnion('ko2', array('k1', 'k2'), array(5, 2));// 集合k1和集合k2并集于k02 ,array(5,1)中元素的个数与子集合对应,然后 5 对应k1,k1每个元素score都要乘以5 ,同理1对应k2,k2每个元素score乘以1,然后元素按照递增排序,默认相同的元素score(SUM)相加
$redis->zUnion('ko2', array('k1', 'k2'), array(10, 2), 'MAX');// 各个子集乘以因子之后,元素按照递增排序,相同的元素的score取最大值(MAX),也可以设置MIN 取最小值
$redis->zInter('ko1', array('k1', 'k2'));// 集合k1和集合k2取交集于k01 ,且按照score值递增排序,如果集合元素相同,则新集合中的元素的score值相加
$redis->zInter('ko1', array('k1', 'k2'), array(5, 1));//集合k1和集合k2取交集于k01 ,array(5,1)中元素的个数与子集合对应,然后 5 对应k1,k1每个元素score都要乘以5 ,同理1对应k2,k2每个元素score乘以1,然后元素score按照递增排序,默认相同的元素score(SUM)相加
$redis->zInter('ko1', array('k1', 'k2'), array(5, 1), 'MAX');// 各个子集乘以因子之后,元素score按照递增排序,相同的元素score取最大值(MAX)也可以设置MIN 取最小值
//redis hash是一个string类型的field和value的映射表 . 它的添加,删除操作都是O(1)(平均) . hash特别适合用于存储对象。
$redis->hSet('h', 'name', 'TK'); // 在h表中 添加name字段 value为TK
$redis->hSetNx('h', 'name', 'TK');// 在h表中 添加name字段 value为TK 如果字段name的value存在返回false 否则返回 true
$redis->hGet('h', 'name'); // 获取h表中name字段value
$redis->hLen('h'); // 获取h表长度即字段的个数
$redis->hDel('h', 'email'); // 删除h表中email 字段
$redis->hKeys('h'); // 获取h表中所有字段
$redis->hVals('h'); // 获取h表中所有字段value
$redis->hGetAll('h'); // 获取h表中所有字段和value 返回一个关联数组(字段为键值)
$redis->hExists('h', 'email'); //判断email 字段是否存在与表h 不存在返回false
$redis->hSet('h', 'age', 28);
$redis->hIncrBy('h', 'age', -2);// 设置h表中age字段value加(-2) 如果value是个非数值 则返回false 否则,返回操作后的value
$redis->hIncrByFloat('h', 'age', -0.33);// 设置h表中age字段value加(-2.6) 如果value是个非数值 则返回false 否则返回操作后的value(小数点保留15位)
$redis->hMset('h', array('score' => '80', 'salary' => 2000)); // 表h 批量设置字段和value全选复制放进笔记
$redis->hMGet('h', array('score', 'salary')); // 表h 批量获取字段的value
Request:不同的http方法表达不同的行为:
Response : 根据动作响应不同的状态码(status code)
GET
, PUT
和PATCH
请求成功时,要返回对应的数据,及状态码200
,即SUCCESSPOST
创建数据成功时,要返回创建的数据,及状态码201
,即CREATEDDELETE
删除数据成功时,不返回数据,状态码要返回204
,即NO CONTENTGET
不到数据时,状态码要返回404
,即NOT FOUND400
,即BAD REQUEST401
,即NOT AUTHORIZED403
,即FORBIDDEN规范的API应该包含版本信息,在RESTful API中,最简单的包含版本的方法是将版本信息放到url中,如:
/api/v1/posts/
/api/v1/drafts/
/api/v2/posts/
/api/v2/drafts/
另一种优雅的做法是,使用HTTP header中的
accept
来传递版本信息,这也是GitHub API 采取的策略。
RESTful API 中的url是指向资源的,而不是描述行为的,因此设计API时,应使用名词而非动词来描述语义,否则会引起混淆和语义不清。即:
# Bad APIs
/api/getArticle/1/
/api/updateArticle/1/
/api/deleteArticle/1/
上面四个url都是指向同一个资源的,虽然一个资源允许多个url指向它,但不同的url应该表达不同的语义,上面的API可以优化为:
# Good APIs
/api/Article/1/
article 资源的获取、更新和删除分别通过
GET
,PUT
和DELETE
方法请求API即可。试想,如果url以动词来描述,用PUT
方法请求/api/deleteArticle/1/
会感觉多么不舒服
(1)Url是区分大小写的,这点经常被忽略,即:
/Posts
/posts
上面这两个url是不同的两个url,可以指向不同的资源
(2)Back forward Slash (/
)
目前比较流行的API设计方案,通常建议url以/
作为结尾,如果API GET
请求中,url不以/
结尾,则重定向到以/
结尾的API上去(这点现在的web框架基本都支持),因为有没有 /
,也是两个url,即:
/posts/
/posts
这也是两个不同的url,可以对应不同的行为和资源
(3)连接符 -
和 下划线 _
RESTful API 应具备良好的可读性,当url中某一个片段(segment)由多个单词组成时,建议使用 -
来隔断单词,而不是使用 _
,即:
# Good
/api/featured-post/
# Bad
/api/featured_post/
这主要是因为,浏览器中超链接显示的默认效果是,文字并附带下划线,如果API以_
隔断单词,二者会重叠,影响可读性。
文章参考RESTful API 编写指南 ,挑选出常见,并对自己有帮助的部分