├── .DS_Store ├── README.md ├── arithmetic.md ├── array.md ├── designPatterns.md ├── grammar.md ├── high-php.md ├── http.md ├── images ├── .DS_Store ├── 302.png ├── 403.png ├── 404.png ├── auto-load.png ├── iterator.png ├── iterator2.png ├── nginx-fz.png ├── nginx_gzip1.png ├── nginx_gzip2.2.png ├── nginx_gzip2.png └── ok.png ├── jwt_diy ├── Core │ ├── Common.php │ ├── Controller.php │ └── RedisService.php ├── config.php ├── login.php └── user.php ├── miaosha.md ├── mysql.md ├── nginx.md ├── oop.md ├── redis.md ├── senior.md └── untitled /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### phper技术栈超全整理 2 | - php 3 | - 基础语法 4 | * [运算符](grammar.md#运算符) 5 | * [数组指针](grammar.md#数组指针) 6 | * 文件操作管理 7 | * 图片操作 8 | * 字符串操作 9 | - [面向对象](oop.md) 10 | - 魔术方法 11 | - [对象引用](oop.md#对象引用) 12 | - [访问控制](oop.md#访问控制private) 13 | - [访问控制之继承](oop.md#访问控制之继承) 14 | - [对象遍历](oop.md#对象遍历) 15 | - [设计模式](designPatterns.md) 16 | - [单例模式](designPatterns.md#单例模式) 17 | - [简单工厂模式](designPatterns.md#简单工厂模式) 18 | - [观察者模式](designPatterns.md#观察者模式) 19 | - [建造者模式](designPatterns.md#建造者模式) 20 | - [策略模式](designPatterns.md#策略模式) 21 | - [责任链模式](designPatterns.md#责任链模式) 22 | - [对象映射模式](designPatterns.md#对象映射模式) 23 | - [队列消息模式](designPatterns.md#队列消息模式) 24 | - [迭代器模式](designPatterns.md#迭代器模式) 25 | - [注册树模式](designPatterns.md#注册树模式) 26 | - php高级 27 | * php缓冲区 28 | * [php自动加载](high-php.md#php自动加载) 29 | * [php反射](high-php.md#php反射) 30 | * PHP中间件 31 | * [php接口数据安全解决方案一](high-php.md#php接口数据安全解决方案一) 32 | * [php接口数据安全解决方案二](high-php.md#php接口数据安全解决方案二) 33 | * php7新特性 34 | * [swoole](https://github.com/lisiqiong/swoole-demo) 35 | * [常用算法](arithmetic.md) 36 | + 排序 37 | + [冒泡排序](arithmetic.md#冒泡排序) 38 | + [快速排序](arithmetic.md#快速排序) 39 | + [选择排序](arithmetic.md#选择排序) 40 | + 查找 41 | + [二分法查找](arithmetic.md#二分法查找) 42 | + [递归](arithmetic.md#递归) 43 | + [顺序查找](arithmetic.md#顺序查找) 44 | + 其它 45 | + [乘法口诀](arithmetic.md#乘法口诀) 46 | + [寻最小的n个数](arithmetic.md#寻最小的n个数) 47 | + [寻相同元素](arithmetic.md#寻相同元素) 48 | + [抽奖](arithmetic.md#抽奖) 49 | + [数组反转](arithmetic.md#数组反转) 50 | + [随机打乱数组](arithmetic.md#随机打乱数组) 51 | + [寻找最小元素](arithmetic.md#寻找最小元素) 52 | - http协议 53 | * [http协议说明](http.md#http协议说明) 54 | * 无状态 55 | * http状态码 56 | * telnet实现get请求 57 | * telnet实现post请求 58 | * telnet实现文件上传 59 | - mysql 60 | + mysql查询缓存 61 | + 存储引擎区别 62 | + 索引优化 63 | + mysql分区分表 64 | + 主从复制 65 | + 读写分离 66 | + 双主热备 67 | + 负载均衡 68 | + 数据库备份和维护 69 | - [nginx](nginx.md) 70 | - [nginx信号量](nginx.md#nginx信号量) 71 | - [location](nginx.md#location) 72 | - [rewrite重写](nginx.md#rewrite重写) 73 | - [nginx防盗链](nginx.md#nginx防盗链) 74 | - [nginx之gzip压缩提升网站速度](nginx.md#nginx之gzip压缩提升网站速度) 75 | - [expires缓存提升网站负载](nginx.md#expires缓存提升网站负载) 76 | - [nginx反向代理](nginx.md#nginx反向代理) 77 | - [nginx实现负载均衡](nginx.md#nginx实现负载均衡) 78 | - redis 79 | - 用户积分排行榜 80 | - 事务 81 | - redis数据开发设计 82 | - 主从复制 83 | - 集群分片 84 | - 数据备份策略 85 | - 常见reds错误分析 86 | - 监控redis的服务状态 87 | - 可视化管理工具 88 | - [redis防止商品超发](redis.md#redis防止商品超发) 89 | - redis持久化 90 | - linux 91 | * 实现无密码登录 92 | * shell脚本实现代码发布 93 | * shell脚本实现服务监控 94 | * shell脚本实现日志定时备份 95 | * shell脚本实现数据库备份 96 | - [高并发大流量web解决思路及方案](senior.md) 97 | + [高并发web架构相关概念](senior.md#高并发web架构相关概念) 98 | + [高并发大流量web整体解决思路](senior.md#高并发大流量web整体解决思路) 99 | + [web服务器负载均衡](senior.md#web服务器负载均衡) 100 | + [cdn加速](senior.md#cdn加速) 101 | + [建立独立的图片服务器](senior.md#建立独立的图片服务器) 102 | + [动态页面静态化](senior.md#动态页面静态化) 103 | + [php并发编程实战](senior.md#php并发编程实战) 104 | + [mysql数据层的优化](senior.md#mysql数据层的优化) 105 | + [mysql缓存层的优化](senior.md#mysql缓存层的优化) 106 | -------------------------------------------------------------------------------- /arithmetic.md: -------------------------------------------------------------------------------- 1 | # php实现几种常见算法 2 | * 排序 3 | * [冒泡排序](#冒泡排序) 4 | * [快速排序](#快速排序) 5 | * [选择排序](#选择排序) 6 | * 查找 7 | * [二分法查找](#二分法查找) 8 | * [递归](#递归) 9 | * [顺序查找](#顺序查找) 10 | * 其它 11 | * [乘法口诀](#乘法口诀) 12 | * [寻最小的n个数](#寻最小的n个数) 13 | * [寻相同元素](#寻相同元素) 14 | * [抽奖](#抽奖) 15 | * [数组反转](#数组反转) 16 | * [随机打乱数组](#随机打乱数组) 17 | * [寻找最小元素](#寻找最小元素) 18 | 19 | ### 公共方法 20 | ``` 21 | //创建数据 22 | function createData($num) { 23 | $arr = []; 24 | for ($i = 0; $i < $num; $i++) { 25 | $arr[$i] = rand($i, 1000); 26 | } 27 | return $arr; 28 | } 29 | 30 | //打印输出数组 31 | function printSortArr($fun, $num = 10) { 32 | $data = createData($num); 33 | $dataString = implode(',', $data); 34 | echo "原数据:{$dataString}" . PHP_EOL; 35 | $arr = $fun($data); 36 | echo "算法[{$fun}]数据打印" . PHP_EOL; 37 | foreach ($arr as $key => $value) { 38 | # code... 39 | echo $value . PHP_EOL; 40 | } 41 | } 42 | 43 | ``` 44 | 45 | ### 冒泡排序 46 | ``` 47 | $arr[$j]) { 62 | $temp = $arr[$j - 1]; 63 | $arr[$j - 1] = $arr[$j]; 64 | $arr[$j] = $temp; 65 | } 66 | } 67 | } 68 | return $arr; 69 | } 70 | 71 | 72 | 73 | printSortArr('mp_sort', 10); 74 | ``` 75 | 76 | ##### 输出结果 77 | ``` 78 | 原数据:504,480,612,677,613,395,506,129,479,605 79 | 算法[mp_sort]数据打印 80 | 129 81 | 395 82 | 479 83 | 480 84 | 504 85 | 506 86 | 605 87 | 612 88 | 613 89 | 677 90 | ``` 91 | 92 | ### 快速排序 93 | 94 | ``` 95 | 1){ 104 | //定义一基准数,再定义一个小于基准数的数组,和一个大于基准数的数组,然后再递归进行快速排序 105 | $k = $arr[0];//定基准数 106 | $x = [];//小于基准数的数组 107 | $y = [];//大于基准数的数组 108 | $size = count($arr); 109 | for($i=1;$i<$size;$i++){ 110 | if($arr[$i]>$k){ 111 | $y[] = $arr[$i]; 112 | }elseif($arr[$i]<=$k){ 113 | $x[] = $arr[$i]; 114 | } 115 | } 116 | $x = quickSort($x); 117 | $y = quickSort($y); 118 | return array_merge($x,array($k),$y); 119 | }else{ 120 | return $arr; 121 | } 122 | } 123 | 124 | $arr = [2,78,3,23,532,13,67]; 125 | print_r(quickSort($arr)); 126 | 127 | ``` 128 | 129 | ### 选择排序 130 | ``` 131 | $arr[$j]) { 144 | $min = $j; 145 | } 146 | } 147 | if ($min != $i) { 148 | $temp = $arr[$min]; 149 | $arr[$min] = $arr[$i]; 150 | $arr[$i] = $temp; 151 | } 152 | } 153 | return $arr; 154 | } 155 | 156 | printSortArr('xz_sort', 10); 157 | 158 | ``` 159 | #### 运行结果 160 | ``` 161 | 原数据:845,435,918,889,232,62,162,617,729,540 162 | 算法[xz_sort]数据打印 163 | 62 164 | 162 165 | 232 166 | 435 167 | 540 168 | 617 169 | 729 170 | 845 171 | 889 172 | 918 173 | ``` 174 | 175 | ### 二分法查找 176 | 177 | ``` 178 | 1,'name'=>'湖北省','pid'=>0,'son'=>''], 235 | ['id'=>2,'name'=>'广东省','pid'=>0,'son'=>''], 236 | ['id'=>3,'name'=>'湖南省','pid'=>0,'son'=>''], 237 | ['id'=>4,'name'=>'武汉市','pid'=>1,'son'=>''], 238 | ['id'=>5,'name'=>'荆州市','pid'=>1,'son'=>''], 239 | ['id'=>6,'name'=>'宜昌市','pid'=>1,'son'=>''], 240 | ['id'=>7,'name'=>'咸宁市','pid'=>1,'son'=>''], 241 | ['id'=>8,'name'=>'仙桃市','pid'=>1,'son'=>''], 242 | ['id'=>9,'name'=>'潜江市','pid'=>1,'son'=>''], 243 | ['id'=>10,'name'=>'深圳市','pid'=>2,'son'=>''], 244 | ['id'=>11,'name'=>'广州市','pid'=>2,'son'=>''], 245 | ['id'=>12,'name'=>'珠海','pid'=>2,'son'=>''], 246 | ['id'=>13,'name'=>'佛山市','pid'=>2,'son'=>''], 247 | ['id'=>14,'name'=>'长沙市','pid'=>3,'son'=>''], 248 | ['id'=>15,'name'=>'岳阳市','pid'=>3,'son'=>''], 249 | ['id'=>16,'name'=>'株洲市','pid'=>3,'son'=>''], 250 | ['id'=>17,'name'=>'衡阳市','pid'=>3,'son'=>''], 251 | ]; 252 | 253 | function recursive($arr,$pid=0){ 254 | $tree = []; 255 | foreach($arr as $k=>$v){ 256 | if($v['pid'] == $pid){ 257 | $v['son'] = recursive($arr,$v['id']); 258 | $tree[] = $v; 259 | } 260 | 261 | } 262 | return $tree; 263 | } 264 | print_r(recursive($areaList)); 265 | ``` 266 | ### 顺序查找 267 | ``` 268 | $v){ 279 | if($v==$findNum){ 280 | echo "查到了,键值为$k"; 281 | $flag = true; 282 | break; 283 | } 284 | } 285 | if($flag==false){ 286 | echo "未找到该数"; 287 | } 288 | 289 | ``` 290 | ### 乘法口诀 291 | ``` 292 | '一','2'=>'二','3'=>'三','4'=>'四','5'=>'五','6'=>'六','7'=>'七','8'=>'八','9'=>'九', 307 | '10'=>'十','12'=>'十二','14'=>'十四','15'=>'十五','16'=>'十六','18'=>'十八','20'=>'二十','21'=>'二十一','24'=>'二十四','25'=>'二十五','27'=>'二十七','28'=>'二十八','30'=>'三十', 308 | '31'=>'三十一', '32'=>'三十二','35'=>'三十五','36'=>'三十六','40'=>'四十','42'=>'四十二','45'=>'四十五','48'=>'四十八','49'=>'四十九','52'=>'五十二','54'=>'五十四','56'=>'五十六', 309 | '63'=>'六十三','64'=>'六十四','72'=>'七十二','81'=>'八十一' 310 | ]; 311 | for($i=1;$i<=9;$i++){ 312 | for($j=1;$j<=$i;$j++){ 313 | $num = $j*$i; 314 | if($j==$i){ 315 | echo $word[$j].$word[$i].'得'.$word[$num]."\n"; 316 | }else{ 317 | echo $word[$j].$word[$i].'得'.$word[$num].' '; 318 | } 319 | unset($num); 320 | } 321 | } 322 | ``` 323 | ### 寻最小的n个数 324 | ``` 325 | $arr[$pos]) { 360 | //echo '$i:'.$i.'--$post:'.$pos.PHP_EOL; 361 | $pos = $i; 362 | } 363 | } 364 | return $pos; 365 | } 366 | $array = [1, 100, 20, 22, 33, 44, 55, 66, 23, 79, 18, 20, 11, 9, 129, 399, 145,88,56,84,12,17]; 367 | //$min_array = get_min_array($array, 10); 368 | //print_r($min_array); 369 | $arr = [130,2,4,100,89,8,99]; 370 | $num = get_max_pos($arr); 371 | echo $num; 372 | print_r($arr[$num]); 373 | 374 | ``` 375 | ### 寻相同元素 376 | ``` 377 | $arr2[$j]){ 393 | $j++; 394 | }else{ 395 | $sameArr[] = $arr1[$i]; 396 | $i++; 397 | $j++; 398 | } 399 | } 400 | if(!empty($sameArr)){ 401 | $sameArr = array_unique($sameArr); 402 | } 403 | return $sameArr; 404 | } 405 | $result = findCommon($arr1,$arr2); 406 | print_r($result); 407 | ``` 408 | ### 抽奖 409 | ``` 410 | 1,'name'=>'特等奖','v'=>1], 413 | ['id'=>2,'name'=>'二等奖','v'=>3], 414 | ['id'=>3,'name'=>'三等奖','v'=>5], 415 | ['id'=>4,'name'=>'四等奖','v'=>20], 416 | ['id'=>5,'name'=>'谢谢参与','v'=>71], 417 | ]; 418 | function draw($arr) 419 | { 420 | $result = []; 421 | //计算总抽奖池的积分数 422 | $sum = []; 423 | foreach($arr as $key=>$value){ 424 | $sum[$key] = $value['v']; 425 | } 426 | $randSum = array_sum($sum); 427 | $randNum = mt_rand(1,$randSum); 428 | error_log('随机数数:'.$randNum); 429 | $count = count($arr); 430 | $s = 0; 431 | $e = 0; 432 | for($i=0;$i<$count;$i++){ 433 | if($i==0){ 434 | $s = 0; 435 | }else{ 436 | $s += $sum[$i-1]; 437 | } 438 | $e += $sum[$i]; 439 | if($randNum>=$s && $randNum<=$e){ 440 | $result = $arr[$i]; 441 | } 442 | } 443 | unset($sum); 444 | return $result; 445 | } 446 | print_r(draw($arr)); 447 | ``` 448 | ### 数组反转 449 | ``` 450 | $arr[$i]){ 548 | $min = $i; 549 | } 550 | } 551 | echo "数组中最小值的索引为:".$min.',最小值为:'.$arr[$min]; 552 | ``` 553 | 554 | 555 | -------------------------------------------------------------------------------- /array.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/array.md -------------------------------------------------------------------------------- /designPatterns.md: -------------------------------------------------------------------------------- 1 | # PHP设计模式 2 | ### 什么是设计模式? 3 | 1. 设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式是为了可以重用代码,让代码更容易被他人理解,保证 4 | 代码的可靠性。 5 | 6 | 2. 设计模式是一种思想,跟编程语言关系不大,尽管不同语言实现设计模式不尽相同,但是思想核心是一致的。他们都要遵循设计模式的六大规则,设计 7 | 模式不一定就是完完整整的按照案例书写的,只要符合设计模式的六大规则就是好的设计模式。 8 | 9 | 3. 设计模式的六大规则 10 | - 单一职责:一个类只负责一项职责。 11 | - 里氏代换:子类可以扩展父类的功能,但不能够改变父类原有的功能。 12 | - 依赖倒置:高层模块不应该依赖底层模块,二者都应该依赖抽象;抽象不应该依赖细节;细节应该依赖抽象。 13 | - 接口隔离:一个类对另一个类的依赖应该建立在最小的接口上。 14 | - 迪米特法则:一个对象应该对其它对象保持最少的了解。 15 | - 开闭原则:一个软件实体的模块或函数,对实体扩展开放 对实体修改关闭。 16 | 17 | --- 18 | 19 | ### 下面用php实现如下的设计模式 20 | + [单例模式](#单例模式) 21 | + [简单工厂模式](#简单工厂模式) 22 | + [观察者模式](#观察者模式) 23 | + [建造者模式](#建造者模式) 24 | + [策略模式](#策略模式) 25 | + [责任链模式](#责任链模式) 26 | + [对象映射模式](#对象映射模式) 27 | + [队列消息模式](#队列消息模式) 28 | + [迭代器模式](#迭代器模式) 29 | + [注册树模式](#注册树模式) 30 | 31 | ## 单例模式 32 | --- 33 | ``` 34 | getInfo(); 51 | echo PHP_EOL; 52 | $db2 = new Mysql(); 53 | var_dump($db2); 54 | echo $db2->getInfo(); 55 | echo PHP_EOL; 56 | 57 | class Db{ 58 | 59 | private static $_instance; 60 | 61 | private function __construct(){ 62 | 63 | } 64 | 65 | //私有化克隆方法 66 | private function __clone(){ 67 | 68 | } 69 | 70 | //只有这个入口可以获取db的实例 71 | //instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例: 72 | public static function getInstance(){ 73 | if(!(self::$_instance instanceof Db) ){ 74 | self::$_instance = new Db(); 75 | } 76 | return self::$_instance; 77 | } 78 | 79 | //获取数据库信息 80 | public function getInfo(){ 81 | return 'the database is mysql'; 82 | } 83 | 84 | } 85 | 86 | $db3 = Db::getInstance(); 87 | echo $db3->getInfo(); 88 | var_dump($db3); 89 | echo PHP_EOL; 90 | $db4 = Db::getInstance(); 91 | echo $db4->getInfo(); 92 | var_dump($db4); 93 | 94 | ``` 95 | #### 运行结果 96 | 97 | ``` 98 | object(Mysql)#1 (0) { 99 | } 100 | the mysql version is 5.7. 101 | object(Mysql)#2 (0) { 102 | } 103 | the mysql version is 5.7. 104 | the database is mysqlobject(Db)#3 (0) { 105 | } 106 | 107 | the database is mysqlobject(Db)#3 (0) { 108 | } 109 | ``` 110 | 111 | #### 总结: 112 | * db1,db2实现化后是两个不同的实例#1,#2 113 | * db3,db4调用的单例的实例化方法,获取的实际上是一个实例#3 114 | * 单例模式需要将构造方法设置为私有,防止外面生成新的实例,需要将clone方法设置为私有防止克隆 115 | * 定义一个变量存储对象实例,如果对象实例是属于类创建的则直接返回,否则重新生成,保持类的实例只有一个存在 116 | 117 | ## 简单工厂模式 118 | --- 119 | ``` 120 | connect(); 209 | 210 | echo PHP_EOL; 211 | 212 | $db = Factory::createFactory('sqlite'); 213 | $db->connect(); 214 | 215 | ``` 216 | #### 运行结果 217 | ``` 218 | mysql connect ok 219 | sqlite connect ok 220 | ``` 221 | 222 | #### 总结 223 | 1. 只需要传递一个参数就可以实例化想要的类 224 | 2. 使用者没有感知,可以统一数据库操作的方法 225 | 3. 方便数据库切换 226 | 227 | 228 | ## 观察者模式 229 | --- 230 | #### 概念 231 | 观察者模式有时候被称为发布订阅模式,或者模型视图模式,在这个模式中,当一个对象生改变的时候,依赖它的对象会全部收到通知,并自动更新。 232 | #### 使用场景 233 | 一件事情发生以后,要执行一连串的更新操作,传统的方法就是就是在事件之后加入直接加入处理逻辑,当更新的逻辑变的多了后,变变得难以维护,这种方式是耦合的,侵入式的,增加新的逻辑需要修改主题代码。 234 | #### 实现方式 235 | 需要实现两个角色,观察者和被观察者对象。当被观察者发生改变的时候观察者观察者就会观察到这样的变化。 236 | 237 | #### 角色分析 238 | 1. 观察者,观察变化的对象,以及观察者接收的通知处理实现。 239 | 2. 被观察者,添加观察者,删除观察者,通知观察者。 240 | 241 | ``` 242 | _obj[] = $observer; 263 | } 264 | 265 | //通知观察者 266 | public function notify($lesson=null,$time=null){ 267 | //遍历所有观察者通知对应的观察者 268 | foreach ($this->_obj as $observer) { 269 | $observer->update($lesson,$time); 270 | } 271 | } 272 | 273 | } 274 | 275 | /** 276 | *因为定义的观察者抽象类不能够直接实例化,所以需要创建一个观察者来继承该类 277 | *@desc 建立真实观察者对象 278 | **/ 279 | class TrueObservable extends Observable 280 | { 281 | private $_lesson='python'; 282 | public function trgger($lesson='',$time='') 283 | { 284 | $this->_lesson = $lesson; 285 | $this->notify($lesson,$time); 286 | } 287 | } 288 | 289 | /*** 290 | *@desc 创建观察者对象老师 291 | **/ 292 | class Obteacher implements Observer 293 | { 294 | public $name; 295 | public function __construct($name){ 296 | $this->name = $name; 297 | } 298 | public function update($lesson = '',$time=''){ 299 | echo "老师".$this->name."您好,您".$time."的".$lesson."即将开始直播,提醒您做好必要的准备工作".PHP_EOL; 300 | } 301 | } 302 | 303 | 304 | /*** 305 | *@desc 创建观察者对象学生 306 | **/ 307 | class Obstudent implements Observer 308 | { 309 | public $name; 310 | public function __construct($name){ 311 | $this->name = $name; 312 | } 313 | public function update($lesson = '',$time=''){ 314 | echo "学生".$this->name."你好,你预约的课程".$lesson."开始时间为".$time."请按时到达,祝你学习愉快".PHP_EOL; 315 | } 316 | } 317 | 318 | $obj = new TrueObservable(); 319 | $obj->addObserver(new Obteacher('张学友')); 320 | $obj->addObserver(new Obstudent('刘德华')); 321 | $obj->trgger('《php之设计模式》','2月19日下午2点'); 322 | ``` 323 | 324 | #### 运行结果 325 | ``` 326 | 老师张学友您好,您2月19日下午2点的《php之设计模式》即将开始直播,提醒您做好必要的准备工作 327 | 学生刘德华你好,你预约的课程《php之设计模式》开始时间为2月19日下午2点请按时到达,祝你学习愉快 328 | ``` 329 | #### 总结 330 | 该例子使用观察者模式,来实现当一个类的两个变量发生改变时,依赖它的对象会全部收到通知,并自动更新为当前的所传递的值的所属信息. 331 | 332 | ## 建造者模式 333 | --- 334 | ### 角色分析 335 | 以汽车为例子,建造者模式应该如下四个角色: 336 | 1. Product:产品角色,可以理解为行业规范,具体到一辆汽车的具有的功能和属性需要具体的建造者去完成,也就是汽车品牌公司去完成。 337 | 2. Builder:抽象的建造者角色,行业规范有了,具体建造者的抽象,将让构造者来具体需要实现的属性和功能抽象化,让各个品牌来实现。 338 | 3. ConcreateBuilder:具体建造者角色,具体的汽车品牌根据抽象的定义来实现汽车的属性和功能同时调用产品的具体构造一辆车。 339 | 4. Director:导演者角色,它是指挥者,指挥具体实例化那个建造者角色来驱动生成一辆真正的自己品牌的车。 340 | --- 341 | ``` 342 | _brand.'的'.$this->_model.$this->_type."靓车,此车目前售价".$this->_price."w元"; 358 | } 359 | 360 | } 361 | 362 | /** 363 | *@desc 抽象的建造者类 364 | **/ 365 | abstract class Builder{ 366 | public $_car;//产品的对象 367 | public $_info=[];//参数信息 368 | public function __construct(array $info){ 369 | $this->_car = new Car(); 370 | $this->_info = $info; 371 | } 372 | 373 | abstract function buildBrand(); 374 | 375 | abstract function buildModel(); 376 | 377 | abstract function buildType(); 378 | 379 | abstract function buildPrice(); 380 | 381 | abstract function getCar(); 382 | 383 | } 384 | 385 | /** 386 | *@desc 具体的宝马车构造 387 | **/ 388 | class BmwCar extends Builder{ 389 | 390 | public function buildBrand(){ 391 | $this->_car->_brand = $this->_info['brand']; 392 | } 393 | 394 | public function buildModel(){ 395 | $this->_car->_model = $this->_info['model']; 396 | } 397 | 398 | public function buildType(){ 399 | $this->_car->_type = $this->_info['type']; 400 | } 401 | 402 | public function buildPrice(){ 403 | $this->_car->_price = $this->_info['price']; 404 | } 405 | 406 | /** 407 | *@desc 获取整个车的对象标示,让指挥者操作创建具体车 408 | **/ 409 | public function getCar(){ 410 | return $this->_car; 411 | } 412 | 413 | } 414 | 415 | 416 | /** 417 | *@desc 具体的奥迪车构造 418 | **/ 419 | class AudiCar extends Builder{ 420 | 421 | public function buildBrand(){ 422 | $this->_car->_brand = $this->_info['brand']; 423 | } 424 | 425 | public function buildModel(){ 426 | $this->_car->_model = $this->_info['model']; 427 | } 428 | 429 | public function buildType(){ 430 | $this->_car->_type = $this->_info['type']; 431 | } 432 | 433 | public function buildPrice(){ 434 | $this->_car->_price = $this->_info['price']; 435 | } 436 | 437 | 438 | /** 439 | *@desc 获取整个车的对象标示,让指挥者操作创建具体车 440 | **/ 441 | public function getCar(){ 442 | return $this->_car; 443 | } 444 | 445 | } 446 | 447 | 448 | /*** 449 | *@desc 创建指挥者,指挥者调用具体的车的构造创建一辆真正的车 450 | **/ 451 | class Director{ 452 | public $_builder;//构造者对戏那个 453 | public function __construct($builder){ 454 | $this->_builder = $builder; 455 | } 456 | 457 | //指挥方法,指挥生成一辆车 458 | public function Contruct(){ 459 | $this->_builder->buildBrand(); 460 | $this->_builder->buildModel(); 461 | $this->_builder->buildType(); 462 | $this->_builder->buildPrice(); 463 | return $this->_builder->getCar(); 464 | } 465 | 466 | } 467 | 468 | //创建一辆宝马车 469 | $info = ['brand'=>'宝马','model'=>'525','type'=>'轿车','price'=>'50']; 470 | $director = new Director(new BmwCar($info)); 471 | echo $director->Contruct()->getCarInfo(); 472 | 473 | echo PHP_EOL; 474 | 475 | 476 | //创建一辆奥迪车 477 | $info = ['brand'=>'奥迪','model'=>'Q5','type'=>'suv','price'=>'51']; 478 | $director = new Director(new AudiCar($info)); 479 | echo $director->Contruct()->getCarInfo(); 480 | ``` 481 | 482 | #### 运行结果 483 | ``` 484 | 恭喜您拥有了一辆,宝马的525轿车靓车,此车目前售价50w元 485 | 恭喜您拥有了一辆,奥迪的Q5suv靓车,此车目前售价51w元 486 | ``` 487 | 488 | ## 策略模式 489 | --- 490 | #### 定义 491 | 在软件编程中可以理解为定义一系列算法,将一个个算法封装起来,也可以理解为一系列的处理方式,根据不同的场景使用对应的算法策略的一种设计模式。 492 | 493 | #### 小故事 494 | 三国时期,刘备要去东吴,诸葛亮担心主公刘备出岔子,特意准备了三个锦囊,让赵云在适当时机打开锦囊,按其中方式处理,这三个锦囊妙计如下: 495 | 1. 第一个锦囊妙计:借孙权之母、周瑜之丈人以助刘备,终于弄假成真,使刘备得续佳偶。 496 | 2. 第二个锦囊妙计:周瑜的真美人计,又被诸葛亮的第二个锦囊计破了,它以荆州危急,借得孙夫人出头,向国太谎说要往江边祭祖,乃得以逃出东吴。尽管周瑜早为防备,孙权派人追捕。 497 | 3. 第三个锦襄妙计:又借得孙夫人之助,喝退拦路之兵。 498 | 499 | #### 角色分析 500 | 1. 抽象策略角色,抽象的处理方式,通常由一个接口或抽象类实现。 501 | 2. 具体策略角色,具体的处理方式或者说是妙计。 502 | 3. 环境角色,也就是条件。 503 | 504 | ``` 505 | _strategy = $strategy; 550 | } 551 | 552 | //执行选择的锦囊妙计 553 | public function skillBag(){ 554 | $this->_strategy->skill(); 555 | } 556 | 557 | } 558 | 559 | $type = 2;//选择第一个锦囊妙计 560 | switch ($type) { 561 | case '1': 562 | $obj = new Control(new oneStrategy()); 563 | break; 564 | case 2: 565 | $obj = new Control(new twoStrategy()); 566 | break; 567 | case 3: 568 | $obj = new Control(new threeStrategy()); 569 | break; 570 | default: 571 | $obj = new Control(new oneStrategy()); 572 | } 573 | $obj->skillBag(); 574 | ``` 575 | 576 | #### 运行结果 577 | ``` 578 | 以荆州危急,借得孙夫人 579 | ``` 580 | 581 | ## 责任链模式 582 | --- 583 | 584 | ## 队列消息模式 585 | --- 586 | #### 在php框架中,启动框架时会去加载php框架生命周期中必须要运行的核心类,我们可以使用if来判断是否加载对应的类的先后,但是这种方式比较不容易维护。在这里我们来实现一个核心加载器,当框架运行的时候来驱动框架整个生命周期的核心类的调用。我们下面介绍使用php的队列消息模式的思想来实现PHP框架核心类加载。 587 | ``` 588 | 0){ 616 | return true; 617 | }else{ 618 | return false; 619 | } 620 | } 621 | 622 | /** 623 | * @desc 向对象末尾加入对象 624 | * @param $array 625 | */ 626 | public static function pushObj($array){ 627 | array_push(self::$_objlist,$array); 628 | } 629 | 630 | /** 631 | * @desc 依次执行需要加载的对象 632 | */ 633 | public static function doObj(){ 634 | //取出数组中的第一组数据 635 | $objArr = array_shift(self::$_objlist); 636 | //获取类名 637 | $className = $objArr[0]; 638 | //获取方法名称 639 | $funcNmae = $objArr[1]; 640 | //获取参数信息 641 | $params = $objArr[2]; 642 | //获取调用类型,是静态直接调用,还是对象调用方法,还是单例模式的方法调用 643 | $type = $objArr[3]; 644 | //使用call_user_func_array方法调用类对应的方法 645 | if($type==self::_STATIC){ 646 | call_user_func_array([$className,$funcNmae],$params); 647 | }elseif ($type==self::_OBJECT){ 648 | $obj = new $className(); 649 | call_user_func_array([$obj,$funcNmae],$params); 650 | }elseif ($type==self::_SIGNLE){ 651 | $signleObj = $className::getInstance(); 652 | call_user_func_array([$signleObj,$funcNmae],$params); 653 | } 654 | } 655 | } 656 | 657 | class A{ 658 | public static function init(){ 659 | echo "A类型调用静态方法init成功
"; 660 | } 661 | } 662 | 663 | class B{ 664 | public function add($name,$age=null){ 665 | echo "B类通过对象方法调用add方法成功,参数name:{$name} , age:{$age}
"; 666 | } 667 | } 668 | 669 | class C{ 670 | private static $_instance; 671 | 672 | private function __construct() 673 | { 674 | } 675 | 676 | private function __clone() 677 | { 678 | // TODO: Implement __clone() method. 679 | } 680 | 681 | /** 682 | * @desc 获取实例化对象的入口 683 | */ 684 | public static function getInstance(){ 685 | if(!(self::$_instance instanceof C)){ 686 | self::$_instance = new C(); 687 | } 688 | return self::$_instance; 689 | } 690 | 691 | public function show(){ 692 | echo " C类单例类调用show方法成功
"; 693 | } 694 | 695 | } 696 | 697 | /** 698 | * 使用unshiftObj,pushObj方法决定类执行的先后顺序,实现类的核心加载 699 | */ 700 | Loader::unshiftObj(['A','init',[],Loader::_STATIC]); 701 | Loader::unshiftObj(['B','add',['思琼哈哈哈',33],Loader::_OBJECT]); 702 | Loader::pushObj(["C","show",[],Loader::_SIGNLE]); 703 | while (Loader::listen()){ 704 | Loader::doObj(); 705 | } 706 | 707 | ``` 708 | #### 运行结果如下 709 | ``` 710 | B类通过对象方法调用add方法成功,参数name:思琼哈哈哈 , age:33 711 | A类型调用静态方法init成功 712 | C类单例类调用show方法成功 713 | ``` 714 | 715 | ## 迭代器模式 716 | 说到迭代器模式我们不得不说foreach,迭代器就是遍历的意思,在php中对对象有两种遍历方式: 717 | - 1.普通遍历只能够遍历public属性 718 | - 2.类实现了迭代器类的遍历 719 | 在php使用foreach遍历对象的时候,会检查这个实例没有实现Iterator接口,如果实现了就会内置使用迭代器的方式遍历对象 720 | *** 721 | ### 迭代器的流程图 722 | ![avatar](./images/iterator.png) 723 | ### php迭代器方法执行流程图 724 | ![avatar](./images/iterator2.png) 725 | ``` 726 | class ArrayIterator implements \Iterator{ 727 | 728 | private $info = ['a','b','c','d']; 729 | private $index; 730 | 731 | //第一步初始化遍历指针 732 | public function rewind() 733 | { 734 | $this->index = 0; 735 | } 736 | 737 | //第二步判断当前元素是否合法 738 | public function valid() 739 | { 740 | return $this->indexinfo); 741 | } 742 | 743 | //第三步获取当前元素值 744 | public function current() 745 | { 746 | return $this->info[$this->index]; 747 | } 748 | 749 | //第四步获取当前元素键 750 | public function key(){ 751 | return $this->index; 752 | } 753 | 754 | //第五步指针下移 755 | public function next() 756 | { 757 | $this->index++; 758 | } 759 | 760 | } 761 | //使用迭代器遍历对象属性 762 | $arrayIter = new ArrayIterator(); 763 | foreach ($arrayIter as $key){ 764 | var_dump($key); 765 | } 766 | //结果会将变量数组遍历出来 767 | ``` 768 | 769 | ## 注册树模式 770 | ### 什么是注册树模式 771 | 注册树模式当然也叫注册器模式,注册器模式是将对象实例注册到一颗全局的对象树上,需要的时候从对象树上采摘的模式设计方法 772 | ### 为什么要采用注册树模式 773 | 单例模式解决的是如何在整个项目中创建唯一对象实例的问题,工厂模式解决的是如何不通过new建立实例对象的方法。注册树模式解决的是不管你是通过单例模式还是工厂模式还是二者结合生成的对象,都统统的“插到”注册树上。在使用对象的时候直接从树上取对象即可,这和我们使用全局变量一样方便实用。 774 | ### 注册树类 775 | ``` 776 | conn = $conn; 826 | } 827 | 828 | /** 829 | * @desc 获取数据库连接对象 830 | * @param $host 831 | * @param $user 832 | * @param $passwd 833 | * @param $dbname 834 | */ 835 | static public function getConn($host,$user,$passwd,$dbname){ 836 | $conn = mysqli_connect($host,$user,$passwd,$dbname); 837 | return $conn; 838 | } 839 | 840 | public function insert($sql) 841 | { 842 | // TODO: Implement insert() method. 843 | } 844 | 845 | public function update($sql) 846 | { 847 | // TODO: Implement update() method. 848 | } 849 | 850 | public function delete($sql) 851 | { 852 | // TODO: Implement delete() method. 853 | } 854 | 855 | /** 856 | * @desc 执行数据库 857 | * @param $sql 858 | */ 859 | public function query($sql){ 860 | return mysqli_query($this->conn,$sql); 861 | } 862 | 863 | /** 864 | * @desc 关闭数据库 865 | */ 866 | public function close(){ 867 | mysqli_close($this->conn); 868 | } 869 | 870 | } 871 | ``` 872 | 873 | ### 运行注册树 874 | ``` 875 | conn("localhost","root","","test"); 882 | $sql = "select * from admin"; 883 | $res = $db->query($sql); 884 | print_r($res); 885 | //运行结果会打印出admin表的数据 886 | ``` 887 | 888 | 889 | 890 | -------------------------------------------------------------------------------- /grammar.md: -------------------------------------------------------------------------------- 1 | # php语法基础整理 2 | + [运算符++](#运算符++) 3 | + [数组指针](#数组指针) 4 | + 文件操作管理 5 | + 图片操作 6 | + 字符串操作 7 | 8 | ## 数组指针 9 | ### 1.介绍几个数组指针的函数 10 | - current() - 返回数组中的当前单元 11 | - end() - 将数组的内部指针指向最后一个单元 12 | - prev() - 将数组的内部指针倒回一位 13 | - reset() - 将数组的内部指针指向第一个单元 14 | - each() - 返回数组中当前的键/值对并将数组指针向前移动一步 15 | 16 | 17 | ``` 18 | '.current($listArr).PHP_EOL; 27 | next($listArr); 28 | echo "第二个元素".key($listArr).'=>'.current($listArr).PHP_EOL; 29 | next($listArr); 30 | echo "第三个元素".key($listArr).'=>'.current($listArr).PHP_EOL; 31 | end($listArr); 32 | echo "最后一个元素".key($listArr).'=>'.current($listArr).PHP_EOL; 33 | prev($listArr);//内部指针倒回一位 34 | echo "倒数第二位".key($listArr).'=>'.current($listArr).PHP_EOL; 35 | reset($listArr); 36 | echo "第一个元素".key($listArr).'=>'.current($listArr).PHP_EOL; 37 | 38 | ``` 39 | 40 | #### 输出结果 41 | ``` 42 | 第一个元素0=>1232 43 | 第二个元素1=>2456 44 | 第三个元素2=>7789 45 | 最后一个元素11=>3214 46 | 倒数第二位10=>2212 47 | 第一个元素0=>1232 48 | ``` 49 | 50 | ### 2.使用each循环数组 51 | ``` 52 | test(12,2,3); 46 | 47 | // //使用反射来代理调用 48 | // //1.获取到类的反射对象 49 | // $index_reflect_class = new ReflectionClass('IndexAction'); 50 | 51 | // //2.通过反射对象得到实例 52 | // $index1 = $index_reflect_class->newInstance(); 53 | // //var_dump($index1); 54 | 55 | // //3.获取反射方法的对象实例 56 | // $show_method = $index_reflect_class->getMethod('showInfo'); 57 | 58 | // //4.通过反射方法调用showinfo 59 | // $show_method->invoke($index1); 60 | 61 | 62 | /* 63 | * php thinkphp控制器调度机制 64 | * 1.indexAction中的方法和访问修饰符是不确定的,如果是public可以执行 65 | * 2.如果存在_before_index方法,并且是public的,执行该方法 66 | * 3.再判断有没有_after_index方法,并且是public的,执行该方法 67 | */ 68 | $index_reflect_class = new ReflectionClass('IndexAction'); 69 | //通过反射取得实例对象 70 | $controller = $index_reflect_class->newInstance(); 71 | //执行index方法 72 | if($index_reflect_class->hasMethod('index')){ 73 | $index_method = $index_reflect_class->getMethod('index'); 74 | if($index_method->isPublic()){ 75 | 76 | //先执行before_index,方法存在并public执行 77 | if($index_reflect_class->hasMethod('_before_index')){ 78 | $before_index = $index_reflect_class->getMethod('_before_index'); 79 | if($before_index->isPublic()){ 80 | // $params = $before_index->getParameters(); 81 | // print_r($params); 82 | //通过反射方法调用类实例对象 83 | $before_index->invoke($controller); 84 | } 85 | } 86 | 87 | //执行index方法 88 | $index_method->invoke($controller); 89 | //后执行after_index,方法存在并public执行 90 | if($index_reflect_class->hasMethod('_after_index')){ 91 | $after_index = $index_reflect_class->getMethod('_after_index'); 92 | if($after_index->isPublic()){ 93 | //通过反射方法调用类实例对象 94 | $after_index->invoke($controller); 95 | } 96 | } 97 | 98 | }else{ 99 | echo "index不是公用方法不不执行".PHP_EOL; 100 | } 101 | }else{ 102 | echo "index 方法不存在不存在".PHP_EOL; 103 | } 104 | 105 | ``` 106 | 107 | 运行结果 108 | ``` 109 | _before_index执行before_index方法 110 | 执行index方法 111 | _after_index执行after_index方法 112 | ``` 113 | 114 | ## php自动加载 115 | --- 116 | #### 下面显示例子的文件目录结构图 117 | ![avatar](./images/auto-load.png) 118 | ## 一、没有使用命名空间的几种实现 119 | #### test/oneClass.php 120 | ``` 121 | class oneClass{ 122 | 123 | public function show(){ 124 | echo "这里是oneClass.php的show方法
"; 125 | } 126 | 127 | } 128 | ``` 129 | 130 | #### test/twoClass.php 131 | ``` 132 | "; 138 | } 139 | 140 | } 141 | ``` 142 | 143 | 下面7种方式都可以实现自动加载,结果都为: 144 | ``` 145 | 这里是oneClass.php的show方法 146 | 这里是twoClass.php的show方法 147 | ``` 148 | 149 | ### 方法一:index.php 使用__autoload()魔术方法实现自动加载 150 | 151 | ``` 152 | show(); 161 | $two = new twoClass(); 162 | $two->show(); 163 | ``` 164 | 165 | 166 | #### 运行结果 167 | ``` 168 | Deprecated: __autoload() is deprecated, use spl_autoload_register() instead in /Users/lidong/Desktop/wwwroot/test/April/autoload1/index.php on line 5 169 | 这里是oneClass.php的show方法 170 | 这里是twoClass.php的show方法 171 | ``` 172 | #### 总结:在PHP7.2以后使用__autoload()会报一个警告,7.2之前这种方式是没提示的.这种方式,是调用一个找不到的类会自动取调用__autoload()方法然后在方法里面执行include引用,实现自动加载。 173 | 174 | ### 方法二:index2.php 使用spl_autoload_register()方法实现自动加载,创建自定义register方法调用 175 | ``` 176 | show(); 186 | $two = new twoClass(); 187 | $two->show(); 188 | ``` 189 | 190 | ### 方法三:index3.php 使用spl_autoload_register()方法,不定义register方法直接使用回调 191 | ``` 192 | show(); 200 | $two = new twoClass(); 201 | $two->show(); 202 | ``` 203 | 204 | #### 方法四:index4.php 使用spl_autoload_register()方法,调用类的register方法实现自动加载 205 | ``` 206 | class autoLoad{ 207 | public static function register($classname){ 208 | include "./test/{$classname}.php"; 209 | } 210 | } 211 | 212 | spl_autoload_register(["autoLoad","register"]); 213 | 214 | $one = new oneClass(); 215 | $one->show(); 216 | $two = new twoClass(); 217 | $two->show(); 218 | ``` 219 | 220 | ## 二、使用命名空间的几种实现 221 | #### test2/oneClass.php 222 | ``` 223 | "; 230 | } 231 | 232 | } 233 | ``` 234 | 235 | #### test2/twoClass.php 236 | ``` 237 | "; 243 | } 244 | 245 | } 246 | ``` 247 | 248 | #### 方法五:index5.php,使用spl_autoload_register(),调用加载类的register方法,转化传递过来的命名空间实现自动加载 249 | ``` 250 | show(); 263 | $two = new auto\test2\twoClass(); 264 | $two->show(); 265 | ``` 266 | 267 | #### 方法六:index6.php 跟方法五类似,区别是use方法调用类实例化时可以直接使用类名,实现自动加载 268 | ``` 269 | show(); 285 | $two = new twoClass(); 286 | $two->show(); 287 | ``` 288 | 289 | #### 方法七:index7.php 与方法五和六思路一致,只不过加载类放在外部不是引用在统一文件,要点就是命名空间定义的类,要使用也要先include,实现自动加载 290 | ###### autoLoad.php 291 | ``` 292 | show(); 314 | $two = new twoClass(); 315 | $two->show(); 316 | ``` 317 | 318 | 319 | ### 总结:所有的自动加载思想都是调用一个没引用的类库后PHP会自动调用自动执行的一个方法,这个方法有可能是类的方法也有可能是普通方法,但不管怎么样都最终使用include执行文件包含,只不过命名空间需要转化下获取类名。另外值得注意的是,如果是一个php的框架自动加载实现也基本一致,只不过他会根据不同文件夹下面的定义判断后include来实现不同文件夹下文件的引用,来实现整个框架的自动加载。 320 | 321 | ## php接口数据安全解决方案一 322 | *** 323 | - [前言](#前言) 324 | - [目录介绍](#目录介绍) 325 | - [登录鉴权图](#登录鉴权图) 326 | - [接口请求安全性校验整体流程图](#接口请求安全性校验整体流程图) 327 | - [代码展示](#代码展示) 328 | - [演示](#演示) 329 | - [后记](#后记) 330 | 331 | ## 前言 332 | 目的: 333 | * 1.实现前后端代码分离,分布式部署 334 | * 2.利用token替代session实现状态保持,token是有时效性的满足退出登录,token存入redis可以解决不同服务器之间session不同步的问题,满足分布式部署 335 | * 3.利用sign,前端按照约定的方式组合加密生成字符串来校验用户传递的参数跟后端接收的参数是否一直,保障接口数据传递的安全 336 | * 4.利用nonce,timestamp来保障每次请求的生成sign不一致,并将sign与nonce组合存入redis,来防止api接口重放 337 | 338 | ## 目录介绍 339 | *** 340 | ``` 341 | 342 | ├── Core 343 | │   ├── Common.php(常用的公用方法) 344 | │   ├── Controller.php (控制器基类) 345 | │   └── RedisService.php (redis操作类) 346 | ├── config.php (redis以及是否开启关闭接口校验的配置项) 347 | ├── login.php (登录获取token入口) 348 | └── user.php(获取用户信息,执行整个接口校验流程) 349 | 350 | ``` 351 | 352 | ## 登录鉴权图 353 | *** 354 | ![](https://img2018.cnblogs.com/blog/595183/201906/595183-20190614182945976-471067830.png) 355 | 356 | ## 接口请求安全性校验整体流程图 357 | *** 358 | ![](https://img2018.cnblogs.com/blog/595183/201906/595183-20190614182707043-770306388.png) 359 | 360 | ## 代码展示 361 | ### common.php 362 | ``` 363 | $code, 377 | 'msg'=>$msg, 378 | ]; 379 | if(!empty($data)){ 380 | $outData['data'] = $data; 381 | } 382 | echo json_encode($outData); 383 | die(); 384 | } 385 | 386 | /*** 387 | * @desc 创建token 388 | * @param $uid 389 | */ 390 | public static function createToken($uid){ 391 | $time = time(); 392 | $rand = mt_rand(100,999); 393 | $token = md5($time.$rand.'jwt-token'.$uid); 394 | return $token; 395 | } 396 | 397 | /** 398 | * @desc 获取配置信息 399 | * @param $type 配置信息的类型,为空获取所有配置信息 400 | */ 401 | public static function getConfig($type=''){ 402 | $config = include "./config.php"; 403 | if(empty($type)){ 404 | return $config; 405 | }else{ 406 | if(isset($config[$type])){ 407 | return $config[$type]; 408 | } 409 | return []; 410 | } 411 | } 412 | 413 | } 414 | 415 | ``` 416 | 417 | ### RedisService.php 418 | ``` 419 | redis = new \Redis(); 438 | $this->port = $config['port'] ? $config['port'] : 6379; 439 | $this->host = $config['host']; 440 | if(isset($config['db_id'])){ 441 | $this->dbId = $config['db_id']; 442 | $this->redis->connect($this->host, $this->port); 443 | } 444 | if(isset($config['auth'])) 445 | { 446 | $this->redis->auth($config['auth']); 447 | $this->auth = $config['auth']; 448 | } 449 | $this->redis->select($this->dbId); 450 | } 451 | 452 | /** 453 | *@desc 得到实例化的对象 454 | ***/ 455 | public static function getInstance($config){ 456 | if(!self::$_instance instanceof self) { 457 | self::$_instance = new self($config); 458 | } 459 | return self::$_instance; 460 | 461 | } 462 | 463 | /** 464 | *@desc 防止克隆 465 | **/ 466 | private function __clone(){} 467 | 468 | /* 469 | *@desc 设置字符串类型的值,以及失效时间 470 | **/ 471 | public function set($key,$value=0,$timeout=0){ 472 | if(empty($value)){ 473 | $this->error = "设置键值不能够为空哦~"; 474 | return $this->error; 475 | } 476 | $res = $this->redis->set($key,$value); 477 | if($timeout){ 478 | $this->redis->expire($key,$timeout); 479 | } 480 | return $res; 481 | } 482 | 483 | /** 484 | *@desc 获取字符串类型的值 485 | **/ 486 | public function get($key){ 487 | return $this->redis->get($key); 488 | } 489 | 490 | } 491 | 492 | ``` 493 | 494 | ### Controller.php 495 | ``` 496 | _config = Common::getConfig(); 528 | //2.获取redis对象 529 | $redisConfig = $this->_config['redis']; 530 | $this->redis = RedisService::getInstance($redisConfig); 531 | 532 | //3.token校验 533 | $this->checkToken(); 534 | //4.校验api的合法性check_api为true校验,为false不用校验 535 | if($this->_config['checkApi']){ 536 | // 5. sign签名验证 537 | $this->checkSign(); 538 | 539 | //6.校验nonce,预防接口重放 540 | $this->checkNonce(); 541 | } 542 | } 543 | 544 | /** 545 | * @desc 校验token的有效性 546 | */ 547 | private function checkToken(){ 548 | if(!isset($_POST['token'])){ 549 | Common::outJson('10000','token不能够为空'); 550 | } 551 | $this->token = $_POST['token']; 552 | $key = "token:".$this->token; 553 | $mid = $this->redis->get($key); 554 | if(!$mid){ 555 | Common::outJson('10001','token已过期或不合法,请先登录系统 '); 556 | } 557 | $this->mid = $mid; 558 | } 559 | 560 | /** 561 | * @desc 校验签名 562 | */ 563 | private function checkSign(){ 564 | if(!isset($_GET['sign'])){ 565 | Common::outJson('10002','sign校验码为空'); 566 | } 567 | $this->sign = $_GET['sign']; 568 | $postParams = $_POST; 569 | $params = []; 570 | foreach($postParams as $k=>$v) { 571 | $params[] = sprintf("%s%s", $k,$v); 572 | } 573 | sort($params); 574 | $apiSerect = $this->_config['apiSerect']; 575 | $str = sprintf("%s%s%s", $apiSerect, implode('', $params), $apiSerect); 576 | if ( md5($str) != $this->sign ) { 577 | Common::outJson('10004','传递的数据被篡改,请求不合法'); 578 | } 579 | } 580 | 581 | /** 582 | * @desc nonce校验预防接口重放 583 | */ 584 | private function checkNonce(){ 585 | if(!isset($_POST['nonce'])){ 586 | Common::outJson('10003','nonce为空'); 587 | } 588 | $this->nonce = $_POST['nonce']; 589 | $nonceKey = sprintf("sign:%s:nonce:%s", $this->sign, $this->nonce); 590 | $nonV = $this->redis->get($nonceKey); 591 | if ( !empty($nonV)) { 592 | Common::outJson('10005','该url已经被调用过,不能够重复使用'); 593 | } else { 594 | $this->redis->set($nonceKey,$this->nonce,360); 595 | } 596 | } 597 | 598 | } 599 | ``` 600 | 601 | ### config.php 602 | ``` 603 | [ 607 | 'host' => 'localhost', 608 | 'port' => '6379', 609 | 'auth' => '123456', 610 | 'db_id' => 0,//redis的第几个数据库仓库 611 | ], 612 | //是否开启接口校验,true开启,false,关闭 613 | 'checkApi'=>true, 614 | //加密sign的盐值 615 | 'apiSerect'=>'test_jwt' 616 | ]; 617 | ``` 618 | 619 | ### login.php 620 | ``` 621 | set($key,$uid,3600); 649 | $data['token'] = $token; 650 | Common::outJson(0,'登录成功',$data); 651 | 652 | ``` 653 | 654 | ### user.php 655 | ``` 656 | 2, 675 | "name"=>'巴八灵', 676 | "age"=>30, 677 | ]; 678 | if($this->mid==$_POST['mid']){ 679 | Common::outJson(0,'成功获取用户信息',$userInfo); 680 | }else{ 681 | Common::outJson(-1,'未找到该用户信息'); 682 | } 683 | } 684 | } 685 | //获取用户信息 686 | $user = new UserController(); 687 | $user->getUser(); 688 | ``` 689 | 690 | ## 演示用户登录 691 | *** 692 | 693 | **简要描述:** 694 | 695 | - 用户登录接口 696 | 697 | **请求URL:** 698 | - ` http://localhost/login.php ` 699 | 700 | **请求方式:** 701 | - POST 702 | 703 | **参数:** 704 | 705 | |参数名|必选|类型|说明| 706 | |:---- |:---|:----- |----- | 707 | |username |是 |string |用户名 | 708 | |pwd |是 |string | 密码 | 709 | 710 | **返回示例** 711 | 712 | ``` 713 | { 714 | "code": 0, 715 | "msg": "登录成功", 716 | "data": { 717 | "token": "86b58ada26a20a323f390dd5a92aec2a" 718 | } 719 | } 720 | 721 | { 722 | "code": -1, 723 | "msg": "用户名或密码错误" 724 | } 725 | 726 | ``` 727 | 728 | ## 演示获取用户信息 729 | 730 | **简要描述:** 731 | 732 | - 获取用户信息,校验整个接口安全的流程 733 | 734 | **请求URL:** 735 | - `http://localhost/user.php?sign=f39b0f2dea817dd9dbef9e6a2bf478de ` 736 | 737 | **请求方式:** 738 | - POST 739 | 740 | **参数:** 741 | 742 | |参数名|必选|类型|说明| 743 | |:---- |:---|:----- |----- | 744 | |token |是 |string |token | 745 | |mid |是 |int |用户id | 746 | |nonce |是 |string | 防止用户重放字符串 md5加密串 | 747 | |timestamp |是 |int | 当前时间戳 | 748 | 749 | **返回示例** 750 | 751 | ``` 752 | { 753 | "code": 0, 754 | "msg": "成功获取用户信息", 755 | "data": { 756 | "id": 2, 757 | "name": "巴八灵", 758 | "age": 30 759 | } 760 | } 761 | 762 | { 763 | "code": "10005", 764 | "msg": "该url已经被调用过,不能够重复使用" 765 | } 766 | 767 | { 768 | "code": "10004", 769 | "msg": "传递的数据被篡改,请求不合法" 770 | } 771 | { 772 | "code": -1, 773 | "msg": "未找到该用户信息" 774 | } 775 | ``` 776 | 777 | ## 后记 778 | *** 779 | 上面完整的实现了整个api的安全过程,包括接口token生成时效性合法性验证,接口数据传输防篡改,接口防重放实现。仅仅靠这还不能够最大限制保证接口的安全。条件满足的情况下可以使用https协议从数据底层来提高安全性,另外本实现过程token是使用redis存储,下一篇文章我们将使用第三方开发的库实现JWT的规范操作,来替代redis的使用。 780 | 781 | ## php接口数据安全解决方案二 782 | *** 783 | #### jwt说明 784 | JWT是什么 785 | JWT是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。基于token的身份验证可以替代传统的cookie+session身份验证方法。 786 | 它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。 787 | 788 | 本实例是使用github开源项目来实现,项目地址为 789 | [点击查看源jwt项目代码](https://github.com/lcobucci/jwt/tree/3.2) 790 | 791 | 怎么使用jwt项目? 792 | - 1.阅读GitHub上项目文档说明 793 | - 2.composer安装jwt(composer require lcobucci/jwt) 794 | 795 | #### 实例演示token签名并创建token 796 | ``` 797 | setHeader('alg','HS256') 804 | //配置发行者 805 | ->setIssuer("jwtTest") 806 | //配置Audience 807 | ->setAudience("php") 808 | //配置令牌发出的时间(签发时间) 809 | ->setIssuedAt($time) 810 | //配置令牌该时间之前不接收处理该Token 811 | ->setNotBefore($time+60) 812 | //配置令牌到期的时间 813 | ->setExpiration($time + 7200) 814 | //配置一个自定义uid声明 815 | ->set('uid',20) 816 | //使用sha256进行签名,密钥为123456 817 | ->sign(new Sha256(),"123456") 818 | //获取token 819 | ->getToken(); 820 | // echo $token; 821 | //获取设置所有header头 822 | $headers = $token->getHeaders(); 823 | //获取所有的配置 824 | $claims = $token->getClaims(); 825 | 826 | $alg = $token->getHeader('alg'); 827 | $iss = $token->getClaim('iss'); 828 | $aud = $token->getClaim('aud'); 829 | $iat = $token->getClaim('iat'); 830 | $exp = $token->getClaim('exp'); 831 | $nbf = $token->getClaim('nbf'); 832 | $uid = $token->getClaim('uid'); 833 | 834 | echo "=====下面是设置的header头信息======
"; 835 | echo "当前token的alg盐值为{$alg}
"; 836 | 837 | echo "=====下面是所有配置信息
"; 838 | echo "当前token的发行者是:{$iss}
"; 839 | echo "当前token的Audience是:{$aud}
"; 840 | echo "当前token的令牌发出时间是:{$iat}
"; 841 | echo "当前token不接收处理的时间为:{$nbf}
"; 842 | echo "当前token的到期时间:{$exp}
"; 843 | echo "当前token的uid是:{$uid}
"; 844 | echo "当前token字符串为:{$token}"; 845 | ``` 846 | 结果展示: 847 | ``` 848 | =====下面是设置的header头信息====== 849 | 当前token的alg盐值为HS256 850 | =====下面是所有配置信息 851 | 当前token的发行者是:jwtTest 852 | 当前token的Audience是:php 853 | 当前token的令牌发出时间是:1563246935 854 | 当前token不接收处理的时间为:1563246995 855 | 当前token的到期时间:1563254135 856 | 当前token的uid是:20 857 | 当前token字符串为:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqd3RUZXN0IiwiYXVkIjoicGhwIiwiaWF0IjoxNTYzMjQ2OTM1LCJuYmYiOjE1NjMyNDY5OTUsImV4cCI6MTU2MzI1NDEzNSwidWlkIjoyMH0.O8tscKPweCvSaXkOVbmhtEcsJ7BWRxRn9s_xXFstgsE 858 | ``` 859 | #### 解析token并校验token合法性 860 | ``` 861 | parse((string) $token); 873 | $uid = $decodeToken->getClaim('uid'); 874 | $exp = $decodeToken->getClaim('exp'); 875 | $time = time(); 876 | echo "当前时间为:{$time}
"; 877 | $result = $decodeToken->verify(new Sha256(),"123456"); 878 | 879 | //对token进行发行者和audience的校验 880 | $data = new ValidationData(); 881 | $data->setAudience("php"); 882 | $data->setIssuer("jwtTest"); 883 | $resultVali = $decodeToken->validate($data); 884 | // print_r($resultVali); 885 | 886 | //判断token签名和校验发行者是否合法 887 | if($result && $resultVali){ 888 | //如果设置的token有效时间大于当前时间则表示token过期了 889 | if($time>$exp){ 890 | echo "该token已经过期了
"; 891 | }else{ 892 | echo "该token是合法的uid为:{$uid},过期时间为{$exp}
"; 893 | } 894 | }else{ 895 | echo "该token不合法签名错误或发行者不合法
"; 896 | } 897 | ``` 898 | 运行后结果为: 899 | ``` 900 | 当前时间为:1563248373 901 | 该token是合法的uid为:20,过期时间为1563253478 902 | ``` 903 | #### 类库封装管理jwt实例 904 | ``` 905 | token; 966 | } 967 | 968 | /** 969 | * 设置token 970 | * @param $token 971 | * @return $this 972 | */ 973 | public function setToken($token){ 974 | $this->token = $token; 975 | return $this; 976 | } 977 | 978 | /** 979 | * 设置uid 980 | * @param $uid 981 | * @return $this 982 | */ 983 | public function setUid($uid){ 984 | $this->uid = $uid; 985 | return $this; 986 | } 987 | 988 | /** 989 | * 获取uid 990 | * @return mixed 991 | */ 992 | public function getUid(){ 993 | return $this->uid; 994 | } 995 | 996 | /** 997 | * 获取exp 998 | * @return mixed 999 | */ 1000 | public function getExp(){ 1001 | return $this->exp; 1002 | } 1003 | 1004 | /** 1005 | * 编码jwt token 1006 | */ 1007 | public function encode(){ 1008 | $time = time(); 1009 | $this->token = (new Builder())->setHeader('alg','HS256') 1010 | ->setIssuer($this->iss) 1011 | ->setAudience($this->aud) 1012 | ->setIssuedAt($time) 1013 | ->setExpiration($time + self::EXP) 1014 | ->set('uid',$this->uid) 1015 | ->sign(new Sha256(),$this->serect) 1016 | ->getToken(); 1017 | return $this; 1018 | } 1019 | 1020 | /** 1021 | * 解析传递的token 1022 | * @return 用户传递的decode 1023 | */ 1024 | public function decode(){ 1025 | if(!$this->decodeToken){ 1026 | // Parses from a string 1027 | $this->decodeToken = (new Parser())->parse((string) $this->token); 1028 | $this->uid = $this->decodeToken->getClaim('uid'); 1029 | $this->exp = $this->decodeToken->getClaim('exp'); 1030 | // error_log('exp:'.$this->decodeToken->getClaim('exp')); 1031 | // error_log('uid:'.$this->decodeToken->getClaim('uid')); 1032 | } 1033 | return $this->decodeToken; 1034 | } 1035 | 1036 | /** 1037 | * verify校验token signature串第三个字符串 1038 | * @return mixed 1039 | */ 1040 | public function verify(){ 1041 | $result = $this->decode()->verify(new Sha256(),$this->serect); 1042 | return $result; 1043 | } 1044 | 1045 | /** 1046 | * 校验传递的token是否有效,校验前两个字符串 1047 | * @return mixed 1048 | */ 1049 | public function validate(){ 1050 | $data = new ValidationData(); 1051 | $data->setAudience($this->aud); 1052 | $data->setIssuer($this->iss); 1053 | return $this->decode()->validate($data); 1054 | } 1055 | 1056 | } 1057 | 1058 | /* 1059 | @desc 模拟登录返回token 1060 | */ 1061 | $jwtAuth = JwtAuth::getInstance(); 1062 | $uid = 20; 1063 | $token = $jwtAuth->setUid($uid)->encode()->getToken(); 1064 | echo "当前已经为您生成最新token:{$token}
"; 1065 | 1066 | 1067 | /* 1068 | 1.解析token 1069 | 2.校验token签名是否合法 1070 | 3.验证token发行者等信息是否合法 1071 | 4.校验token是否过期 1072 | */ 1073 | $token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqd3RUZXN0IiwiYXVkIjoicGhwIiwiaWF0IjoxNTYzMjY4NjExLCJleHAiOjE1NjMyNjg2NzEsInVpZCI6MjB9.PeEA3xTE2lKl4YCYQ2cjHSNYsrJ24HRnW1-yKM-LgHc'; 1074 | $jwtAuth2 = JwtAuth::getInstance(); 1075 | $jwtAuth2->setToken($token); 1076 | if($jwtAuth2->validate() && $jwtAuth2->verify()){ 1077 | //初始化用户id 1078 | $uid = $jwtAuth2->getUid(); 1079 | $exp = $jwtAuth2->getExp(); 1080 | echo "token合法,您当前的uid为:{$uid},当前时间戳为:".time()."token有效时间为:{$exp}
"; 1081 | }else{ 1082 | echo "token校验失败,token签名不合法,或token发行者信息不合法
"; 1083 | } 1084 | ``` 1085 | 演示结果为 1086 | ``` 1087 | 当前已经为您生成最新token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqd3RUZXN0IiwiYXVkIjoicGhwIiwiaWF0IjoxNTYzMjY4NjIzLCJleHAiOjE1NjMyNjg2ODMsInVpZCI6MjB9.BrsVElhVkTIq5xH3-JpvqvawNhDALb98VYZGbMTzWV8 1088 | token合法,您当前的uid为:20,当前时间戳为:1563268623token有效时间为:1563268671 1089 | ``` 1090 | ``` 1091 | 当前已经为您生成最新token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqd3RUZXN0IiwiYXVkIjoicGhwIiwiaWF0IjoxNTYzMjY4NzA1LCJleHAiOjE1NjMyNjg3NjUsInVpZCI6MjB9.juTM5iG8LNDid8Sp4jOjtHeTitaIB2WxZeW3GjnQrB0 1092 | token校验失败,token签名不合法,或token发行者信息不合法 1093 | ``` 1094 | 1095 | 1096 | -------------------------------------------------------------------------------- /http.md: -------------------------------------------------------------------------------- 1 | # http协议学习 2 | * http协议说明(#http协议说明) 3 | * 无状态 4 | * http状态码 5 | * telnet实现get请求 6 | * telnet实现post请求 7 | * telnet实现文件上传 8 | 9 | ## http协议说明 10 | 11 | ### http协议执行过程 12 | - 1.客户机通过tcp/ip协议建立到服务器的tcp连接 13 | - 2.客户端向服务端发送http协议请求 14 | - 3.服务端向客户端发送http协议应答包 15 | - 4.断开连接,客户端渲染html文档 16 | -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/.DS_Store -------------------------------------------------------------------------------- /images/302.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/302.png -------------------------------------------------------------------------------- /images/403.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/403.png -------------------------------------------------------------------------------- /images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/404.png -------------------------------------------------------------------------------- /images/auto-load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/auto-load.png -------------------------------------------------------------------------------- /images/iterator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/iterator.png -------------------------------------------------------------------------------- /images/iterator2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/iterator2.png -------------------------------------------------------------------------------- /images/nginx-fz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/nginx-fz.png -------------------------------------------------------------------------------- /images/nginx_gzip1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/nginx_gzip1.png -------------------------------------------------------------------------------- /images/nginx_gzip2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/nginx_gzip2.2.png -------------------------------------------------------------------------------- /images/nginx_gzip2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/nginx_gzip2.png -------------------------------------------------------------------------------- /images/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lisiqiong/phper/6ea1baad71ec2abea097e07f582894f59b092dd5/images/ok.png -------------------------------------------------------------------------------- /jwt_diy/Core/Common.php: -------------------------------------------------------------------------------- 1 | $code, 15 | 'msg'=>$msg, 16 | ]; 17 | if(!empty($data)){ 18 | $outData['data'] = $data; 19 | } 20 | echo json_encode($outData); 21 | die(); 22 | } 23 | 24 | /*** 25 | * @desc 创建token 26 | * @param $uid 27 | */ 28 | public static function createToken($uid){ 29 | $time = time(); 30 | $rand = mt_rand(100,999); 31 | $token = md5($time.$rand.'jwt-token'.$uid); 32 | return $token; 33 | } 34 | 35 | /** 36 | * @desc 获取配置信息 37 | * @param $type 配置信息的类型,为空获取所有配置信息 38 | */ 39 | public static function getConfig($type=''){ 40 | $config = include "./config.php"; 41 | if(empty($type)){ 42 | return $config; 43 | }else{ 44 | if(isset($config[$type])){ 45 | return $config[$type]; 46 | } 47 | return []; 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /jwt_diy/Core/Controller.php: -------------------------------------------------------------------------------- 1 | _config = Common::getConfig(); 33 | //2.获取redis对象 34 | $redisConfig = $this->_config['redis']; 35 | $this->redis = RedisService::getInstance($redisConfig); 36 | 37 | //3.token校验 38 | $this->checkToken(); 39 | //4.校验api的合法性check_api为true校验,为false不用校验 40 | if($this->_config['checkApi']){ 41 | // 5. sign签名验证 42 | $this->checkSign(); 43 | 44 | //6.校验nonce,预防接口重放 45 | $this->checkNonce(); 46 | } 47 | } 48 | 49 | /** 50 | * @desc 校验token的有效性 51 | */ 52 | private function checkToken(){ 53 | if(!isset($_POST['token'])){ 54 | Common::outJson('10000','token不能够为空'); 55 | } 56 | $this->token = $_POST['token']; 57 | $key = "token:".$this->token; 58 | $mid = $this->redis->get($key); 59 | if(!$mid){ 60 | Common::outJson('10001','token已过期或不合法,请先登录系统 '); 61 | } 62 | $this->mid = $mid; 63 | } 64 | 65 | /** 66 | * @desc 校验签名 67 | */ 68 | private function checkSign(){ 69 | if(!isset($_GET['sign'])){ 70 | Common::outJson('10002','sign校验码为空'); 71 | } 72 | $this->sign = $_GET['sign']; 73 | $postParams = $_POST; 74 | $params = []; 75 | foreach($postParams as $k=>$v) { 76 | $params[] = sprintf("%s%s", $k,$v); 77 | } 78 | sort($params); 79 | $apiSerect = $this->_config['apiSerect']; 80 | $str = sprintf("%s%s%s", $apiSerect, implode('', $params), $apiSerect); 81 | if ( md5($str) != $this->sign ) { 82 | Common::outJson('10004','传递的数据被篡改,请求不合法'); 83 | } 84 | } 85 | 86 | /** 87 | * @desc nonce校验预防接口重放 88 | */ 89 | private function checkNonce(){ 90 | if(!isset($_POST['nonce'])){ 91 | Common::outJson('10003','nonce为空'); 92 | } 93 | $this->nonce = $_POST['nonce']; 94 | $nonceKey = sprintf("sign:%s:nonce:%s", $this->sign, $this->nonce); 95 | $nonV = $this->redis->get($nonceKey); 96 | if ( !empty($nonV)) { 97 | Common::outJson('10005','该url已经被调用过,不能够重复使用'); 98 | } else { 99 | $this->redis->set($nonceKey,$this->nonce,360); 100 | } 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /jwt_diy/Core/RedisService.php: -------------------------------------------------------------------------------- 1 | redis = new \Redis(); 20 | $this->port = $config['port'] ? $config['port'] : 6379; 21 | $this->host = $config['host']; 22 | if(isset($config['db_id'])){ 23 | $this->dbId = $config['db_id']; 24 | $this->redis->connect($this->host, $this->port); 25 | } 26 | if(isset($config['auth'])) 27 | { 28 | $this->redis->auth($config['auth']); 29 | $this->auth = $config['auth']; 30 | } 31 | $this->redis->select($this->dbId); 32 | } 33 | 34 | /** 35 | *@desc 得到实例化的对象 36 | ***/ 37 | public static function getInstance($config){ 38 | if(!self::$_instance instanceof self) { 39 | self::$_instance = new self($config); 40 | } 41 | return self::$_instance; 42 | 43 | } 44 | 45 | /** 46 | *@desc 防止克隆 47 | **/ 48 | private function __clone(){} 49 | 50 | /* 51 | *@desc 设置字符串类型的值,以及失效时间 52 | **/ 53 | public function set($key,$value=0,$timeout=0){ 54 | if(empty($value)){ 55 | $this->error = "设置键值不能够为空哦~"; 56 | return $this->error; 57 | } 58 | $res = $this->redis->set($key,$value); 59 | if($timeout){ 60 | $this->redis->expire($key,$timeout); 61 | } 62 | return $res; 63 | } 64 | 65 | /** 66 | *@desc 获取字符串类型的值 67 | **/ 68 | public function get($key){ 69 | return $this->redis->get($key); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /jwt_diy/config.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'host' => 'localhost', 7 | 'port' => '6379', 8 | 'auth' => '123456', 9 | 'db_id' => 0,//redis的第几个数据库仓库 10 | ], 11 | //是否开启接口校验,true开启,false,关闭 12 | 'checkApi'=>true, 13 | //加密sign的盐值 14 | 'apiSerect'=>'test_jwt' 15 | ]; -------------------------------------------------------------------------------- /jwt_diy/login.php: -------------------------------------------------------------------------------- 1 | set($key,$uid,3600); 29 | $data['token'] = $token; 30 | Common::outJson(0,'登录成功',$data); 31 | -------------------------------------------------------------------------------- /jwt_diy/user.php: -------------------------------------------------------------------------------- 1 | 2, 20 | "name"=>'巴八灵', 21 | "age"=>30, 22 | ]; 23 | if($this->mid==$_POST['mid']){ 24 | Common::outJson(0,'成功获取用户信息',$userInfo); 25 | }else{ 26 | Common::outJson(-1,'未找到该用户信息'); 27 | } 28 | } 29 | } 30 | //获取用户信息 31 | $user = new UserController(); 32 | $user->getUser(); -------------------------------------------------------------------------------- /miaosha.md: -------------------------------------------------------------------------------- 1 | 2 | 基本需求-扣库存实现 3 | 1.初始化库存到本地库存 4 | 2.本地减库存,成功则进行统一减库存,失败则返回 5 | 3.统一减库存成功则写入MQ,异步创建订单 6 | 4.告知用户抢购成功 -------------------------------------------------------------------------------- /mysql.md: -------------------------------------------------------------------------------- 1 | # mysql知识整理 2 | - 基础知识 3 | - 普通查询语句 4 | - join 5 | - 子查询 6 | - union 7 | - union all 8 | - [触发器](#触发器) 9 | - 存储过程 10 | - 数据库优化 11 | - 应用系统sql优化 12 | - 分表策略 13 | - 分库策略 14 | - 索引 15 | - 普通索引,联合索引 16 | - 如何选择字段创建索引 17 | - 索引失效原因 18 | - 查询缓存 19 | - mysql服务器优化 20 | - 开启慢查询定位问题 21 | - mysql连接数 22 | - 操作系统与硬件优化 23 | - 系统架构整体优化 24 | - 负载均衡 25 | - 缓存 26 | - 分布式优化 27 | - 数据管理与维护 28 | - 数据备份 29 | - mysqldump备份数据库 30 | - mysldump备份数据表 31 | - mysqldump备份数据表结构 32 | - 数据恢复 33 | - mysql日志 34 | - mysql监控 35 | - mysql常用工具 36 | 37 | 38 | 39 | ## 触发器 40 | 触发器的定义:触发器(TRIGGER)是MySQL的数据库对象之一,从5.0.2版本开始支持。该对象与编程语言中的函数非常类似,都需要声明、执行等。但是触发器的执行不是由程序调用,也不是由手工启动,而是由事件来触发、激活从而实现执行。 41 | 42 | #### 触发器的基本语法 43 | ``` 44 | CREATE TRIGGER trigger_name 45 | trigger_time 46 | trigger_event ON tbl_name 47 | FOR EACH ROW 48 | begin 49 | …… 50 | end 51 | ``` 52 | 53 | 这里触发器的有两种:before,after 54 | 触发的事件类型为:update,insert,delete 55 | #### 创建两张表并插入一些数据演示下触发器的使用 56 | ``` 57 | CREATE TABLE goods ( 58 | id INT(11) UNSIGNED NOT NULL auto_increment, 59 | name VARCHAR(40) not null COMMENT '商品名称', 60 | stock SMALLINT(11) UNSIGNED NOT NULL COMMENT '商品库存', 61 | PRIMARY KEY(`id`) 62 | )ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT '商品表' ; 63 | ``` 64 | 65 | ``` 66 | INSERT INTO goods (`name`,`stock`) values ('iphonex',50),('小米2',30),('联想手机',40); 67 | ``` 68 | 69 | ``` 70 | CREATE TABLE goods_order( 71 | oid int(11) UNSIGNED NOT NULL auto_increment COMMENT '订单id,自增id', 72 | gid INT(11) UNSIGNED NOT NULL COMMENT 'goods表的商品id', 73 | nums SMALLINT(11) UNSIGNED NOT NULL COMMENT '订单购买数量', 74 | PRIMARY KEY(`oid`) 75 | ) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT '商品订单'; 76 | ``` 77 | 78 | #### 创建触发器当有新订单的时候减少goods表的库存数量 79 | ``` 80 | CREATE TRIGGER t1 81 | AFTER 82 | INSERT 83 | ON goods_order 84 | FOR EACH ROW 85 | BEGIN 86 | UPDATE goods SET stock = stock-new.nums WHERE id=new.gid; 87 | END 88 | ``` 89 | 90 | #### 运行创建触发器语句后显示触发器 91 | ``` 92 | MySQL [test]> show triggers\G; 93 | *************************** 1. row *************************** 94 | Trigger: t1 95 | Event: INSERT 96 | Table: goods_order 97 | Statement: BEGIN 98 | UPDATE goods SET stock = stock-new.nums WHERE id=new.gid; 99 | END 100 | Timing: AFTER 101 | Created: NULL 102 | sql_mode: NO_ENGINE_SUBSTITUTION 103 | Definer: root@% 104 | character_set_client: utf8 105 | collation_connection: utf8_general_ci 106 | Database Collation: utf8_unicode_ci 107 | 1 row in set (0.00 sec) 108 | ``` 109 | 110 | #### 查询当前数据库商品表和订单表的数据 111 | ``` 112 | MySQL [test]> select * from goods; 113 | +----+--------------+-------+ 114 | | id | name | stock | 115 | +----+--------------+-------+ 116 | | 1 | iphonex | 50 | 117 | | 2 | 小米2 | 30 | 118 | | 3 | 联想手机 | 40 | 119 | +----+--------------+-------+ 120 | 3 rows in set (0.00 sec) 121 | 122 | MySQL [test]> select * from goods_order; 123 | Empty set (0.00 sec) 124 | ``` 125 | 126 | #### 向订单表中插入数据查看商品表库存是否自动减掉了 127 | ``` 128 | MySQL [test]> insert into goods_order (`gid`,`nums`) values (1,2); 129 | Query OK, 1 row affected (0.00 sec) 130 | 131 | MySQL [test]> select * from goods; 132 | +----+--------------+-------+ 133 | | id | name | stock | 134 | +----+--------------+-------+ 135 | | 1 | iphonex | 48 | 136 | | 2 | 小米2 | 30 | 137 | | 3 | 联想手机 | 40 | 138 | 139 | MySQL [test]> select * from goods_order; 140 | +-----+-----+------+ 141 | | oid | gid | nums | 142 | +-----+-----+------+ 143 | | 1 | 1 | 2 | 144 | +-----+-----+------+ 145 | ``` 146 | 经过对照发现当添加一条商品,购买数量为2时商品表的库存数由50变为了48说明触发器运行成果,和我们所猜想的一致。 147 | 148 | #### 想订单表中插入超过库存总量的数据时测试效果 149 | ``` 150 | MySQL [test]> insert into goods_order (`gid`,`nums`) values (2,32); 151 | ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`test`.`goods`.`stock` - NEW.nums)' 152 | 153 | 插入超过库存数量的商品时sql报错,因为我们设置的字段是无符号的,如果当前字段是有符号此时,字段会更新为-2,这肯定是不符合我们要求的,此时需要修改t1触发器来,当超过库存总数量的时候我们减掉最大库存即可。 154 | ``` 155 | #### 修改t1触发器 156 | ``` 157 | DROP TRIGGER t1; 158 | CREATE TRIGGER t1 159 | BEFORE 160 | INSERT 161 | ON goods_order 162 | FOR EACH ROW 163 | BEGIN 164 | DECLARE rnum int; 165 | ##查询插入之前商品的库存 166 | SELECT stock into rnum from goods where id=new.gid; 167 | ##如果即购买订单的商品数量大于总库存,则设置为购买的数量为当前的商品库存数量 168 | if new.nums>rnum THEN 169 | SET new.nums = rnum; 170 | END IF; 171 | UPDATE goods SET stock = stock-new.nums WHERE id=new.gid; 172 | END 173 | ``` 174 | 175 | #### 插入订单查看效果 176 | ``` 177 | MySQL [test]> select * from goods; 178 | +----+--------------+-------+ 179 | | id | name | stock | 180 | +----+--------------+-------+ 181 | | 1 | iphonex | 48 | 182 | | 2 | 小米2 | 30 | 183 | | 3 | 联想手机 | 40 | 184 | +----+--------------+-------+ 185 | 3 rows in set (0.00 sec) 186 | 187 | MySQL [test]> select * from goods_order; 188 | +-----+-----+------+ 189 | | oid | gid | nums | 190 | +-----+-----+------+ 191 | | 1 | 1 | 2 | 192 | +-----+-----+------+ 193 | 1 row in set (0.00 sec) 194 | 195 | 196 | MySQL [test]> insert into goods_order (`gid`,`nums`) values (2,32); 197 | Query OK, 1 row affected (0.00 sec) 198 | 199 | MySQL [test]> select * from goods; 200 | +----+--------------+-------+ 201 | | id | name | stock | 202 | +----+--------------+-------+ 203 | | 1 | iphonex | 48 | 204 | | 2 | 小米2 | 0 | 205 | | 3 | 联想手机 | 40 | 206 | +----+--------------+-------+ 207 | 3 rows in set (0.00 sec) 208 | 209 | MySQL [test]> select * from goods_order; 210 | +-----+-----+------+ 211 | | oid | gid | nums | 212 | +-----+-----+------+ 213 | | 1 | 1 | 2 | 214 | | 3 | 2 | 30 | 215 | +-----+-----+------+ 216 | 2 rows in set (0.00 sec 217 | 218 | ``` 219 | 从上门可以看出,当我们订单中购买数量大于商品库存总数的时候,商品库存只会扣除最大库存数 220 | 221 | #### 创建触发t2,实现当删除订单表时恢复商品库存数量 222 | ``` 223 | CREATE TRIGGER t2 224 | AFTER 225 | DELETE 226 | ON goods_order 227 | FOR EACH ROW 228 | BEGIN 229 | UPDATE goods SET stock=stock+old.nums where id=old.gid; 230 | END 231 | ``` 232 | 233 | 查询当前所有触发器 234 | ``` 235 | MySQL [test]> show triggers\G; 236 | *************************** 1. row *************************** 237 | Trigger: t1 238 | Event: INSERT 239 | Table: goods_order 240 | Statement: BEGIN 241 | DECLARE rnum int; 242 | ##查询插入之前商品的库存 243 | SELECT stock into rnum from goods where id=new.gid; 244 | ##如果即购买订单的商品数量大于总库存,则设置为购买的数量为当前的商品库存数量 245 | if new.nums>rnum THEN 246 | SET new.nums = rnum; 247 | END IF; 248 | UPDATE goods SET stock = stock-new.nums WHERE id=new.gid; 249 | END 250 | Timing: BEFORE 251 | Created: NULL 252 | sql_mode: NO_ENGINE_SUBSTITUTION 253 | Definer: root@% 254 | character_set_client: utf8 255 | collation_connection: utf8_general_ci 256 | Database Collation: utf8_unicode_ci 257 | *************************** 2. row *************************** 258 | Trigger: t2 259 | Event: DELETE 260 | Table: goods_order 261 | Statement: BEGIN 262 | UPDATE goods SET stock=stock+old.nums where id=old.gid; 263 | END 264 | Timing: AFTER 265 | Created: NULL 266 | sql_mode: NO_ENGINE_SUBSTITUTION 267 | Definer: root@% 268 | character_set_client: utf8 269 | collation_connection: utf8_general_ci 270 | Database Collation: utf8_unicode_ci 271 | 2 rows in set (0.00 sec) 272 | ``` 273 | 274 | 删除订单表id=1的数据 275 | ``` 276 | MySQL [test]> delete from goods_order where id=1; 277 | ERROR 1054 (42S22): Unknown column 'id' in 'where clause' 278 | MySQL [test]> delete from goods_order where oid=1; 279 | Query OK, 1 row affected (0.00 sec) 280 | 281 | MySQL [test]> select * from goods; 282 | +----+--------------+-------+ 283 | | id | name | stock | 284 | +----+--------------+-------+ 285 | | 1 | iphonex | 50 | 286 | | 2 | 小米2 | 0 | 287 | | 3 | 联想手机 | 40 | 288 | +----+--------------+-------+ 289 | 3 rows in set (0.00 sec) 290 | 291 | MySQL [test]> select * from goods_order; 292 | +-----+-----+------+ 293 | | oid | gid | nums | 294 | +-----+-----+------+ 295 | | 3 | 2 | 30 | 296 | +-----+-----+------+ 297 | 1 row in set (0.00 sec) 298 | ``` 299 | 由上门可以看出,删除id=1的数据后,订单表id=1的商品库存数由之前48增加到了50说明触发器成功执行 300 | 301 | #### 最后解释下FOR EACH ROW 302 | for each row表示的是执行的触发起的动作影响了多少行数据就执行多少行的数据 303 | -------------------------------------------------------------------------------- /nginx.md: -------------------------------------------------------------------------------- 1 | # nginx相关配置说明 2 | - [nginx信号量](#nginx信号量) 3 | - [location](#location) 4 | - [rewrite重写](#rewrite重写) 5 | - [nginx防盗链](#nginx防盗链) 6 | - [nginx之gzip压缩提升网站速度](#nginx之gzip压缩提升网站速度) 7 | - [expires缓存提升网站负载](#expires缓存提升网站负载) 8 | - [nginx反向代理](#nginx反向代理) 9 | - [nginx实现负载均衡](#nginx实现负载均衡) 10 | 11 | ## nginx信号量 12 | 信号说明 13 | 14 | | 信号名称 | 作用 | 15 | | ------ | ------ | 16 | | TERM,INT | 快速关闭 | 17 | | QUIT | 从容关闭 | 18 | | HUP | 重新加载配置,用新的配置开始新的工作进程,从容关闭旧的工作进程 | 19 | | USR1 | 重新打开日志文件 | 20 | | USR2 | 平滑升级可执行程序 | 21 | | WINCH | 从容关闭工作进程 | 22 | 23 | #### hup信号优雅重启 24 | a.html 25 | ``` 26 | 27 | 这里是a.html文件 28 | 31 | 32 | ``` 33 | index.html 34 | ``` 35 | 36 | 这里是index.html文件 37 | 40 | 41 | ``` 42 | 43 | 44 | 查看当前nginx的配置文件 45 | ``` 46 | server{ 47 | listen 80; 48 | server_name localhost; 49 | root /Users/lidong/www; 50 | index index.html index.htm; 51 | access_log /Users/lidong/wwwlogs/access.log; 52 | error_log /Users/lidong/wwwlogs/error.log; 53 | } 54 | ``` 55 | 修改nginx的配置文件,将nginx设置为默认读取a.html 56 | ``` 57 | server{ 58 | listen 80; 59 | server_name localhost; 60 | root /Users/lidong/www; 61 | index a.html index.html index.htm; 62 | access_log /Users/lidong/wwwlogs/access.log; 63 | error_log /Users/lidong/wwwlogs/error.log; 64 | } 65 | ``` 66 | 67 | ``` 68 | ps aux|grep nginx 69 | lidong 5019 0.0 0.0 4339176 1136 ?? S 11:16上午 0:00.01 nginx: worker process 70 | lidong 352 0.0 0.0 4339176 1480 ?? S 五08上午 0:00.05 nginx: master process /usr/local/opt/nginx/bin/nginx -g daemon off; 71 | lidong 5284 0.0 0.0 4277252 824 s000 S+ 2:04下午 0:00.01 grep nginx 72 | ``` 73 | 通过ps命令得到nginx的master进程id为352,通过hup信号重启配置 74 | ``` 75 | kill -HUP 352 76 | ``` 77 | 打开浏览器不断观察发现使用信号HUP后会自动的跳转到a.html,我们并没有重启,而且发现不是立马的跳转是过几秒后跳转的,这就是优雅的重新读取nginx的配置文件,从容的关闭旧的进程。 78 | 79 | #### USR1重读日志 80 | ``` 81 | 82 | server{ 83 | listen 80; 84 | server_name localhost; 85 | root /Users/lidong/www; 86 | index index.html index.htm; 87 | access_log /Users/lidong/wwwlogs/access.log; 88 | error_log /Users/lidong/wwwlogs/error.log; 89 | } 90 | 91 | ``` 92 | ##### 刷新http://localhost/index.html 页面 93 | ``` 94 | 95 | 查看日志情况 96 | QiongdeMacBook-Pro:wwwlogs lidong$ ls -l 97 | total 80 98 | -rw-r--r-- 1 lidong staff 16201 3 30 14:36 access.log 99 | QiongdeMacBook-Pro:wwwlogs lidong$ mv access.log access.log.bak 100 | QiongdeMacBook-Pro:wwwlogs lidong$ ls -l 101 | total 88 102 | -rw-r--r-- 1 lidong staff 16410 3 30 14:42 access.log.bak 103 | ``` 104 | 从上面可以看出来虽然改变了log日志文件的名称,但是log日志还是在写入,出现这问题的原因linux中文件识别是以文件node的id来的。 105 | ##### 使用USR1信号用再次刷新 106 | ``` 107 | QiongdeMacBook-Pro:wwwlogs lidong$ kill -USR1 352 108 | QiongdeMacBook-Pro:wwwlogs lidong$ ls -l 109 | total 88 110 | -rw-r--r-- 1 lidong staff 0 3 30 14:49 access.log 111 | -rw-r--r-- 1 lidong staff 16410 3 30 14:42 access.log.bak 112 | -rw-r--r-- 1 lidong staff 252 3 30 14:28 error.log 113 | QiongdeMacBook-Pro:wwwlogs lidong$ ls -l 114 | total 96 115 | -rw-r--r-- 1 lidong staff 418 3 30 14:49 access.log 116 | -rw-r--r-- 1 lidong staff 16410 3 30 14:42 access.log.bak 117 | -rw-r--r-- 1 lidong staff 252 3 30 14:28 error.log 118 | ``` 119 | 通过USR1型号量来重读日志,继续刷新页面,会重新生成access.log日志文件,这个对于运维做日志的备份十分有作用。 120 | 这里有个小技巧,通过ps获取pid可以重新加载配置文件,平滑重启服务,但是感觉比较麻烦,我们可以使用如下方法操作. 121 | 查看配置文件知道nginx的pid存储在那个文件 122 | ``` 123 | kill -HUP `cat /usr/local/etc/nginx/nginx.pid` 124 | ``` 125 | 126 | #### USR2平滑升级 127 | 假设我们重新编译了新的版本的nginx,这个时候/usr/local/nginx/bin nginx 的版本就不是之前的版本了如果启动更新会报错。 128 | ``` 129 | kill -USR2 `cat /usr/local/etc/nginx/nginx.pid` 130 | ``` 131 | 这个时候使用这个命令来平滑升级nginx服务器 132 | 133 | ## location 134 | location有定位的意思,根据uri来进行不同的定位,在虚拟主机中是必不可少的,location可以网站的不同部分,定位到不同的处理方式上。 135 | * location匹配分类 136 | * 精准匹配 137 | * 一般匹配 138 | * 正则匹配 139 | 140 | #### 精准匹配 141 | ``` 142 | location = /index.htm { 143 | root /var/www/html/; 144 | index index.htm index.html; 145 | } 146 | 147 | location = /index.htm { 148 | root html/; 149 | index index.htm index.html; 150 | } 151 | ``` 152 | 153 | ``` 154 | 精准匹配的优先级要优于一般匹配,所以重启nginx后会打开/var/www/html下面的index.htm而不会打开html下的index.htm 155 | ``` 156 | 157 | #### 一般匹配 158 | ``` 159 | location / { 160 | root /usr/local/nginx/html; 161 | index index.htm index.html; 162 | } 163 | 164 | location /apis { 165 | root /var/www/html; 166 | index index.html; 167 | } 168 | ``` 169 | 170 | ``` 171 | 我们访问http://localhost/apis/ 172 | 对于uri的/apis,两个location的pattern都可以匹配它们 173 | 即‘/’能够左前缀匹配,"/apis"也能够左前缀匹配 174 | 但此时最终访问的是目录/var/www/html下的文件 175 | 因为apis/匹配的更长,因此使用该目录下的文件 176 | ``` 177 | 178 | #### 正则匹配 179 | ``` 180 | location / { 181 | root /usr/local/nginx/html; 182 | index index.html index.htm; 183 | } 184 | 185 | location ~ image { 186 | root /var/www/; 187 | index index.html; 188 | } 189 | 190 | ``` 191 | 192 | ``` 193 | 如果我们访问,http://localhost/image/logo.png 194 | 此时"/"与 location /匹配成功 195 | 此时"image"正则与"image/logo.png"也匹配成功?谁发挥作用呢? 196 | 正则表达式的成果将会使用,会覆盖前面的匹配 197 | 图片会真正的返回/var/www/image/logo.png 198 | ``` 199 | ### 总结 200 | * 1.先判断精准命中,如果命中立即返回结果并结束解析过程 201 | * 2.判断普通命中,如果有多个命中,记录下来最长的命中结果,(记录但不结束,最长的为准确) 202 | * 3.继续判断正则表达式的解析结果,按配置里的正则表达式顺序为准,由上到下开始匹配,一旦匹配成功一个,立即返回结果,并结束解析过程。 203 | * 4.普通命中顺序无所谓,按照命中的长短来确定 204 | * 5.正则命中有所谓,从前往后匹配命中 205 | 206 | ## rewrite重写 207 | ``` 208 | if ($remote_addr=192.168.0.200){ 209 | return 403; 210 | } 211 | 212 | if($http_user_agent ~ MSIE){ 213 | rewrite ^.*$ /ie.html; 214 | break; 215 | } 216 | 217 | if(!-e $document_root$fastcgi_script_name){ 218 | return ^.*$ /404.html break; 219 | } 220 | 221 | 222 | 223 | ``` 224 | 225 | ## nginx防盗链 226 | *** 227 | - [什么是防盗链](#什么是防盗链) 228 | - [nginx防盗链](#nginx防盗链) 229 | - [实例演示](#实例演示) 230 | 231 | ### 什么是防盗链 232 | 233 | 防盗链简而言之就是防止第三方或者未进允许的域名访问自己的静态资源的一种限制技术。比如A网站有许多自己独立的图片素材不想让其它网站通过直接调用图片路径的方式访问图片,于是采用防盗链方式来防止。 234 | 235 | ### nginx防盗链 236 | 237 | 防盗链基于客户端携带的referer实现,referer是记录打开一个页面之前记录是从哪个页面跳转过来的标记信息,如果别人只链接了自己网站的图片或某个单独的资源,而不是打开整个页面,这就是盗链,referer就是之前的那个网站域名,正常的referer信息有以下几种 238 | 239 | #### nginx防盗链的代码定义 240 | - 定义合规的引用 241 | ``` 242 | valid_referers none | blocked | server_names | string ...; 243 | ``` 244 | 245 | - 拒绝不合规的引用: 246 | ``` 247 | if ($invalid_referer) { 248 | rewrite ^/.*$ http://www.b.org/403.html 249 | } 250 | ``` 251 | 252 | #### 参数说明: 253 | 254 | - none:请求报文没有referer首部,比如用户直接在浏览器输入域名访问往web网站,就是没有referer信息 255 | - blocked:请求报文由referer信息,但无又有效值为空 256 | - server_names:referer首部中包含本主机及nginx监听的server_name 257 | - invalid_referer:不合规的feferer引用 258 | 259 | ### 实例演示 260 | |图片源地址|调用图片地址| 261 | |:---- |:---| 262 | |dev.api.dd.com |localhost | 263 | 264 | #### 测试页面index.html 265 | 266 | ``` 267 | 268 | 269 | 270 | 271 | 演示nginx防盗链 272 | 273 | 274 | 275 | 276 | 277 | ``` 278 | 279 | #### 正常配置nginx不做防盗链处理 280 | ``` 281 | server { 282 | listen 80; 283 | server_name dev.api.dd.com; 284 | root /Users/lidong/Desktop/wwwroot/dd_api/public; 285 | index index.php index.html index.htm; 286 | access_log /Users/lidong/wwwlogs/dev.api.dd.com_access.log; 287 | error_log /Users/lidong/wwwlogs/dev.api.dd.com_error.log; 288 | location ~ [^/]\.php(/|$) { 289 | fastcgi_pass 127.0.0.1:9000; 290 | fastcgi_index index.php; 291 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 292 | include fastcgi_params; 293 | } 294 | 295 | location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ { 296 | } 297 | 298 | try_files $uri $uri/ @rewrite; 299 | location @rewrite { 300 | rewrite ^/(.*)$ /index.php?_url=/$1; 301 | } 302 | 303 | }` 304 | ``` 305 | #### 运行http://localhost/index.html结果 306 | ![avatar](./images/ok.png) 307 | 308 | #### 配置限定的资源文件如果被第三方调用直接返回403 309 | ``` 310 | server { 311 | listen 80; 312 | server_name dev.api.dd.com; 313 | root /Users/lidong/Desktop/wwwroot/dd_api/public; 314 | index index.php index.html index.htm; 315 | access_log /Users/lidong/wwwlogs/dev.api.dd.com_access.log; 316 | error_log /Users/lidong/wwwlogs/dev.api.dd.com_error.log; 317 | location ~ [^/]\.php(/|$) { 318 | fastcgi_pass 127.0.0.1:9000; 319 | fastcgi_index index.php; 320 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 321 | include fastcgi_params; 322 | } 323 | 324 | location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ { 325 | valid_referers none blocked dev.api.dd.com; 326 | if ($invalid_referer) 327 | { 328 | return 403; 329 | } 330 | } 331 | 332 | try_files $uri $uri/ @rewrite; 333 | location @rewrite { 334 | rewrite ^/(.*)$ /index.php?_url=/$1; 335 | } 336 | 337 | } 338 | ``` 339 | #### 运行http://localhost/index.html结果 340 | ![avatar](./images/403.png) 341 | 342 | #### 配置限定的资源文件如果被第三方调用直接返回一张404的图片 343 | ``` 344 | server { 345 | listen 80; 346 | server_name dev.api.dd.com; 347 | root /Users/lidong/Desktop/wwwroot/dd_api/public; 348 | index index.php index.html index.htm; 349 | access_log /Users/lidong/wwwlogs/dev.api.dd.com_access.log; 350 | error_log /Users/lidong/wwwlogs/dev.api.dd.com_error.log; 351 | location ~ [^/]\.php(/|$) { 352 | fastcgi_pass 127.0.0.1:9000; 353 | fastcgi_index index.php; 354 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 355 | include fastcgi_params; 356 | } 357 | 358 | location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ { 359 | valid_referers none blocked dev.api.dd.com; 360 | if ($invalid_referer) 361 | { 362 | rewrite ^/ http://dev.api.dd.com/404.jpeg; 363 | } 364 | } 365 | 366 | try_files $uri $uri/ @rewrite; 367 | location @rewrite { 368 | rewrite ^/(.*)$ /index.php?_url=/$1; 369 | } 370 | 371 | } 372 | ``` 373 | #### 运行http://localhost/index.html结果 374 | 调用的图片显示302 375 | ![avatar](./images/302.png) 376 | 用一张源站的404替换显示 377 | ![avatar](./images/404.png) 378 | 379 | ## nginx反向代理 380 | 跨域:浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域 。 381 | 382 | 下表格为前后端分离的域名,技术信息: 383 | 384 | | 前后端 | 域名 | 服务器 | 使用技术 | 385 | | ------ | ------ | ------ | ------ | 386 | | 前端 | http://b.yynf.com | nginx | vue框架 | 387 | | 后端 | http://api.yynf.com | nginx | php | 388 | 389 | 390 | 两种方式解决跨域的问题: 391 | 392 | 解决方法一: 393 | 394 | 在php入口index.php文件加入header头代码,允许访问解决了js调用api跨域的问题。 395 | 396 | ``` 397 | header("Access-Control-Allow-Origin: *"); 398 | header("Access-Control-Allow-Headers: Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With,api-key"); 399 | header("Access-Control-Allow-Method: GET, POST, OPTIONS, HEAD"); 400 | header("Access-Control-Allow-Credentials: true"); 401 | ``` 402 | 403 | 404 | 解决方法二: 405 | 406 | 使用nginx的反向代理解决跨域: 407 | 408 | api的nginx配置不需要改变只需要改变前端的服务器的nginx配置即可: 409 | ``` 410 | location /apis { 411 | rewrite ^.+apis/?(.*)$ /$1 break; 412 | include uwsgi_params; 413 | proxy_pass http://api.yynf.com; 414 | } 415 | ``` 416 | 417 | proxy_pass url地址 418 | 419 | 让nginx监控/apis目录(这里自己定义只要跟nginx配置中保持一致即可),如果发现了这个目录就将所有请求代理到http://api.yynf.com这个请求中,当然也需要在js调用api的请求中多加一层请求结构: 420 | 前端代码中js请求地址 421 | - 旧的js请求api的地址 http://api.yynf.com/badmin/user/add 422 | - 新的js请求api的地址 http://api.yynf.com/apis/badmin/user/add 423 | 424 | 这样一来访问页面就会发现前端代码调用api地址都转向了http://api.yynf.com/apis/,利用将请求通过服务器内部代理实现了跨域问题。 425 | 代理解决跨域的优点: 426 | - 1.有效的隐藏实际api的请求地址和服务器的ip地址 427 | - 2.各司其职让前后端更方便管理,个自搭建自己的服务器保持一定的规范即可。 428 | 429 | ## nginx实现负载均衡 430 | 负载均衡:针对web负载均衡简单的说就是将请求通过负债均衡软件或者负载均衡器将流量分摊到其它服务器。 431 | 负载均衡的分类如下图: 432 | ![avatar](./images/nginx-fz.png) 433 | 434 | 今天分享一下nginx实现负载均衡的实现,操作很简单就是利用了nginx的反向代理和upstream实现: 435 | 436 | 437 | | 服务器名称 | 地址 | 作用 | 438 | | ------ | ------ | ------ | 439 | | A服务器 | 192.168.0.212 | 负载均衡服务器 | 440 | | B服务器 | 192.168.0.213 | 后端服务器 | 441 | | C服务器 | 192.168.0.215 | 后端服务器 | 442 | 443 | 444 | ### A服务器nginx配置如下: 445 | ``` 446 | 1 upstream apiserver { 447 | 2 server 192.168.0.213:8081 weight=1 max_fails=2 fail_timeout=3; 448 | 3 server 192.168.0.215:8082 weight=1 max_fails=2 fail_timeout=3; 449 | 4 } 450 | 5 451 | 6 server { 452 | 7 listen 80; 453 | 8 server_name api.test.com; 454 | 9 455 | 10 location / { 456 | 11 proxy_pass http://apiserver; 457 | 12 458 | 13 } 459 | 14 460 | 15 location ~ /\.ht { 461 | 16 deny all; 462 | 17 } 463 | 18 } 464 | ``` 465 | 466 | ### B服务器配置如下: 467 | ``` 468 | 1 server { 469 | 2 listen 8081; 470 | 3 server_name 192.168.0.213; 471 | 4 set $root_path '/data/wwwroot/Api/public/'; 472 | 5 root $root_path; 473 | 6 index index.php index.html index.htm; 474 | 7 access_log /data/wwwlogs/access_log/api.8081.log; 475 | 8 try_files $uri $uri/ @rewrite; 476 | 9 location @rewrite { 477 | 10 rewrite ^/(.*)$ /index.php?_url=/$1; 478 | 11 } 479 | 12 480 | 13 location ~ \.php { 481 | 14 fastcgi_pass 127.0.0.1:9000; 482 | 15 fastcgi_index index.php; 483 | 16 include /usr/local/nginx/conf/fastcgi_params; 484 | 17 fastcgi_param PHALCON_ENV dev; 485 | 18 fastcgi_split_path_info ^(.+\.php)(/.+)$; 486 | 19 fastcgi_param PATH_INFO $fastcgi_path_info; 487 | 20 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 488 | 21 } 489 | 22 } 490 | ``` 491 | 492 | ### C服务器配置如下: 493 | 494 | ``` 495 | server { 496 | listen 8082; 497 | server_name 192.168.0.215; 498 | set $root_path '/data/wwwroot/Api/public/'; 499 | root $root_path; 500 | index index.php index.html index.htm; 501 | access_log /data/wwwlogs/access_log/api.8081.log; 502 | try_files $uri $uri/ @rewrite; 503 | location @rewrite { 504 | rewrite ^/(.*)$ /index.php?_url=/$1; 505 | } 506 | 507 | location ~ \.php { 508 | fastcgi_pass 127.0.0.1:9000; 509 | fastcgi_index index.php; 510 | include /usr/local/nginx/conf/fastcgi_params; 511 | fastcgi_param PHALCON_ENV dev; 512 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 513 | fastcgi_param PATH_INFO $fastcgi_path_info; 514 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 515 | } 516 | } 517 | ``` 518 | 519 | 520 | 到期负载均衡搭建完成,测试的可以访问搭建的域名地址,然后在对应的后端服务器打印access的log日志进行查看请求是否在轮询服务器。 521 | 522 | 思考:负载均衡搭建是搭建成功了,但是也有问题 523 | - 1.这样的架构会出现session无法共享的问题? 524 | - 2.如果其中有一台后端服务器宕机了怎么处理? 525 | 这些问题后面会有文章进行说明 526 | 527 | 528 | ## nginx之gzip压缩提升网站速度 529 | *** 530 | ### 为啥使用gzip压缩 531 | 开启nginx的gzip压缩,网页中的js,css等静态资源的大小会大大的减少从而节约大量的带宽,提高传输效率,给用户快的体验。 532 | 533 | ### nginx实现gzip 534 | nginx实现资源压缩的原理是通过默认集成的ngx_http_gzip_module模块拦截请求,并对需要做gzip的类型做gzip,使用非常简单直接开启,设置选项即可。。 535 | 536 | gzip生效后的请求头和响应头 537 | 538 | ``` 539 | Request Headers: 540 | Accept-Encoding:gzip,deflate,sdch 541 | 542 | Response Headers: 543 | Content-Encoding:gzip 544 | Cache-Control:max-age240 545 | ``` 546 | 547 | gzip的处理过程 548 | 549 | 从http协议的角度看,请求头声明acceopt-encoding:gzip deflate sdch(是指压缩算法,其中sdch是google自己家推的一种压缩方式) 550 | 服务器-〉回应-〉把内容用gzip压缩-〉发送给浏览器-》浏览器解码gzip->接收gzip压缩内容 551 | 552 | #### gzip的常用配置参数: 553 | - gzip on|off  是否开启gzip 554 | - gzip_buffers  4k  缓冲(压缩在内存中缓冲几块?每块多大?) 555 | - gzip_comp_level [1-9]   推荐6  压缩级别,级别越高压缩的最小,同时越浪费cpu资源 556 | - gzip_disable   正则匹配UA是什么样的URi不进行gzip 557 | - gzip_min_length  200开始压缩的最小长度,小于这个长度nginx不对其进行压缩 558 | - gzip_http_version  1.0|1.1开始压缩的http协议版本(默认1.1) 559 | - gzip_proxied  设置请求者代理服务器,该如何缓存内容 560 | - gzip_types  text/plain  application/xml  对哪些类型的文件用压缩如txt,xml,html,css 561 | - gzip_vary  off 是否传输gzip压缩标志 562 | 563 | #### nginx配置gzip 564 | 565 | 静态页面index.html 566 | 567 | ``` 568 | 569 | 570 | 571 | 572 | 演示nginx做gzip压缩 573 | 574 | 575 | 576 | 577 |

nginx实现gzip压缩,减少带宽的占用,同时提升网站速度

578 |

nginx实现gzip压缩,减少带宽的占用,同时提升网站速度

579 |

nginx实现gzip压缩,减少带宽的占用,同时提升网站速度

580 |

nginx实现gzip压缩,减少带宽的占用,同时提升网站速度

581 |

nginx实现gzip压缩,减少带宽的占用,同时提升网站速度

582 |

nginx实现gzip压缩,减少带宽的占用,同时提升网站速度

583 | 584 | 585 | ``` 586 | 587 | nginx的配置 588 | 589 | ``` 590 | server{ 591 | listen 80; 592 | server_name localhost 192.168.0.96; 593 | 594 | gzip on; 595 | gzip_buffers 32 4k; 596 | gzip_comp_level 6; 597 | gzip_min_length 200; 598 | gzip_types application/javascript application/x-javascript text/javascript text/xml text/css; 599 | gzip_vary off; 600 | 601 | root /Users/lidong/Desktop/wwwroot/test; 602 | 603 | index index.php index.html index.htm; 604 | 605 | access_log /Users/lidong/wwwlogs/access.log; 606 | error_log /Users/lidong/wwwlogs/error.log; 607 | 608 | location ~ [^/]\.php(/|$) { 609 | fastcgi_pass 127.0.0.1:9000; 610 | fastcgi_index index.php; 611 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 612 | include fastcgi_params; 613 | } 614 | 615 | } 616 | ``` 617 | 618 | 为使用gzip前的页面请求: 619 | ![avatar](./images/nginx_gzip1.png) 620 | 621 | 开启了gzip页面的请求: 622 | ![avatar](./images/nginx_gzip2.png) 623 | ![avatar](./images/nginx_gzip2.2.png) 624 | 625 | #### 注意 626 | - 图片,mp3一般不需要压缩,因为压缩率比较小 627 | - 一般压缩text,css,js,xml格式的文件 628 | - 比较小的文件不需要压缩,有可能还会比源文件更大 629 | - 二进制文件不需要压缩 630 | 631 | ## expires缓存提升网站负载 632 | *** 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | -------------------------------------------------------------------------------- /oop.md: -------------------------------------------------------------------------------- 1 | # php面相对象知识整理 2 | * [对象引用](#对象引用) 3 | * [访问控制private](#访问控制private) 4 | * [访问控制之继承](#访问控制之继承) 5 | * [对象遍历](#对象遍历) 6 | 7 | ## 访问控制private 8 | #### 私有属性内部调用 9 | ``` 10 | name = $name; 21 | $this->age = $age; 22 | $this->status = $status; 23 | } 24 | 25 | /** 26 | * @desc 获取用户信息 27 | **/ 28 | public function getUserInfo() { 29 | return $this->name . "今年" . $this->age . "岁," . '婚姻状态:' . $this->status; 30 | } 31 | 32 | //给私有属性变量赋值 33 | public function __set($key, $value) { 34 | if ($key == 'age') { 35 | $this->$key = $value + 2; 36 | } else { 37 | $this->$key = $value; 38 | } 39 | } 40 | 41 | //获取私有变量 42 | public function __get($key) { 43 | return $this->$key; 44 | } 45 | 46 | } 47 | 48 | $obj = new Person("巴八灵", 28, "已婚"); 49 | echo $obj->getUserInfo(); 50 | /** 运行结果 51 | 巴八灵今年28岁,婚姻状态:已婚 52 | **/ 53 | 54 | ``` 55 | #### 私有属性外部调用 56 | ``` 57 | name . "今年" . $this->age . "岁," . '婚姻状态:' . $this->status; 71 | } 72 | 73 | //给私有属性变量赋值 74 | public function __set($key, $value) { 75 | if ($key == 'age') { 76 | $this->$key = $value + 2; 77 | } else { 78 | $this->$key = $value; 79 | } 80 | } 81 | 82 | //获取私有变量 83 | public function __get($key) { 84 | return $this->$key; 85 | } 86 | 87 | } 88 | 89 | $obj = new Person(); 90 | $obj->name = '巴八灵'; 91 | $obj->age = 28; 92 | $obj->status = "已婚"; 93 | echo "age设置后的值" . $obj->age . PHP_EOL; 94 | echo $obj->getUserInfo(); 95 | 96 | /** 运行后结果 97 | age设置后的值30 98 | 巴八灵今年30岁,婚姻状态:已婚 99 | **/ 100 | 101 | ``` 102 | 103 | ## 访问控制之继承 104 | #### 继承类属性权限设置 105 | ``` 106 | name.',brand:'.$this->brand.',price:'.$this->price; 116 | } 117 | 118 | } 119 | 120 | class Bm extends Car{ 121 | 122 | protected $price = "30w"; 123 | public $name = '普通车2'; 124 | protected $brand = "宝马"; 125 | 126 | public function __construct(){ 127 | $this->getInfo(); 128 | } 129 | 130 | private function getInfo(){ 131 | echo "name:".$this->name.',brand:'.$this->brand.',price:'.$this->price; 132 | } 133 | 134 | } 135 | 136 | $bm = new Bm(); 137 | ``` 138 | #### 结果说明 139 | ``` 140 | Fatal error: Access level to Bm::getInfo() must be protected (as in class Car) or weaker in /Users/lidong/www/test/march/27/Car.php on line 29 141 | [Finished in 0.1s] 142 | ``` 143 | >总结:子类的属性和方法的访问控制权限应该跟父类保持一致或者更加宽松。 144 | 145 | ## 对象遍历 146 | ``` 147 | $value) { 165 | echo $key . '--' . $value . PHP_EOL; 166 | } 167 | /** 168 | 输出结果 169 | val1--value1 170 | val2--value2 171 | val3--value3 172 | **/ 173 | ``` 174 | 175 | ## 对象引用 176 | #### 对象赋值 177 | ``` 178 | brand = '宝马'; 194 | $car1->brand = '奥迪'; 195 | echo $car1->brand.PHP_EOL; 196 | echo $car2->brand.PHP_EOL; 197 | ``` 198 | 结果为: 199 | ``` 200 | object(Car)#1 (1) { 201 | ["brand"]=> 202 | NULL 203 | } 204 | object(Car)#1 (1) { 205 | ["brand"]=> 206 | NULL 207 | } 208 | object(Car)#2 (1) { 209 | ["brand"]=> 210 | NULL 211 | } 212 | 奥迪 213 | 奥迪 214 | ``` 215 | > 赋值后发现对象都为#1标示,再重新创建一个对象实例变量对象标示变为了#2,同一个标示指向相同的信息修改会影响 216 | #### 赋值引用对象 217 | ``` 218 | brand = '奥迪'; 228 | echo $car2->brand.PHP_EOL; 229 | echo $car1->brand.PHP_EOL; 230 | 231 | ``` 232 | 运行结果为: 233 | ``` 234 | object(Car)#1 (1) { 235 | ["brand"]=> 236 | NULL 237 | } 238 | object(Car)#1 (1) { 239 | ["brand"]=> 240 | NULL 241 | } 242 | 奥迪 243 | 奥迪 244 | ``` 245 | > 赋值引用对象都指向同一个对象地址,修改赋值相互有影响 -------------------------------------------------------------------------------- /redis.md: -------------------------------------------------------------------------------- 1 | # redis信息整理 2 | * 事务 3 | * redis数据开发设计 4 | * 主从复制 5 | * 集群分片 6 | * 数据备份策略 7 | * 常见reds错误分析 8 | * 监控redis的服务状态 9 | * 可视化管理工具 10 | * [redis防止商品超发](#redis防止商品超发) 11 | * redis持久化 12 | 13 | ## redis防止商品超发 14 | #### 公用方法 function.php 15 | ``` 16 | handler = new Redis(); 41 | $this->handler->connect($conf['host'], $conf['port']); //连接Redis 42 | //设置密码 43 | if(isset($conf['auth'])){ 44 | $this->handler->auth($conf['auth']); //密码验证 45 | } 46 | //选择数据库 47 | if(isset($conf['db'])){ 48 | $this->handler->select($conf['db']);//选择数据库2 49 | }else{ 50 | $this->handler->select(0);//默认选择0库 51 | } 52 | } 53 | 54 | //获取key的值 55 | public function get($name){ 56 | return $this->handler->get($name); 57 | } 58 | 59 | //设置key的值 60 | public function set($name,$value){ 61 | return $this->handler->set($name,$value); 62 | } 63 | 64 | //判断key是否存在 65 | public function exists($key){ 66 | if($this->handler->exists($key)){ 67 | return true; 68 | } 69 | return false; 70 | } 71 | 72 | //当key不存在的设置key的值,存在则不设置 73 | public function setnx($key,$value){ 74 | return $this->handler->setnx($key,$value); 75 | } 76 | 77 | //将key的数值增加指定数值 78 | public function incrby($key,$value){ 79 | return $this->handler->incrBy($key,$value); 80 | } 81 | 82 | } 83 | ``` 84 | 85 | #### 抢购业务实现 index.php 86 | ``` 87 | conf = $conf; 101 | if(empty($type)) 102 | return ''; 103 | if($type==self::V1){ 104 | $this->way1(self::V1); 105 | }elseif($type==self::V2){ 106 | $this->way2(self::V2); 107 | }else{ 108 | return ''; 109 | } 110 | } 111 | 112 | //抢购商品方式一 113 | protected function way1($v){ 114 | $redis = new myRedis($this->conf); 115 | $keyNmae = getKeyName($v); 116 | if(!$redis->exists($keyNmae)){ 117 | $redis->set($keyNmae,0); 118 | } 119 | $currAmount = $redis->get($keyNmae); 120 | if(($currAmount+self::INCRAMOUNT)>self::AMOUNTLIMIT){ 121 | writeLog("没有抢到商品",$v); 122 | return; 123 | } 124 | $redis->incrby($keyNmae,self::INCRAMOUNT); 125 | writeLog("抢到商品",$v); 126 | } 127 | 128 | //抢购商品方式二 129 | protected function way2($v){ 130 | $redis = new myRedis($this->conf); 131 | $keyNmae = getKeyName($v); 132 | if(!$redis->exists($keyNmae)){ 133 | $redis->setnx($keyNmae,0); 134 | } 135 | if($redis->incrby($keyNmae,self::INCRAMOUNT) > self::AMOUNTLIMIT){ 136 | writeLog("没有抢到商品",$v); 137 | return; 138 | } 139 | writeLog("抢到商品",$v); 140 | } 141 | 142 | } 143 | 144 | //实例化调用对应执行方法 145 | $type = isset($_GET['v'])?$_GET['v']:'way1'; 146 | $conf = [ 147 | 'host'=>'192.168.0.214','port'=>'6379', 148 | 'auth'=>'test','db'=>2, 149 | ]; 150 | new sendAward($conf,$type); 151 | /*** 152 | 通过ab工具压力测试模拟超发的情况,再结合日志打印的数据说明方法可以有效的防止超发 153 | ab -c 100 -n 200 http://192.168.0.213:8083/index.php?v=way2 154 | ab -c 100 -n 200 http://192.168.0.213:8083/index.php?v=way2 155 | **/ 156 | ``` 157 | 158 | -------------------------------------------------------------------------------- /senior.md: -------------------------------------------------------------------------------- 1 | # 高并发大流量web解决思路及方案 2 | + [高并发web架构相关概念](#高并发web架构相关概念) 3 | + [高并发大流量web整体解决思路](#高并发大流量web整体解决思路) 4 | + [web服务器负载均衡](#web服务器负载均衡) 5 | + [cdn加速](#cdn加速) 6 | + [建立独立的图片服务器](#建立独立的图片服务器) 7 | + [动态页面静态化](#动态页面静态化) 8 | + [php并发编程实战](#php并发编程实战) 9 | + [mysql数据层的优化](#mysql数据层的优化) 10 | + [mysql缓存层的优化](#mysql缓存层的优化) 11 | 12 | ## 高并发web架构相关概念 13 | - QPS:每秒钟请求或查询的数量,在互联网领域,指每秒相应请求数(http请求)。 14 | - 峰值每秒的QPS:(总PV数*80%)/(6小时秒数*20%),80%的访问量集中在20%的时间 15 | - 并发连接数:系统同时处理的请求数量 16 | - 吞吐量:单位时间内处理的请求数量(通常由QPS与并发数决定)。 17 | - 响应时间:从请求发出到收到响应花费的时间。例如系统处理一个HTTP请求需要100ms,这个100ms就是系统的响应时间。 18 | - PV:综合浏览量(Page View)即页面浏览量和点击量,一个访客在24小时内访问的页面数量。 19 | - UV:独立访客(UniQue Visitor),即一定时间范围内相同访客多次访问网站,只计算为1个独立访客。 20 | - 带宽:计算带宽大小关注两个指标,峰值流量和平均页面大小。 21 | - 日网站带宽=PV/统计时间(一天换算到秒)*平均页面大小(单位KB)*8。 22 | - 压力测试:测试承受的最大并发,测试最大承受的QPS,需要注意的测试并发测试机需要与被测试机器分开,不要对线上服务器进行并发测试,观察ab测试的所在机器,以及被测试机器的前端机的CPU,内存,网络等都不超过最高限度的75%。 23 | - 并发量 24 | - 响应速度 25 | - 容错能力 26 | - 常用的性能测试工具:ab,wrk,http_load,Web Bench,Siege,Apache JMeter。 27 | 28 | ## 高并发大流量web整体解决思路 29 | - 流量优化 30 | - web资源防盗链防止第三方系统盗用图片,css,js等占用服务器流量和服务器带宽 31 | - 前端优化 32 | - 减少http请求:图片合并,js合并,css合并压缩,虽然文件可能大点但请求会减少 33 | - 添加异步请求:通过实际ajax调用接口获取数据 34 | - 启动浏览器的缓存和文件压缩(也可以启用nginx的压缩模块) 35 | - cdn加速:解决带宽不够用的问题,数据缓存到cdn的节点,访问的时候选择就近的节点,减少带宽加快访问速度 36 | - 建立独立的图片服务器:图片是很吃io的,可以将图片服务器与web服务器完全分离开,可以区分其它服务器单独搭建图片服务器不属于计算型的配置可以适当的调整,图片服务器还可以集群 37 | - 服务端的优化 38 | - 页面的静态化:动态的页面静态html,减少服务器的负载压力,页面静态化穿透,静态化有有效时间 39 | - 动态语言并发处理:异步处理,多线程,队列的异步处理 40 | - 数据库的优化: 41 | - 数据库的缓存:memcache,redis的缓存 42 | - mysql索引优化,mysql分库分表,mysql分区操作,mysql主从复制读写分离,mysql的负载均衡,mysql的主从热备 43 | - web服务器的优化: 44 | - 负载均衡:可以使用ningx的反向代理使用负载均衡,可以使用网络分层中的第四层lvs实现负载均衡 45 | 46 | ### web服务器负载均衡 47 | #### 负债均衡 48 | - 四层负载均衡:所谓四层负载均衡就是基于IP+端口的负载均衡 49 | - 七层负载均衡:所谓七层的负载均衡就是基于(URL)信息的负载均衡 50 | 51 | #### 七层负载均衡实现: 52 | 基于URL等应用层信息的负债均衡 53 | ningx的proxy是它一个很强大的功能,实现了7层负载均衡,功能强大,性能卓越,运行稳定,配置简单灵活,能够自动剔除工作不正常的后端服务器,上传文件可以使用异步模式上传,支持多种分配策略,可以分配权重,分配方式灵活。 54 | 55 | #### nginx负载均衡策略 56 | - IP Hash(内置) 57 | - 加权轮询(内置) 58 | - fair策略(扩展) 59 | - 通用hash(扩展) 60 | - 一致性hash(扩展) 61 | 62 | ##### IP Hash策略 63 | nginx内置的另一个负载均衡的策略,流程和轮询很相似,只是其中的算法和具体的策略有些变化,IP hash算法是一种变相的轮询算法 64 | 65 | ##### 加权轮训策略 66 | 首先将请求都分给高权重的机器,直到该机器的权值降到了比其他机器低,才开始将请求分给下一个高权重的机器,当所有后端机器都down掉时,nginx会立即将所有机器的标志位清成初始状态,以避免造成所有的机器都处在timeout的状态 67 | 68 | ##### fair策略 69 | 根据后端服务器的响应时间判断负载情况,从中选出负载最轻的机器进行分流 70 | 71 | 通用hash、一致性hash策略,通用hash比较简单,可以以nginx内置的变量为key进行hash,一致性hash采用了内置的一致性hash环,支持memcache 72 | 73 | #### 四层负载均衡实现 74 | 通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器 75 | 76 | lvs相关术语: 77 | 78 | - DS:director server 目标服务器,即负载均衡器 79 | - RS:Real Server 真实服务器,即后端服务器 80 | - VIP:直接面向用户的IP地址,通常为公网IP地址 81 | - DIP:Director Server Ip主要用于内部主机通信的IP地址 82 | - RIP:Real Server IP 后端真实服务器的IP地址 83 | - CIP:Client IP 84 | 85 | lvs负载均衡三种方式: 86 | 87 | - NAT:修改目标IP地址为后端的RealServer的IP地址 88 | - DR:修改目标mac地址为后端的RealServer的mac地址 89 | - TUNNEL:较少使用,常用于异地容灾 90 | 91 | 92 | #### 四七层负载均衡优缺点 93 | - 四层比七层可以承载更大的并发量,使用大型站点小 94 | - 七层可以实现更为复杂的负载均衡控制,比如URL、基于session、动静分离等 95 | - 七层能够占用大量的CPU时间,承载的并发量 96 | 97 | ## cdn加速 98 | #### 什么是cdn? 99 | 100 | 节点:可以理解为真实服务器的镜像。 101 | 102 | 全称是Content Delivery Network,即内容分发网络尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。 103 | 104 | 在网络各处放置节点服务器所构成的现有的互联网基础之上的一层智能虚拟网络。 105 | 106 | cdn系统能够实时地根据网络流量和各节点的连接,负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。 107 | 108 | #### cdn的优势是什么? 109 | - 1.本地的cache加速,提高企业站点(尤其含有大量图片和静态页面站点)的访问速度 110 | - 2.跨运营商的网络加速,保证不同网络的用户得到良好的访问质量 111 | - 3.远程访问用户根据DNS负载均衡技术智能自动选择Cache服务器 112 | - 4.自动生成服务器的远程Mirror(镜像)cache服务器,远程用户访问时从cache服务器上读取数据,减少远程访问的带宽,分担网络流量,减轻愿站点web服务器负载等功能。 113 | - 5.广泛分布的cdn节点加上节点之间的智能冗余机制,可以有效的预防黑客入侵 114 | 115 | #### cdn的工作原理是什么? 116 | 117 | 传统的访问:用户在浏览器输入域名发起请求,解析域名获取服务器ip地址,根据ip地址找到对应的服务器,服务器响应并返回数据。 118 | 119 | 使用cdn访问:用户发起请求,智能dns的解析(根据ip判断地理位置,接入网类型,选择路由最短和负载最轻的服务器),取得缓存服务器ip,把内容返回给用户(如果缓存中有),向源站发起请求,将结果访问给用户,将结果存入缓存服务器。 120 | 121 | #### cdn的适用场景? 122 | 站点或者应用中大量静态资源的加速分发,例如:css,js,图片和html 123 | 124 | #### cdn的实现方式? 125 | - BAT等实现的CDN服务 126 | - 使用LVS的4层负载均衡 127 | - 可用nginx,varnish,squid,apache trafficServer做七层负载均衡和cache 128 | 使用squid做反向代理或者nginx做反向代理 129 | 130 | ## 建立独立的图片服务器 131 | 独立的必要性? 132 | 133 | - 1.分担web服务器的I/O负载,将耗费资源的图片服务分离出来,提高服务器的性能和稳定性 134 | - 2.能够专门对图片服务器进行优化,为图片服务设置针对性的缓存方案,减少带宽成本,提高访问速度 135 | 136 | 为啥采用独立的域名? 137 | 138 | 原因:同一域名下浏览器的并发连接数是有限制的,突破浏览器连接的限制,由于cookie的原因,对缓存不利,大部分web cache都只缓存不带cookie的请求,导致每次的图片请求都不能够命中cache 139 | 140 | 独立后的问题? 141 | 142 | - 如何进行图片上传和图片同步 143 | - NPS共享方式 144 | - 利用FTP同步 145 | 146 | ## 动态页面静态化 147 | 相关概念:什么是动态语言静态化,为什么要静态化,静态化的实现方式。 148 | 149 | ## 动态语言的并发处理 150 | ### 什么是进程 151 | 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础 152 | 153 | 进程是一个“执行中的程序” 154 | 155 | #### 进程的状态的三态模型 156 | 多道程序系统中,进程在处理器上交替运行,状态不断发生变化。 157 | 158 | - 运行:当一个进程在处理机上运行时,则称该进程处于运行状态。处于此状态的进程的数目小于等于处理器的数目,对于单处理机系统,处于运行状态的进程只有一个。在没有其它进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。 159 | - 就绪:当一个进程获得了除处理机以外的一切所有资源,一旦得到处理机即可运行,则称此进程处于就绪状态。就绪状态可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。 160 | - 阻塞:也称为等待或睡眠状态,一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。 161 | 162 | ### 什么是线程 163 | 由于用户的并发请求,为每一个请求都创建一个进程显然是行不通的,从系统资源开销方面或是响应用户请求的效率方面来看。因此操作系统中线程的概念便被引进了。 164 | 165 | 线程有时候被称为轻量级进程,是程序执行流的最小单元。 166 | 167 | 线程是进程中的一个实体,是被系统独立调度和分配的基本单位,线程自己不拥有系统资源,只拥有一点儿运行中必不可少的资源但它可与同属一个进程的其它线程共享进程所拥有的全部资源。 168 | 169 | 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。 170 | 171 | 线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派cpu的基本单位指运行中的程序的调度单位。 172 | 173 | #### 线程三状态 174 | - 就绪状态:线程具备运行的所有条件,逻辑上可以运行,在等待处理机。 175 | - 运行状态:线程占有处理机正在运行。 176 | - 阻塞状态:线程在等待一个事件(如某个信号量),逻辑上不可执行。 177 | 178 | ### 什么是协程 179 | 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协称调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切的开销,可以不要加锁的访问全局变量,所以上下文的切换非常快。 180 | 181 | ### 线程和进程的区别? 182 | - 1.线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间。 183 | - 2.进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源。 184 | - 3.线程是处理器调度的基本单位,但进程不是 185 | - 4.二者都可以并发的执行 186 | - 5.每个独立的线程有一个程序运行的入口,顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 187 | 188 | ### 线程和协程的区别? 189 | - 1.一个线程可以多个协程,一个进程也可以单独拥有多个协程 190 | - 2.线程进程都是同步机制,而协程则是异步 191 | - 3.协称能够保留上一次调用时的状态,每次过程重入的时,就相当于进入上一次调用的状态 192 | 193 | ### 什么是多进程? 194 | 同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这就是多进程 195 | 多开一个进程,多分配一份资源,进程间通讯不方便 196 | 197 | ### 什么是多线程? 198 | 线程就是把一个进程分为很多片,每一片都可以是一个独立的流程,与多进程的区别是只会使用一个进程的资源,线程间可以通讯 199 | 200 | ### 多个概念之间的区别? 201 | - 单进程单线程:一个人在一个桌上吃菜 202 | - 单进程多线程:多个人在一个桌子上吃菜 203 | - 多进程单线程:多个人每个人在自己桌子上吃菜 204 | 205 | ### 同步阻塞模型 206 | 多进程:最早的服务器端程序都是通过多进程,多线程来解决并发IO的问题一个请求创建一个进程,然后子进程进入循环同步堵塞地与客户端连接进行交互,收发处理数据。 207 | 208 | #### 步骤 209 | - 创建一个socket 210 | - 进入while循环,阻塞在进程accept操作上,等待客户端连接进入主进程在多进程模型下通过fork创建子进程。 211 | 212 | 多线程模式下可以创建子线程 213 | 214 | 子线程/线程创建成功后进入while循环,阻塞在recv调用上,等待客户端向服务器发送数据 215 | 216 | 收到数据以后服务器程序进行处理然后使用send向客户端发送响应 217 | 218 | 当客户端连接关闭时,子进程/线程退出并销毁所有资源。主进程/线程会回收掉此子进程/线程。 219 | 220 | 这中模型严重的依赖进程的数量解决并发问题。 221 | 222 | 启动大量的进程会带来额外的进程调度消耗 223 | 224 | ### 异步非阻塞模型 225 | 现在各种高并发异步IO的服务器程序都是基于epoll实现的 226 | 227 | IO复用异步非阻塞程序使用经典的Reactor模型,Reactor顾名思义就是反应堆的意思,它本身不处理任何数据收发。只是可以监视一个socket句柄的事件变化。 228 | 229 | #### Reactor模型: 230 | - add:添加一个socket到reactor 231 | - set:修改socket对应的事件,如可读可写 232 | - del:从reactor中移除 233 | - callback:事件发生后回掉指定的函数 234 | 235 | nginx:多线程Reactor 236 | 237 | swoole:多线程Reactor+多进程worker 238 | 239 | ## php并发编程实战 240 | - 1.php的swoole扩展、并行、高性能网络通信引擎,使用纯c语言编写提供了php语言的异步多线程服务器,异步tcp/udp网络客户端,异步mysql,异步redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步dns查询。 241 | - 2.除了异步IO的支持之外,swoole为php多进程的模式设计了多个并发数据结构和IPC通信机制,可以大大简化多线程并发编程的工作 242 | - 3.swoole2.0支持了类似Go语言的协程,可以使用完全同步的代码实现异步程序 243 | - 4.消息队列 244 | - 5.应用解耦 245 | - 场景说明:用户下单后,订单系统需要通知库存系统。 246 | - 假如库存系统无法访问,则订单减库存将失败,从而导致订单失败 247 | - 订单系统跟库存系统解耦 248 | - 引用队列 249 | - 用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功 250 | - 订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作 251 | - 6.流量削峰 252 | 应用场景:秒杀活动,流量瞬间激增,服务器压力大 253 | 用户发起请求后,服务器接收后,先写入消息队列。假如消息队列长度超多最大值,则直接报错或提示用户 254 | 控制请求量,缓解高流量 255 | - 7.日志处理 256 | 应用场景:解决大量日志的传输 257 | 日志采集程序将程序写入消息队列,然后通过日志处理程序的订阅消费日志。 258 | - 8.消息通讯 259 | 聊天室 260 | - 9.常见消息队列产品 261 | kafka,ActiveMQ,ZeroMQ,RabbitMQ,Redis等 262 | php的异步 263 | 消息队列 264 | - 10.接口的并发请求 265 | curl_multi_init 266 | 267 | ## mysql缓存层的优化 268 | 1.什么是数据库缓存 269 | 270 | mysql等一些常见的关系型数据库的数据都存储在磁盘当中,在高并发场景下,业务应用对mysql产生的增删,改,查的操作造成巨大的I/O开销和查询压力,这无疑对数据库和服务器都是一种巨大的压力,为了解决此类问题,缓存数据的概念应运而生。 271 | - 极大的解决数据库服务器的压力 272 | - 提高应用数据的响应速度 273 | 274 | 常见的缓存形式:内存缓存和文件缓存 275 | 276 | 2.为什么要使用数据库缓存 277 | 278 | - 缓存数据是为了让客户端很少甚至不访问数据库服务器进行数据的查询,高并发下,能最大程序地降低对数据库服务器的访问压力。 279 | - 用户请求-》数据查询-》连接数据库服务器并查询数据-》将数据缓存起来(html,内存,json,序列化数据)-》显示给客户端 280 | - 缓存方式的选择 281 | - 缓存场景的选择 282 | - 缓存数据的实时性 283 | - 缓存数据的稳定性 284 | 285 | 3.使用mysql查询缓存 286 | 287 | - 启用mysql查询缓存 288 | - 极大的降低cpu使用率 289 | - query_cache_type查询缓存类型,有0,1,2三个取值。0则不适用查询缓存。1表示始终使用查询缓存,2表示按需使用查询缓存。 290 | 291 | query_cahce_type=1 292 | select SQL_NO_CACHE * from my_table where condition; 293 | query_cache_type=2 294 | select SQL_CACHE * from my_table where condition; 295 | query_cache_size 296 | 297 | 默认情况下query_cache_size为0,表示为查询缓存预留的内存为0,则无法使用查询缓存 298 | SET GLOBAL query_cache_size = 134217728; 299 | 查询缓存可以看作是SQL文本和查询结果的映射 300 | 第二次查询的SQL和第一次查询的SQL完全相同,则会使用缓 301 | SHOW STATUS LIKE ‘Qcache_hits’查看命中次数 302 | 表的结构和数据发生改变时,查询缓存中的数据不再有效 303 | 304 | 情理缓存: 305 | 306 | - FLUSH QUERY CACHE;//清理查询缓存内存碎片 307 | - RESET QUERY CACHE;//从查询缓存中移出所有查询 308 | - FLUSH TABLES;//关闭所有打开的表,同时该操作将会清空查询缓存中的内容 309 | 310 | 4.使用Memcache缓存 311 | 312 | 对于大型站点,如果没有中间缓存层,当流量打入数据库层时,即便有之前的几层为我们挡住一部分流量,但是在大并发的情况下,还是会有大量请求涌入数据库层,这样对于数据库服务器的压力冲击很大,响应速度也会下降,因此添加中间缓存层很有必要。 313 | 314 | memcache是一套分布式的高速缓存系统,由liveJournal的BrandFitzpatrick开发,但目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的、需要频繁访问数据库的网站访问速度提升效果十分显著。 315 | memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像,视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存,然后从内存中读取,从而大大提高读取速度。 316 | 317 | 工作流程:先检查客户端的请求数据是否在memcache中,如有,直接把请求数据返回,不再对数据库进行任何操作;如果请求的数据不在memcache中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到memcached中。 318 | 319 | 通用缓存机制:用查询的方法名+参数作为查询时的key,value对中的key值 320 | 321 | 5.使用Redis缓存 322 | 323 | 与memcache的区别: 324 | 325 | - 性能相差不大 326 | - redis在2.0版本后增加了自己的VM特性,突破物理内存的限制,memcache可以修改最大可用内存,采用LRU算法 327 | - redis依赖客户端来实现分布式读写 328 | - memcache本身没有数据冗余机制 329 | - redis支持(快照,aof)依赖快照进行持久化aof增强了可靠性的同时,对性能有所影响 330 | - redis用户数据量较小的高性能操作和运算上 331 | - memcache用于在动态系统中减少数据库负载,提升性能;适合做缓存提高性能。 332 | - 可用于存储其他数据:session,session_set_save_handler 333 | 334 | ## mysql数据层的优化 335 | 336 | - 数据表数据类型优化:int,smallint.,bigint,enum,ip存储使用int类型ip2long转化存入 337 | - 索引不是越多越好,在合适的字段上创建合适的索引 338 | - 符合索引的前缀原则 339 | - like查询%的问题 340 | - 全表扫描优化 341 | - or条件索引使用情况 342 | - 字符串类型索引失效的问题 343 | - 优化查询数据过程中的数据访问,使用limit,尽量不要使用*,变复杂为简单,切分查询,分解关联查询* 344 | - 优化特定类型查询语句,优化count(),优化关联查询语句,优化子查询,优化group by和distinct,优化limit和union 345 | - 存储引擎的优化:尽量使用innodb 346 | - 数据库表结构的优化:分区操作(对用户透明)partion,分库分表(水平拆分,垂直拆分做副表) 347 | - 数据库服务器架构的优化:主从复制,读写分离,双主热备,负载均衡(lvs实现负载均衡,MyCat数据库中间件实现负载均衡) 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | -------------------------------------------------------------------------------- /untitled: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 我们公司目前未上市,2017年公司另外成立了一个合伙人有限公司,去投现在的公司。本来是想拉投资的结果,情况是直到现在投资也没有拉到。2017年购买了2.5w公司的股权,当时投股前,开会讨论的是员工如果离职公司按净资产回购股权,但是公司说现在还亏钱,回购的话我们不划算,让我可以先离职再等等,事实情况是去年有个同事购买的是5w股到现在都公司都没有回购。我们签的合伙人协议书所有人都签了字,不过是一份,直到现在我要离职了,才给了我们一份复印件。 5 | 下面是我的出资证明书,公司六月份资产负载表,以及股权协议书的入伙以及退伙的合同条款 6 | 我想咨询的问题是: 7 | 1.我是一个小股东 --------------------------------------------------------------------------------