├── .gitignore ├── docs ├── Mysql │ └── 你知道乐观锁和悲观锁有什么区别么?.md ├── 数据结构和算法 │ ├── 海量数据如何去取最大的k个.md │ └── 请你说一说你知道的排序算法及其复杂度.md ├── Redis │ └── 缓存击穿、缓存穿透和缓存雪崩.md ├── 计算机网络 │ ├── 一个网页从输入地址回车,到完整展示网页内容这段时间,发生了什么?.md │ └── 简单说说TCP 三次握手和四次挥手.md ├── Laravel │ ├── 一些非常好用的laravel扩展包你知道么?.md │ ├── Laravel 核心--深入剖析 Laravel 框架门面模式.md │ ├── 深入剖析 Laravel 框架 Jwt 组件的实现原理.md │ └── 一篇文章带你搞懂 Laravel 框架底层运行原理.md ├── PHP │ └── PHP和nginx是如何通信的?.md ├── 设计模式 │ └── 说说你对模板模式的理解吧.md └── 软技能 │ └── 程序员修炼之路:你该知道的 7 个必经阶段.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | PHP-Interview.iml 4 | -------------------------------------------------------------------------------- /docs/Mysql/你知道乐观锁和悲观锁有什么区别么?.md: -------------------------------------------------------------------------------- 1 | ### 前言 2 | 在电商系统,高并发的情况下 3 | 4 | ### 悲观锁 5 | 6 | ### 乐观锁 7 | 8 | ### 区别 -------------------------------------------------------------------------------- /docs/数据结构和算法/海量数据如何去取最大的k个.md: -------------------------------------------------------------------------------- 1 | 1、直接全部排序(只适用于内存够的情况) 2 | 当数据量较小的情况下,内存中可以容纳所有数据。则最简单也是最容易想到的方法是将数据全部排序,然后取排序后的数据中的前K个。 3 | 4 | 这种方法对数据量比较敏感,当数据量较大的情况下,内存不能完全容纳全部数据,这种方法便不适应了。即使内存能够满足要求,该方法将全部数据都排序了,而题目只要求找出top K个数据,所以该方法并不十分高效,不建议使用。 5 | 6 | 7 | 2、快速排序的变形 (只使用于内存够的情况) 8 | 9 | 这是一个基于快速排序的变形,因为第一种方法中说到将所有元素都排序并不十分高效,只需要找出前K个最大的就行。 10 | 11 | 这种方法类似于快速排序,首先选择一个划分元,将比这个划分元大的元素放到它的前面,比划分元小的元素放到它的后面,此时完成了一趟排序。如果此时这个划分元的序号index刚好等于K,那么这个划分元以及它左边的数,刚好就是前K个最大的元素;如果index > K,那么前K大的数据在index的左边,那么就继续递归的从index-1个数中进行一趟排序;如果index < K,那么再从划分元的右边继续进行排序,直到找到序号index刚好等于K为止。再将前K个数进行排序后,返回Top K个元素。这种方法就避免了对除了Top K个元素以外的数据进行排序所带来的不必要的开销。 12 | 13 | 14 | 3、最小堆法 15 | 16 | 这是一种局部淘汰法。先读取前K个数,建立一个最小堆。然后将剩余的所有数字依次与最小堆的堆顶进行比较,如果小于或等于堆顶数据,则继续比较下一个;否则,删除堆顶元素,并将新数据插入堆中,重新调整最小堆。当遍历完全部数据后,最小堆中的数据即为最大的K个数。 17 | 18 | 19 | 4、分治法 20 | 21 | 将全部数据分成N份,前提是每份的数据都可以读到内存中进行处理,找到每份数据中最大的K个数。此时剩下N*K个数据,如果内存不能容纳N*K个数据,则再继续分治处理,分成M份,找出每份数据中最大的K个数,如果M*K个数仍然不能读到内存中,则继续分治处理。直到剩余的数可以读入内存中,那么可以对这些数使用快速排序的变形或者归并排序进行处理。 22 | 23 | 24 | 5、Hash法 25 | 26 | 如果这些数据中有很多重复的数据,可以先通过hash法,把重复的数去掉。这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间。处理后的数据如果能够读入内存,则可以直接排序;否则可以使用分治法或者最小堆法来处理数据。 -------------------------------------------------------------------------------- /docs/Redis/缓存击穿、缓存穿透和缓存雪崩.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | `redis` 是一个高性能的基于内存的非关系型数据库,我相信在互联网工作的程序员,对 `redis` 都不会太陌生,`redis` 在日常的工作中,用的非常广泛,应用场景非常之多,比如:缓存、排行榜、计数器、简单的消息队列和 `session` 共享等等。任何事物都有两面性,`redis` 在使用中也存在一些问题,比如缓存穿透、缓存击穿和缓存雪崩等等。 3 | 4 | ## 缓存穿透 5 | 6 | ### 定义 7 | 8 | 客户端支持的发起对不存在 `redis` 中的数据的请求,导致请求直接落到数据库中,对数据库产出压力 9 | 10 | ### 解决方案 11 | 12 | - 接口层进行参数检验,如果 `id` 是从0开始的,那么小于等于0的 `id` 直接拦截 13 | - 数据库中没有查到的数据,可以直接缓存成key-null,下次请求会直接返回 `null`,不会对数据库造成压力 14 | 15 | ## 缓存击穿 16 | 17 | ### 定义 18 | `redis` 数据库中有一个热门的key,突然失效,导致大量的请求会访问数据库,严重的话可能会导致服务器瘫痪。 19 | 20 | ### 解决方案 21 | 22 | - 最好的办法就是直接设置这个热门的 `key` 永不过期 23 | 24 | ## 缓存雪崩 25 | 26 | ### 定义 27 | 28 | 大量的 `key` 在同一时间内失效,导致大量的请求绕过 `redis`,直接请求数据库 29 | 30 | ### 解决方案 31 | 32 | - 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 33 | - 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。 34 | 35 | ## 通俗案例 36 | 比如有个人,很有钱,买个各个视频网站的会员,其中爱奇艺的会员他没有买,他做了一个网站,把账号,密码分享出来,供大家使用,他相当于就是一个数据库,而网站相当于就是 `redis` 。 37 | 38 | 其中有个人每隔10秒钟就访问网站查询爱奇艺的账号和密码,没有找到,给你打电话,这就是所谓的 **缓存穿透**。 39 | 40 | 大家都喜欢看爱奇艺上面的《三十而已》,可是突然网站(redis)上面的账号和密码不能用了,失效了,给你(数据库)打电话,这就是所谓的 **缓存击穿**,最好的办法就是买个永久的会员。 41 | 42 | 你的各种会员突然同一时间都失效了,那这就是 **缓存雪崩** 了。 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 yexiansen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/计算机网络/一个网页从输入地址回车,到完整展示网页内容这段时间,发生了什么?.md: -------------------------------------------------------------------------------- 1 | ## 一个网页从输入地址回车,到完整展示网页内容这段时间里,发生了什么? 2 | 1、浏览器本地缓存匹配 3 | 4 | 2、本地 `host` 映射对比 5 | 6 | 3、本地 `dns` 缓存解析 7 | 8 | 4、远程 `dns` 解析获得服务器 `ip` 地址 9 | 10 | 5、浏览器发送 `tcp` 连接请求包(`syn`) 11 | 12 | 6、请求包经过传输层、网络层、数据链路层封装通过网卡到达路由器 13 | 14 | 7、路由器转发数据包到所属运营商的服务器; 15 | 16 | 8、运营商通过最短寻址算法通过中继节点到达指定 `ip` 地址; 17 | 18 | 9、服务端可能存在反向代理或者负载均衡,直接转发请求到上游服务器; 19 | 20 | 10、上游服务器收到连接请求,在自身可用的情况下,返回(`syn+ack`); 21 | 22 | 11、浏览器校验 `ack`,再次发送(`syn+ack`); 23 | 24 | 12、服务器校验 `ack` 切换连接状态至 `established`,至于三次握手结束,然后根据请求传输数据包; 25 | 26 | 13、当 `transform-encoding` 为 `chunked` 时,浏览器开始渲染页面; 27 | 28 | 14、四次挥手,连接关闭; 29 | 30 | 15、渲染数据完成。 31 | 32 | ### 渲染的细节: 33 | - html 页面的解析与渲染 34 | - 客户端浏览器加载了 html 文件后,由上到下解析 html 为 DOM 树(DOM Tree) 35 | - 遇到 css 文件,css 中的 url 发起 http 请求。 36 | - 这是第二次 http 请求,由于 http1.1 协议增加了 Connection: keep-alive 声明,故 tcp 连接不会关闭,可以复用。 37 | - http 连接是无状态连接,客户端与服务器端需要重新发起请求–响应。在请求 css 的过程中,解析器继续解析 html,然后到了 script 标签。 38 | - 由于 script 可能会改变 DOM 结构,故解析器停止生成 DOM 树,解析器被 js 阻塞,等待 js 文件发起 http 请求,然后加载。这是第三次 http 请求。js 执行完成后解析器继续解析。 39 | - 由于 css 文件可能会影响 js 文件的执行结果,因此需等 css 文件加载完成后再执行。 40 | - 浏览器收到 css 文件后,开始解析 css 文件为 CSSOM 树(CSS Rule Tree)。 41 | - Render Tree 会被 css 文件阻塞,渲染树生成后,先布局,绘制渲染树中节点的属性 (位置,宽度,大小等),然后渲染,页面就会呈现信息。 42 | - 继续边解析边渲染,遇到了另一个 js 文件,js 文件执行后改变了 DOM 树,渲染树从被改变的 dom 开始再次渲染。 43 | - 继续向下渲染,碰到一个 img 标签,浏览器发起 http 请求,不会等待 img 加载完成,继续向下渲染,之后再重新渲染此部分。 44 | - DOM 树遇到 html 结束标签,停止解析,进而渲染结束。 45 | 46 | > 本文参考连接:https://learnku.com/articles/51040 -------------------------------------------------------------------------------- /docs/计算机网络/简单说说TCP 三次握手和四次挥手.md: -------------------------------------------------------------------------------- 1 | ## TCP 三次握手和四次挥手 2 | TCP 三次握手和四次挥手也是面试题的热门考点,它们分别对应 TCP 的连接和释放过程。下面就来简单认识一下这两个过程 3 | ### TCP 三次握手 4 | 在了解具体的流程之前,我们先认识一下几个概念 5 | 6 | ![](https://gitee.com/yefangyong/blog-image/raw/master/png/20201029143610.png) 7 | 8 | - `SYN`:它的全称是 `Synchronize Sequence Numbers`,同步序列编号。是 `TCP/IP` 建立连接时使用的握手信号。在客户机和服务器之间建立 `TCP` 连接时,首先会发送的一个信号。客户端在接受到 SYN 消息时,就会在自己的段内生成一个随机值 X。 9 | 10 | - `SYN-ACK`:服务器收到 SYN 后,打开客户端连接,发送一个 `SYN-ACK` 作为答复。确认号设置为比接收到的序列号多一个,即 X + 1,服务器为数据包选择的序列号是另一个随机数 Y。 11 | 12 | - `ACK`:`Acknowledge character`, 确认字符,表示发来的数据已确认接收无误。最后,客户端将 `ACK` 发送给服务器。序列号被设置为所接收的确认值即 Y + 1。 13 | 14 | ![](https://gitee.com/yefangyong/blog-image/raw/master/png/20201029143759.png) 15 | 16 | 通俗举例:小红:客户端 小明:服务端 17 | 18 | - 小红给小明打电话,接通了之后,小红说,喂,你听到吗?这就相当于请求建立链接 19 | - 小明给小红回应,说我听到,你听到吗?这就相当于请求响应 20 | - 小红听到小明的回应之后,说我听到,到此连接建立,两个人开始bb(交换信息) 21 | 22 | ### TCP 四次挥手 23 | 在连接终止阶段使用四次挥手,连接的每一端都会独立的终止。下面我们来描述一下这个过程。 24 | ![](https://gitee.com/yefangyong/blog-image/raw/master/png/20201029145714.png) 25 | 26 | - 首先,客户端应用程序决定要终止连接(这里服务端也可以选择断开连接)。这会使客户端将 `FIN` 发送到服务器,并进入 `FIN_WAIT_1` 状态。当客户端处于 `FIN_WAIT_1` 状态时,它会等待来自服务器的 `ACK` 响应。 27 | - 然后第二步,当服务器收到 `FIN` 消息时,服务器会立刻向客户端发送 `ACK` 确认消息。 28 | - 当客户端收到服务器发送的 `ACK` 响应后,客户端就进入 `FIN_WAIT_2` 状态,然后等待来自服务器的 `FIN` 消息 29 | - 服务器发送 `ACK` 确认消息后,一段时间(可以进行关闭后)会发送 `FIN` 消息给客户端,告知客户端可以进行关闭。 30 | - 当客户端收到从服务端发送的 `FIN` 消息时,客户端就会由 `FIN_WAIT_2` 状态变为 `TIME_WAIT` 状态。处于 `TIME_WAIT` 状态的客户端允许重新发送 `ACK` 到服务器为了防止信息丢失。客户端在 `TIME_WAIT` 状态下花费的时间取决于它的实现,在等待一段时间后,连接关闭,客户端上所有的资源(包括端口号和缓冲区数据)都被释放。 31 | 32 | 还是继续用上面通话的例子进行举例: 33 | 34 | - 小红对小明说,我话讲完了,我要挂断电话了。 35 | - 小明说,收到,我这边还有一些东西没有说。 36 | - 经过若干时间后,小明说,我说完了,可以挂断电话了 37 | - 小红收到消息后,又等了若干时间挂断了电话 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-Interview 2 | 3 | ## 问题列表 4 | 5 | ### PHP 6 | - [PHP和nginx是如何通信的了解么?](docs/PHP/PHP和nginx是如何通信的?.md) 7 | 8 | ### Mysql 9 | - [你知道聚簇索引和非聚簇得区别么?](https://www.cnblogs.com/jiawen010/p/11805241.html) 10 | - [你知道乐观锁和悲观锁的区别么?](docs/Mysql/你知道乐观锁和悲观锁有什么区别么?.md) 11 | 12 | ### Laravel 13 | - [一篇文章带你搞懂 Laravel 框架的底层运行原理](docs/Laravel/一篇文章带你搞懂%20Laravel%20框架底层运行原理.md) 14 | - [一些非常好用的 Laravel 框架的扩展包你还知道么?](docs/Laravel/一些非常好用的laravel扩展包你知道么?.md) 15 | - [Laravel 核心--深入剖析 Laravel 框架门面模式](docs/Laravel/Laravel%20核心--深入剖析%20Laravel%20框架门面模式.md) 16 | - [Laravel 核心--深入剖析 Laravel 框架 Jwt 组件实现原理](docs/Laravel/深入剖析%20Laravel%20框架%20Jwt%20组件的实现原理.md) 17 | 18 | ### Linux 19 | 20 | ### Nginx 21 | - [Nginx16连环问,你被问到了吗!](https://juejin.im/post/6844904182923837454) 22 | 23 | ### Docker 24 | - [什么是Docker?看这一篇干货文章就够了!](https://zhuanlan.zhihu.com/p/187505981) 25 | 26 | - [Docker从入门到实践,学习docker必备!](https://yeasy.gitbook.io/docker_practice/repository/dockerhub) 27 | 28 | ### Git 29 | 30 | ### 计算机网络 31 | 32 | - [一个网页从输入地址回车,到完整展示网页内容这段时间里,发生了什么?](docs/计算机网络/一个网页从输入地址回车,到完整展示网页内容这段时间,发生了什么?.md) 33 | - [简单说说你什么是三次握手和四次挥手?](docs/计算机网络/简单说说TCP%20三次握手和四次挥手.md) 34 | 35 | ### 数据结构和算法 36 | - [海量数据如何去取最大的K个?](docs/数据结构和算法/海量数据如何去取最大的k个.md) 37 | - [请你说一说你知道的排序算法及其时间复杂度](docs/数据结构和算法/请你说一说你知道的排序算法及其复杂度.md) 38 | 39 | ### 操作系统 40 | 41 | ### Redis 42 | - [缓存穿透、缓存击穿和缓存雪崩是什么?](docs/Redis/缓存击穿、缓存穿透和缓存雪崩.md) 43 | 44 | ### 设计模式 45 | - [说说你对设计模式中的模板模式的理解吧](docs/设计模式/说说你对模板模式的理解吧.md) 46 | 47 | ### 系统架构 48 | 49 | ### 软技能 50 | - [程序员修炼之路:你该知道的 7 个必经阶段](docs/软技能/程序员修炼之路:你该知道的%207%20个必经阶段.md) 51 | 52 | ## 为什么写这个 53 | 常言道,好记性,不如烂笔头,在笔者几次面试的过程中,总会遇到一些常见的问题,特此记录下来,也是对自己学习知识的一个总结和梳理,构建一个面试体系,而不必慌张的临时准备,时时刻刻都充分准备好。结合工作和实际面试,系统的汇总面试过程中常见的问题,尝试提供简洁准确的答案,包含但不限于计算机网络、数据结构与算法、 54 | 操作系统、PHP、Web、Mysql、Redis、Linux、安全、设计模式、Git、架构等部分。 55 | 56 | 最后祝愿大家都可以拿到满意的Offer~ 57 | -------------------------------------------------------------------------------- /docs/数据结构和算法/请你说一说你知道的排序算法及其复杂度.md: -------------------------------------------------------------------------------- 1 | 参考回答: 2 | 3 | 1、冒泡排序: 4 | 从数组中第一个数开始,依次遍历数组中的每一个数,通过相邻比较交换,每一轮循环下来找出剩余未排序数的中的最大数并“冒泡”至数列的顶端。 5 | 6 | 稳定性:稳定 7 | 8 | 平均时间复杂度:O(n ^ 2) 9 | 10 | 2、插入排序: 11 | 12 | 从待排序的n个记录中的第二个记录开始,依次与前面的记录比较并寻找插入的位置,每次外循环结束后,将当前的数插入到合适的位置。 13 | 14 | 稳定性:稳定 15 | 16 | 平均时间复杂度:O(n ^ 2) 17 | 18 | 3、希尔排序(缩小增量排序): 19 | 20 | 希尔排序法是对相邻指定距离(称为增量)的元素进行比较,并不断把增量缩小至1,完成排序。 21 | 22 | 希尔排序开始时增量较大,分组较多,每组的记录数目较少,故在各组内采用直接插入排序较快,后来增量di逐渐缩小,分组数减少,各组的记录数增多,但由于已经按di−1分组排序,文件叫接近于有序状态,所以新的一趟排序过程较快。因此希尔 排序在效率上比直接插入排序有较大的改进。 23 | 24 | 在直接插入排序的基础上,将直接插入排序中的1全部改变成增量d即可,因为希尔排序最后一轮的增量d就为1。 25 | 26 | 稳定性:不稳定 27 | 28 | 平均时间复杂度:希尔排序算法的时间复杂度分析比较复杂,实际所需的时间取决于各次排序时增量的个数和增量的取值。时间复杂度在O(n ^ 1.3)到O(n ^ 2)之间。 29 | 30 | 4、选择排序: 31 | 32 | 从所有记录中选出最小的一个数据元素与第一个位置的记录交换;然后在剩下的记录当中再找最小的与第二个位置的记录交换,循环到只剩下最后一个数据元素为止。 33 | 34 | 稳定性:不稳定 35 | 36 | 平均时间复杂度:O(n ^ 2) 37 | 38 | 5、快速排序 39 | 40 | 1)从待排序的n个记录中任意选取一个记录(通常选取第一个记录)为分区标准; 41 | 42 | 2)把所有小于该排序列的记录移动到左边,把所有大于该排序码的记录移动到右边,中间放所选记录,称之为第一趟排序; 43 | 44 | 3)然后对前后两个子序列分别重复上述过程,直到所有记录都排好序。 45 | 46 | 稳定性:不稳定 47 | 48 | 平均时间复杂度:O(nlogn) 49 | 50 | 6、堆排序: 51 | 52 | 堆: 53 | 54 | 1、完全二叉树或者是近似完全二叉树。 55 | 56 | 2、大顶堆:父节点不小于子节点键值,小顶堆:父节点不大于子节点键值。左右孩子没有大小的顺序。 57 | 58 | 堆排序在选择排序的基础上提出的,步骤: 59 | 60 | 1、建立堆 61 | 62 | 2、删除堆顶元素,同时交换堆顶元素和最后一个元素,再重新调整堆结构,直至全部删除堆中元素。 63 | 64 | 稳定性:不稳定 65 | 66 | 平均时间复杂度:O(nlogn) 67 | 68 | 7、归并排序: 69 | 70 | 采用分治思想,现将序列分为一个个子序列,对子序列进行排序合并,直至整个序列有序。 71 | 72 | 稳定性:稳定 73 | 74 | 平均时间复杂度:O(nlogn) 75 | 76 | 8、计数排序: 77 | 78 | 思想:如果比元素x小的元素个数有n个,则元素x排序后位置为n+1。 79 | 80 | 步骤: 81 | 82 | 1)找出待排序的数组中最大的元素; 83 | 84 | 2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项; 85 | 86 | 3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加); 87 | 88 | 4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。 89 | 90 | 稳定性:稳定 91 | 92 | 时间复杂度:O(n+k),k是待排序数的范围。 93 | 94 | 9、桶排序: 95 | 96 | 步骤: 97 | 98 | 1)设置一个定量的数组当作空桶子; 常见的排序算法及其复杂度: 99 | 100 | 2)寻访序列,并且把记录一个一个放到对应的桶子去; 101 | 102 | 3)对每个不是空的桶子进行排序。 103 | 104 | 4)从不是空的桶子里把项目再放回原来的序列中。 105 | 106 | 时间复杂度:O(n+C) ,C为桶内排序时间。 107 | 108 | ![](https://gitee.com/yefangyong/blog-image/raw/master/png/20201113104719.png) -------------------------------------------------------------------------------- /docs/Laravel/一些非常好用的laravel扩展包你知道么?.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 自从有了 `composer` 包管理工具,出现大量优秀的扩展包,让我们可以解放双手,大大提高我们的开发效率,有了更多的时间去 `enjoy life and accompany family`,下面我将会列出工作中用到的一些扩展包,希望对大家有所帮助,我不会给出详细的使用说明,大家在使用之前最好先去看下官方的文档,以文档为主,`show time,enjoy`!!! 3 | 4 | ## 扩展包 5 | 6 | ### 1、`PHP` 导出百万级数据到表格 7 | 8 | **简介:** 9 | 10 | PHP 导出是一个比较常见的功能,但常规的导出却有一个内存瓶颈,导致速度慢,甚至会将整个服务给挂掉。 11 | 12 | 这里,采用了PHP 迭代器 yield,开发了一个简单的 composer 包,使用起来比较简单,导出百万级数据,不会拖慢整个服务。 13 | 14 | **使用:** 15 | 16 | ``` 17 | composer require haveyb/export-csv 18 | ``` 19 | 20 | **地址:** 21 | 22 | > https://packagist.org/packages/haveyb/export-csv 23 | 24 | ### 2、`jwt`扩展包 25 | 26 | **简介:** 27 | 28 | `Json web token (JWT)`, 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(`SSO`)场景。`JWT` 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 29 | 30 | 31 | **使用:** 32 | 33 | ``` 34 | composer require tymon/jwt-auth 35 | ``` 36 | 37 | **地址:** 38 | 39 | > https://github.com/tymondesigns/jwt-auth 40 | 41 | ### 3、`laravel` 框架代码提示扩展包 `laravel-ide-helper` 42 | 43 | **简介:** 44 | 45 | 在使用模型或者门面的时候,编辑器无法自动提示模型有哪些属性和方法,这个扩展包直接从源代码完善PHP注释,以致于编辑器可以自动提示模型或者门面有哪些属性和方法。 46 | 47 | **使用:** 48 | 49 | ``` 50 | composer require --dev barryvdh/laravel-ide-helper 51 | php artisan ide-helper:generate - PHPDoc generation for Laravel Facades 52 | php artisan ide-helper:models - PHPDocs for models 53 | php artisan ide-helper:meta - PhpStorm Meta file 54 | ``` 55 | 56 | **地址:** 57 | 58 | > https://github.com/barryvdh/laravel-ide-helper 59 | 60 | ### 4、图片生成或者裁剪扩展包 `BaconQrCode` 61 | 62 | **简介:** 63 | 64 | 在日常开发的过程中,我们不可避免的会遇到生成二维码或者图片的需求,这些代码编写起来比较复杂,但是我们不必重复造轮子,可以直接使用 `BaconQrCode` 扩展包 65 | 66 | **使用:** 67 | 68 | ``` 69 | composer require bacon/bacon-qr-code 70 | ``` 71 | 72 | **地址:** 73 | 74 | > https://github.com/Bacon/BaconQrCode 75 | 76 | ### 5、微信支付和支付宝支付扩展包 77 | 78 | **简介:** 79 | 80 | 在电商系统中,微信支付和支付宝支付是一个不可逃避的话题,开发了多次支付宝与微信支付后,很自然产生一种反感,惰性又来了,想在网上找相关的轮子,可是一直没有找到一款自己觉得逞心如意的,要么使用起来太难理解,要么文件结构太杂乱,只有自己撸起袖子干了。 81 | 82 | **使用:** 83 | 84 | ``` 85 | composer require yansongda/pay 86 | ``` 87 | 88 | **地址:** 89 | 90 | > https://github.com/yansongda/pay 91 | 92 | **欢迎大家补充,持续更新中**。。。。。。 -------------------------------------------------------------------------------- /docs/PHP/PHP和nginx是如何通信的?.md: -------------------------------------------------------------------------------- 1 | ### CGI协议与FastCGI协议 2 | 3 | 每种动态语言(`PHP,Python`等)的代码文件需要通过对应的解析器才能被服务器识别,而 `CGI(Common Gateway Interface)` 通用网关接口协议就是用来使解释器和服务器可以互相通信。PHP文件在服务器上的解析需要用到PHP解析器,再加上对应的 `CGI` 协议,从而可以使服务器可以解析PHP文件。 4 | 5 | 由于CGI的机制是每次处理一个请求,fork一个进程,等请求处理完成后,再去 `kill` 这个进程,在实际应用中比较浪费资源,于是就出现了 `CGI` 的改良版本 `FastCGI` ,`FastCGI` 在请求处理完成后不会马上 `kill` 进程,而是会接着执行其他的请求,这样会大大的提高效率。 6 | 7 | ### PHP-FPM是什么? 8 | 9 | `PHP-FPM` 即 `PHP-FastCGI Process Manager`,它是 `FastCGI` 的一个具体实现,并且提供了进程管理的功能,进程包括 `master` 和 `worker` 两种进程。`master` 进程只有一个,负责监听端口,接受来自服务器的请求,而 `worker` 进程一般有多个,每个进程内部都是嵌入PHP解释器,是代码真正执行的地方。 10 | 11 | ### Nginx 与 php-fpm 通信机制 12 | 13 | 当我们访问一个网站(www.test.com)的时候,处理的流程一般是这样的: 14 | 15 | ![](https://gitee.com/yefangyong/blog-image/raw/master/png/20200908115619.png) 16 | 17 | ### Nginx 与 php-fpm 的结合 18 | 19 | 在 `Linux` 上,`nginx` 与 `php-fpm` 通信有两种方式,`tcp-socket` 和 `unix-socket`。 20 | 21 | `tcp-socket` 的优点是可以跨服务器,而当 `nginx` 和 `php-fpm` 不在同一台机器上时,只能使用这种方式。 22 | 23 | `unix-socket` 又叫IPC(`inter-process communication`) 进程间通信 `socket`,用于实现同一主机的进程间通信,这种方式需要在nginx配置文件中填写 `php-fpm` 的 `socket` 文件位置。 24 | 25 | 两种方式的数据传输过程如下图: 26 | 27 | ![](https://gitee.com/yefangyong/blog-image/raw/master/png/20200908120534.png) 28 | 29 | 二者的不同: 30 | 31 | 由于 `Unix socket` 不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。所以其效率比 `tcp socket` 的方式要高,可减少不必要的 tcp 开销。不过,`unix socket` 高并发时不稳定,连接数爆发时,会产生大量的长时缓存,在没有面向连接协议的支撑下,大数据包可能会直接出错不返回异常。而 `tcp` 这样的面向连接的协议,可以更好的保证通信的正确性和完整性。 32 | 33 | `Nginx` 与 `php-fpm` 结合只需要在各自的配置文件中做设置即可: 34 | 35 | 1)Nginx中的配置,以 `tcp socket` 通信为例 36 | 37 | ```shell script 38 | server { 39 | listen 80; #监听 80 端口,接收http请求 40 | server_name www.test.com; #就是网站地址 41 | root /usr/local/etc/nginx/www/project; # 准备存放代码工程的路径 42 | #路由到网站根目录 www.test.com 时候的处理 43 | location / { 44 | index index.php; #跳转到 www.test.com/index.php 45 | autoindex on; #开启文件索引目录 46 | } 47 | 48 | #当请求网站下 php 文件的时候,反向代理到 php-fpm 49 | location ~ \.php$ { 50 | include /usr/local/etc/nginx/fastcgi.conf; #加载 nginx 的 fastcgi 模块 51 | fastcgi_intercept_errors on; 52 | fastcgi_pass 127.0.0.1:9000; # tcp 方式,php-fpm 监听的 IP 地址和端口 53 | # fasrcgi_pass /usr/run/php-fpm.sock # unix socket 连接方式 54 | } 55 | 56 | } 57 | ``` 58 | 59 | 2)php-fpm配置 60 | 61 | ```shell script 62 | listen = 127.0.0.1:9000 63 | # 或者下面这样 64 | listen = /var/run/php-fpm.sock 65 | ``` 66 | 67 | > 注意,在使用 unix socket 方式连接时,由于 socket 文件本质上是一个文件,存在权限控制的问题,所以需要注意 nginx 进程的权限与 php-fpm 的权限问题,不然会提示无权限访问。(在各自的配置文件里设置用户) 68 | 69 | 通过以上的配置可以完成 `php-fpm` 与 `Nginx` 的通信 70 | 71 | ### 在应用中的选择 72 | 如果是在同一台服务器上运行的 `nginx` 和 `php-fpm`,且并发量不高(不超过 1000),选择 `unix socket`,以提高 `nginx` 和 `php-fpm` 的通信效率。 73 | 74 | 如果是面临高并发业务,则考虑选择使用更可靠的 tcp socket,以负载均衡、内核优化等运维手段维持效率。 75 | 76 | > 文章内容部分来自网络,如有侵权,请联系我删除。 77 | -------------------------------------------------------------------------------- /docs/设计模式/说说你对模板模式的理解吧.md: -------------------------------------------------------------------------------- 1 | ### 一、模板方法模式的定义 2 | 3 | 模板方法是在一个算法中定义一个算法的骨架,具体的实现延迟到子类中去实现。模式方式可以使得子类在不改变算法结构的情况下,重新定义算法某些步骤的实现。 4 | 5 | 看到 "设计模式" 我们往往感觉高深莫测,其实模板方法模式非常之简单,我们只需要关心一个方法而已。 6 | 7 | ![](https://gitee.com/yefangyong/blog-image/raw/master/png/1002892-20181022185854421-1763971815.png) 8 | 9 | ### 二、模板方法的使用场景 10 | 11 | 当算法的实现的骨架或者步骤是相对固定的,而其中某些实现方法的步骤是不一样的时候,就可以使用模板方法模式。 12 | 13 | - 多个子类有共同的方法,并且逻辑基本上相同。 14 | 15 | - 重要复杂、核心的方法可以设计为模板方法,周边的相关的细节功能可以由子类去实现。 16 | 17 | - 重构代码的时候,可以把相同的逻辑代码抽取出来。 18 | 19 | 举个栗子: 20 | 21 | > 一个游戏支付系统,有多种支付的方式,包括支付宝,微信支付等等方式,它们支付成功回调通知系统时,一般都会经历几个阶段,接受参数,验证签名,划账,通知游戏发货,修改订单的状态,这些步骤就是相对固定的,设计成一个模板方法,让子类去继承这个方法,而其中有些步骤的实现方式会有所不同,比如接收参数的形式,微信支付使用xml格式传输数据,而支付宝书数组格式,同时验证签名的过程也有所不同,接收参数和验证签名等方法的具体实现我们就可以延迟要子类中实现。 22 | 23 | ### 三、模板方法的具体实现 24 | 25 | #### 步骤一、创建抽象类和模板方法 26 | ```php 27 | abstract class Cook 28 | { 29 | //定义成final方法,子类可以继承,防止子类修改该算法的步骤或者骨架 30 | final public function cookProcess() 31 | { 32 | //1、倒油 33 | $this->pourOil(); 34 | //2、热油 35 | $this->heatOil(); 36 | //3、倒蔬菜,由于每个菜是不一样的,所以需要子类去实现 37 | $this->pourVegetable(); 38 | //4、放入调味品 每个菜的调味品也是不一样的,延迟到子类中实现 39 | $this->pourSauce(); 40 | //5、翻炒 41 | $this->fry(); 42 | } 43 | 44 | private function pourOil() 45 | { 46 | echo "开始倒油" . PHP_EOL; 47 | } 48 | 49 | private function fry() 50 | { 51 | echo "翻炒" . PHP_EOL; 52 | } 53 | 54 | private function heatOil() 55 | { 56 | echo "热油" . PHP_EOL; 57 | } 58 | 59 | abstract function pourSauce(); 60 | 61 | abstract function pourVegetable(); 62 | } 63 | ``` 64 | 65 | #### 步骤二、创建子类继承抽象类,并且实现抽象方法 66 | ```php 67 | /** 68 | * Class cookeBaoCai 69 | * 手撕包菜 70 | */ 71 | class cookeBaoCai extends Cook 72 | { 73 | public function pourSauce() 74 | { 75 | echo '放入辣椒' . PHP_EOL; 76 | } 77 | 78 | public function pourVegetable() 79 | { 80 | echo "放入包菜" . PHP_EOL; 81 | } 82 | } 83 | 84 | /** 85 | * Class cookCaixin 86 | * 蒜蓉菜心 87 | */ 88 | class cookCaixin extends Cook 89 | { 90 | public function pourVegetable() 91 | { 92 | echo "放入菜心" . PHP_EOL; 93 | } 94 | 95 | public function pourSauce() 96 | { 97 | echo "放入蒜蓉" . PHP_EOL; 98 | } 99 | } 100 | ``` 101 | 102 | #### 步骤三、客户端进行调用 103 | ```php 104 | /** 105 | * 客户端开始调用 106 | */ 107 | $cookCaixin = new cookCaixin(); 108 | $cookCaixin->cookProcess(); 109 | 110 | $cookBaocai = new cookeBaoCai(); 111 | $cookBaocai->cookProcess(); 112 | ``` 113 | 114 | #### 最终结果 115 | ```shell script 116 | 开始倒油 117 | 热油 118 | 放入菜心 119 | 放入蒜蓉 120 | 翻炒 121 | 开始倒油 122 | 热油 123 | 放入包菜 124 | 放入辣椒 125 | 翻炒 126 | ``` 127 | 128 | ### 四、模板方法的优缺点 129 | 130 | 优点如下: 131 | 132 | - 封装不变的部分,扩展可变的部分,把认为不变的部分封装到父类,可变的部分通过继承来实现扩展 133 | - 提取公共的代码,便于维护 134 | - 行为由父类控制,子类实现 135 | 136 | 缺点如下: 137 | 138 | - 需要修改算法的骨架时,需要修改抽象类。因为行为由父类控制,子类实现,增加了代码阅读的复杂度,同时由于引入了抽象类,会引入很多的子类,加大了系统的复杂度。 139 | -------------------------------------------------------------------------------- /docs/Laravel/Laravel 核心--深入剖析 Laravel 框架门面模式.md: -------------------------------------------------------------------------------- 1 | ## 门面模式 2 | **门面模式** 又叫 **外观模式**,提供一个统一的接口去访问多个子系统的多个不同的接口,它为子系统中的一组接口提供一个统一的高层接口,使用子系统更容易使用。 3 | 4 | 本质:就是化零为整;引入一个中介类,把各个分散的功能组合成一个整体,只对外暴露一个统一的接口; 5 | 6 | 这两年流行微服务,即化整为零,把一个大服务拆分成一个个零部件;而门面模式则是反其道,是化零为整; 7 | 8 | ## 目的 9 | 为了用户使用方便,把过度拆分的分散功能,组合成一个整体,对外提供一个统一的接口 10 | 11 | ## Laravel 框架中的门面模式 12 | 13 | ### 如何使用 14 | 15 | `Laravel` 框架通过使用门面设计模式,使得可以通过调用类的静态方法的方式去调用类的普通方法,十分的方便和简单,以 `Cache` 类为例: 16 | ```php 17 | public function getCacheData($name) 18 | { 19 | return Cache::get($name) 20 | } 21 | ``` 22 | 上述代码,其实调用的是 `Cache` 类的非静态 `get` 方法 23 | 24 | ### 源码解析 25 | 这是 `Illuminate\Support\Facades\Cache` facade 类的源代码: 26 | 27 | ```php 28 | class Cache extends Facade 29 | { 30 | /** 31 | * Get the registered name of the component. 32 | * 33 | * @return string 34 | */ 35 | protected static function getFacadeAccessor() 36 | { 37 | return 'cache'; 38 | } 39 | } 40 | ``` 41 | 42 | 我们是如何使用这个类的呢? 43 | 44 | ```php 45 | public function test() 46 | { 47 | Cache::get('test'); 48 | } 49 | ``` 50 | 上述代码看起来非常像调用 `Cache` 中的静态方法 `get` ,但是我们很清楚的看到这个类中只有 `getFacadeAccessor` 这一个方法,并没有静态方法 `get` ,这里的方法 `get()` 实际上存在于容器内部的服务中。所有的细节都隐藏在基本 `Facade` 类中。 51 | 52 | 当我们在 `Cache` 外观上引用任何静态方法时,`Laravel` 会解析 `cache` 服务容器中的 绑定并针对该对象运行请求的方法。 53 | 54 | 现在让我们仔细研究一下这个细节,每个外观都将扩展基本抽象 `Facade` 类。细节隐藏在这里的三种方法中: 55 | 56 | - `__callStatic()` -简单的PHP魔术方法 57 | - `getFacadeRoot()` -从IoC容器中获取服务 58 | - `resolveFacadeInstance()` -负责解决服务实例 59 | 60 | 当我们调用静态方法 `get` 的时候,`Cache` 类中没有此方法,会自动调用父类中的魔术方法如下: 61 | ```php 62 | public static function __callStatic($method, $args) 63 | { 64 | $instance = static::getFacadeRoot(); 65 | 66 | if (! $instance) { 67 | throw new RuntimeException('A facade root has not been set.'); 68 | } 69 | 70 | return $instance->$method(...$args); 71 | } 72 | ``` 73 | 74 | 方法 `getFacadeRoot()` 返回外观背后的服务对象的实例: 75 | 76 | ```php 77 | public static function getFacadeRoot() 78 | { 79 | return static::resolveFacadeInstance(static::getFacadeAccessor()); 80 | } 81 | ``` 82 | 83 | 方法 `resolveFacadeInstance` 复杂解决服务实例,在这里,我们检查传递给对象的参数,然后检查是否已经解决了该服务。如果不是,则只需从容器中检索它: 84 | 85 | ```php 86 | protected static function resolveFacadeInstance($name) 87 | { 88 | if (is_object($name)) { 89 | return $name; 90 | } 91 | 92 | if (isset(static::$resolvedInstance[$name])) { 93 | return static::$resolvedInstance[$name]; 94 | } 95 | 96 | if (static::$app) { 97 | return static::$resolvedInstance[$name] = static::$app[$name]; 98 | } 99 | } 100 | ``` 101 | 102 | 上述代码中 `static::$app[$name]` 值得注意的是,其中 `$app` 为应用容器,是一个对象,为什么可以以数组的方式访问变量呢?那是因为容器实现了 `ArrayAccess` 类,这个类使得容器对象有了访问数组的能力,对于 `ArrayAccess` 这个类我前面文章有写过,不了解的,可以去看看哦。 103 | 调用 `static::$app[$name]` 会直接去调用容器类的 `offsetGet`的方法: 104 | ```php 105 | public function offsetGet($key) 106 | { 107 | return $this->make($key); 108 | } 109 | ``` 110 | 上述代码,其中 `make` 方法继续去解析和返回我们需要的实例,这部分代码不在本文的讨论范围之内,关于容器技术博客其他文章中有详细的描述。 111 | 112 | 至此,我们就揭开了 `Laravel` 框架中门面模式的面纱了。 113 | 114 | -------------------------------------------------------------------------------- /docs/软技能/程序员修炼之路:你该知道的 7 个必经阶段.md: -------------------------------------------------------------------------------- 1 | # 编码历练 2 | 3 | 代码行经验是个非常重要的东西,当你还没有 1 万行代码经验的时候,你来问如何提升设计能力的问题,我只能告诉你不要太纠结,理论看看就好,老老实实写代码吧。 4 | 5 | 据说,一个程序员平均每天码代码的速度是 200~300 行,你可能会说,我一天怎么也要写上 1000 行吧,别忘了,当你码完代码后,你还需要测试、调试、优化、BUG Fix,这些时间你没法一直码代码的。 6 | 7 | 编码规范就不多说了,如果你的代码还是杂乱无章的状态,就先别谈什么设计与架构了,我会觉得有点扯淡。 8 | 9 | 另外,作为代码洁癖患者,推荐大家不要把代码写完后,批量格式化处理,或者手工再去整理代码,而是每敲一个字符上去,它都是符合规范的。习惯真的很重要,有时在招聘面试的时候,我真想添加一个环节,现场编写程序完成一个简单但容易出错的任务。 10 | 11 | # 理论学习 12 | 13 | 简单说就是看书,看博客,你所能得到的资源,质量高的就行。例如:《重构 – 改善既有代码的设计》、《敏捷软件开发:原则、模式与实践》、《UML 和模式应用》、”面向对象设计原则”(五大原则)、《设计模式》等。 14 | 15 | 《设计模式》这本书是很古老的一本书了,只有短短 200 页,但是,这是最难看懂的一本书,一个月都可能看不完(看小说的话,200 页 3 个小时也许就看完了吧),而且就算看完了,也不会全看懂,很可能看懂不超过 30%。看不懂没关系,看了就行,不用太纠结,这不能说明什么问题。 16 | 17 | 另外,我想说一下,多线程技术是程序员必须掌握的,而且需要理解透彻,现在的高级技术例如 GCD,会掩盖你对多线程理解不足的问题,因为使用实在太简单了。别说你没写过多线程依然完成了复杂的项目,更别说你随手写出的多线程代码好像也没出什么问题啊,把你的代码给我,我写个 Demo 让它出错乃至崩溃,如果我做不到,恭喜你。 18 | 19 | # 实践 20 | 21 | 现在,你已经具备了一定的编码经验,而且已经学习了足够的理论知识,接下来就是真正练手的时候了。好好反复思考你学习的这些理论知识,要如何运用到项目中去,身体力行的去实践,一定要把那些理论搞清楚,用于指导你的实践,收起从前的自信,首先否定自己以前的做法,保证每次做出的东西相比以前是有进步有改进的。 22 | 23 | # 重温理论 24 | 25 | 你已经能看到自己的进步了,发现比以前做的更好了,但是总感觉还不够,好像有瓶颈似的,恭喜你,我已经能看到你未来的潜力了。 26 | 27 | 重新拿起书本,重温一遍之前看的似懂非懂的东西,你会发现之前没弄懂的东西,现在豁然开朗了,不再是那种难于理解的晦涩感了。就算是以前你觉得已经弄懂的,也再看一遍,通常会有新的收获。 28 | 29 | # 再实践 30 | 31 | 这个阶段,你已经掌握了较多的东西了,不但实践经验丰富,各种理论也能手到擒来了,但是你发现你的设计依然不够专业。而且你回过头去看你以前写的代码,你会惊讶:天啊,这是谁写的代码,怎么能这样干!然后。。。我就不多说了,你已经进入了自省的阶段,掌握了适合自己的学习方法,再要学习什么新东西,都不再是个事。 32 | 33 | # 总结 34 | 35 | 先别太得意(不信?那你去做一堂讲座看看),你需要总结了,总结自己的学习方法,总结项目经验,总结设计理论知识。 36 | 37 | 如果你能有自己独到的理解,而不是停留在只会使用成熟的设计模式什么的,能根据自己的经验教训总结一些设计原则出来,那自然是极好的。 38 | 39 | # 分享 40 | 41 | 分享是最好的学习催化剂,当你要准备一场培训分享的时候,你会发现你先前以为已经理解的东西其实并没有真正理解透彻,因为你无法把它讲清楚,实际上就是研究不够,这时会迫使你去重新深入学习,融汇贯通,然后你才敢走上讲台。否则当别人提问的时候,你根本回答不上来。 42 | 43 | 以上,便是我认为的程序员修炼道路的必经阶段。 44 | 45 | 然后,我再说说其他对提升非常重要的几点: 46 | 47 | ## 1\. 养成先设计,再编码的习惯 48 | 49 | 几乎所有的程序员,一开始都不太愿意写文档,也不太愿意去精心设计,拿到需求总是忍不住那双躁动的手,总觉得敲在键盘上,一行一行的代码飙出来,才有成就感,才是正确的工作姿势。 50 | 51 | 没讨论清楚不要编码,不然你一定会返工。 52 | 53 | ## 2. 设计重于编码,接口重于实现 54 | 55 | 制定接口的过程,本身就是设计过程,接口一定要反复推敲,尽量做减法而不是加法,在能满足需求的情况下越简单越好。 56 | 57 | 另外,不要一个人冥思苦想,先简单做一个雏形出来,然后拿去找使用方沟通,直到对方满意为止。 不要完全根据使用需求去设计接口,参考 MVVM,ViewModel 就是根据 View 的需要而对 Model 进行的再封装,不能将这些接口直接设计到 Model 中。 58 | 59 | ## 3. 不盲从设计模式 60 | 61 | 设计模式只是一种解决问题的套路方法,你也可以有自己的方法,当然设计模式如果用好了,会让你的设计显得专业与优雅,毕竟前辈们的心血结晶。但是滥用的话,也会导致更严重的问题,甚至可能成为灾难。个人觉得面向对象设计原则更加重要,有些原则是必须遵守的(如单向依赖、SRP 等),而设计模式本身都是遵守这些原则的,有些模式就是为了遵循某原则而设计出来的。 62 | 63 | 抽象不是万能的,在适当的地方使用,需要仔细推敲。当有更好的方案不用抽象就能解决问题时,尽量避免抽象,笔者见过太多的抽象过火过度设计的案例了,增加了太多维护成本,还不如按照最自然的方式去写。 64 | 65 | ## 4. 空杯心态,向身边的同学学习,站在巨人的肩上,站在别人的肩上 66 | 67 | 有人提意见,先收下它(无论接受与否)。 68 | 69 | 很多程序猿,也都有一个毛病,就是觉得自己技术牛的不行,不愿意接受别人的意见,尤其是否定意见(文人相轻)。 70 | 71 | 而无论是理论的学习,还是编码实践,向身边的同学学习将是对自己影响最大的(三人行,必有我师),比刻意参加相关培训要有用的多。 72 | 73 | 我自己就经常在跟团队同学的讨论中获益,当百思不得解的时候,把问题抛出来讨论一下,通常都能得到一个最佳方案。 74 | 75 | 另外,跟团队其他人讨论还有一个好处,就是当你的设计有妥协,有些不专业的时候,别人看到代码也不会产生质疑,因为他参与了讨论的,你不用花那么多时间去做解释。 76 | 77 | 设计期间就一定要找他人讨论,我一直比较反对一个人把设计做完了,把文档写完了,然后才找大家开个评审会那种模式,虽然也有效果,但是效果真的达不到极致,大家没有参与到设计中来,通过一场会议的时间理解不一定有那么深,最关键的是,如果设计有些问题的时候,但也不是致命问题,难道还让打回重新设计么? 78 | 79 | 等前期讨论足够后,大家都知道你的思路与方案,而且最后也有设计文档,当其他人来阅读你的代码的时候,根本无需你再指引,今后的工作交接都不是很需要了,何乐而不为呢? 80 | 81 | 最后,我想在此呼吁一下,当你去修改维护别人的代码时,最好找模块负责人做深入的讨论沟通,让他明白你的需求以及你的方案,请他帮忙评估方案是否可行,是否会踩坑、埋坑等。这样我们的项目才不会出现坏味道蔓延,而如果你恰好是某模块负责人,请行使你的权力,拒绝有问题的不符合要求的代码提交入库。 82 | 83 | > 作者|杨长元 84 | > 来源|[阿里巴巴云原生公众号](https://mp.weixin.qq.com/s/H7L3LVwrVVOUza5bTfB96A) -------------------------------------------------------------------------------- /docs/Laravel/深入剖析 Laravel 框架 Jwt 组件的实现原理.md: -------------------------------------------------------------------------------- 1 | ## jwt 是什么? 2 | 3 | `Json web token (JWT)`, 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(`SSO`)场景。`JWT` 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 4 | 5 | ## 为什么不使用 session,而直接使用jwt呢? 6 | 7 | - `session` 存储在服务器端,而 `Jwt` 存储在客户端。 8 | - `session` 方式存储用户信息的最大问题在于要占用服务器内存,增加服务器的开销,而 `Jwt` 方式将用户状态分散到了客户端中,可以明显减轻服务器的内存压力。 9 | 10 | ## 如何使用 11 | 12 | 1、`composer` 安装 13 | 14 | ``` 15 | composer require tymon/jwt-auth 16 | ``` 17 | 18 | 2、注册服务提供者,`laravel` 框架版本小于5.4时,需要手动在 `config/app.php` 文件中注册服务提供者 19 | 20 | ``` 21 | 'providers' => [ 22 | 23 | ... 24 | 25 | Tymon\JWTAuth\Providers\LaravelServiceProvider::class, 26 | ] 27 | 28 | ``` 29 | 30 | 3、生成配置文件,执行以下命令 31 | 32 | ``` 33 | php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" 34 | ``` 35 | 36 | 4、生成 `jwt` 密钥 37 | 38 | ``` 39 | php artisan jwt:secret 40 | ``` 41 | 你也可以在 `.env` 文件中修改密钥,比如 JWT_SECRET=xxxx 42 | 43 | 5、修改 `auth.php` 文件中的配置,修改 `Laravel` 框架默认的 `auth` 驱动 44 | 45 | ``` 46 | 'defaults' => [ 47 | 'guard' => 'api', 48 | 'passwords' => 'users', 49 | ], 50 | 51 | ... 52 | 53 | 'guards' => [ 54 | 'api' => [ 55 | 'driver' => 'jwt', 56 | 'provider' => 'users', 57 | ], 58 | ], 59 | 60 | 'providers' => [ 61 | 'users' => [ 62 | 'driver' => 'eloquent', 63 | 'model' => App\Models\User\User::class 64 | ], 65 | 66 | // 'users' => [ 67 | // 'driver' => 'database', 68 | // 'table' => 'users', 69 | // ], 70 | ], 71 | ``` 72 | 73 | 上述配置,表示 `auth` 驱动使用 `jwt` 的方式,用户提供者使用 `users`,用户提供者支持 `database` 和 `eloquent` 两种方式 74 | 75 | 6、修改 `User` 模型,实现 `getJWTIdentifier()` 和 `getJWTCustomClaims()` 76 | 77 | ```php 78 | getKey(); 100 | } 101 | 102 | /** 103 | * Return a key value array, containing any custom claims to be added to the JWT. 104 | * 105 | * @return array 106 | */ 107 | public function getJWTCustomClaims() 108 | { 109 | return []; 110 | } 111 | } 112 | ``` 113 | 114 | 7、添加控制器认证中间件 115 | 116 | ```php 117 | /** 118 | * Create a new AuthController instance. 119 | * 120 | * @return void 121 | */ 122 | public function __construct() 123 | { 124 | $this->middleware('auth:api', ['except' => ['login']]); 125 | } 126 | ``` 127 | 128 | 上述代码表示,每次请求都需要经过 `auth` 这个中间件,登录方法除外 129 | 130 | ## 常用方法 131 | 132 | 官方文档写的很清楚,这里直接给出官方的说明文档,地址如下: 133 | 134 | > https://jwt-auth.readthedocs.io/en/develop/auth-guard/ 135 | 136 | ## 源码解析 (重点) 137 | 138 | 这里会对源码进行分析,默认你了解一些基本的知识和框架的运行流程,比如容器,依赖注入,门面模式等知识,这里不会对这些知识和内容过多的阐述,如果有不懂和不理解的地方,欢迎评论,大家系好安全带,老司机要发车了!!! 139 | 140 | ### 用户鉴权 141 | 142 | 当访问控制器中某个方法时,会先经过 `Authenticate` 中间件进行处理 143 | 144 | ```php 145 | //1.1 经过 handle 方法处理请求 146 | public function handle($request, Closure $next, ...$guards) 147 | { 148 | $this->authenticate($request, $guards); 149 | 150 | return $next($request); 151 | } 152 | ``` 153 | 154 | ```php 155 | //1.2 经过 authenticate 方法检查用户是否登录 156 | protected function authenticate($request, array $guards) 157 | { 158 | if (empty($guards)) { 159 | $guards = [null]; 160 | } 161 | 162 | foreach ($guards as $guard) { 163 | // 访问 JwtGuard 中的 check 方法 164 | if ($this->auth->guard($guard)->check()) { // $this->auth->guard($guard) 会生成JwtGuard对象 165 | return $this->auth->shouldUse($guard); 166 | } 167 | } 168 | 169 | $this->unauthenticated($request, $guards); 170 | } 171 | ``` 172 | 173 | ```php 174 | //1.3 访问代码块 GuardHelpers 中的 check 方法,校验用户是否登录 175 | public function check() 176 | { 177 | return ! is_null($this->user()); 178 | } 179 | ``` 180 | 181 | ```php 182 | //1.4 访问 JwtGuard 中的 user 方法 183 | public function user() 184 | { 185 | if ($this->user !== null) { 186 | return $this->user; 187 | } 188 | //获取请求头中的 token,校验 token 是否过期或者在黑名单中,具体的代码请查看源码 189 | if ($this->jwt->setRequest($this->request)->getToken() && 190 | ($payload = $this->jwt->check(true)) && 191 | $this->validateSubject() 192 | ) { 193 | //根据负载 payload 获取用户的信息,然后返回 194 | return $this->user = $this->provider->retrieveById($payload['sub']); 195 | } 196 | } 197 | ``` 198 | 199 | ```php 200 | //1.5 创建用户模型,根据主键获取用户信息并且返回 201 | public function retrieveById($identifier) 202 | { 203 | $model = $this->createModel(); 204 | 205 | return $this->newModelQuery($model) 206 | ->where($model->getAuthIdentifierName(), $identifier) 207 | ->first(); 208 | } 209 | ``` 210 | 211 | 至此,用户鉴权的核心流程就分析完毕了,这里只给出了核心代码,需要大家根据上述代码去查看源码,只有知其然知其所以然,才可以放心大胆的使用扩展包,出现问题也才可以及时的发现和解决,下车!!! 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /docs/Laravel/一篇文章带你搞懂 Laravel 框架底层运行原理.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 知其然知其所以然,刚开始接触框架的时候大不部分人肯定一脸懵逼,不知道如何实现的,没有一定的基础知识,直接去看框架的源码,只会被直接劝退,`Laravel` 框架是一款非常优秀的 `PHP` 框架,这篇文章就是带你彻底搞懂框架的运行原理,好让你在面试的过程中有些谈资(吹牛),学习和研究优秀框架的源码也有助于我们自身技术的提升,接下来系好安全带,老司机要开始开车了!!! 3 | ## 准备知识 4 | - 熟悉 php 基本知识,如常见的数组方法,闭包函数的使用,魔术方法的使用 5 | - 熟悉 php 的反射机制和依赖注入 6 | - 熟悉 php 命名空间概念和 compose 自动加载 7 | - 熟悉常见的设计模式,包括但是不限于单例模式,工厂模式,门面模式,注册树模式,装饰者模式等 8 | ## 运行原理概述 9 | `Laravel` 框架的入口文件 `index.php` 10 | 11 | 1、引入自动加载 `autoload.php` 文件 12 | 13 | 2、创建应用实例,并同时完成了 14 | 15 | ``` 16 | 基本绑定($this、容器类Container等等)、 17 | 18 | 基本服务提供者的注册(Event、log、routing)、 19 | 20 | 核心类别名的注册(比如db、auth、config、router等) 21 | ``` 22 | 23 | 3、开始 `Http` 请求的处理 24 | 25 | ``` 26 | make 方法从容器中解析指定的值为实际的类,比如 $app->make(Illuminate\Contracts\Http\Kernel::class); 解析出来 App\Http\Kernel 27 | 28 | handle 方法对 http 请求进行处理 29 | 30 | 实际上是 handle 中 sendRequestThroughRouter 处理 http 的请求 31 | 32 | 首先,将 request 绑定到共享实例 33 | 34 | 然后执行 bootstarp 方法,运行给定的引导类数组 $bootstrappers,这里是重点,包括了加载配置文件、环境变量、服务提供者、门面、异常处理、引导提供者等 35 | 36 | 之后,进入管道模式,经过中间件的处理过滤后,再进行用户请求的分发 37 | 38 | 在请求分发时,首先,查找与给定请求匹配的路由,然后执行 runRoute 方法,实际处理请求的时候 runRoute 中的 runRouteWithinStack 39 | 40 | 最后,经过 runRouteWithinStack 中的 run 方法,将请求分配到实际的控制器中,执行闭包或者方法,并得到响应结果 41 | ``` 42 | 43 | 4、 将处理结果返回 44 | 45 | ## 详细源码分析 46 | 47 | 1、注册自动加载类,实现文件的自动加载 48 | 49 | ```php 50 | require __DIR__.'/../vendor/autoload.php'; 51 | ``` 52 | 53 | 2、创建应用容器实例 `Application` (该实例继承自容器类 `Container`),并绑定核心(web、命令行、异常),方便在需要的时候解析它们 54 | 55 | ```php 56 | $app = require_once __DIR__.'/../bootstrap/app.php'; 57 | ``` 58 | 59 | `app.php` 文件如下: 60 | 61 | ```php 62 | singleton( 69 | Illuminate\Contracts\Http\Kernel::class, 70 | App\Http\Kernel::class 71 | ); 72 | // 绑定命令行kernel 73 | $app->singleton( 74 | Illuminate\Contracts\Console\Kernel::class, 75 | App\Console\Kernel::class 76 | ); 77 | // 绑定异常处理 78 | $app->singleton( 79 | Illuminate\Contracts\Debug\ExceptionHandler::class, 80 | App\Exceptions\Handler::class 81 | ); 82 | // 返回应用实例 83 | return $app; 84 | ``` 85 | 86 | 3、在创建应用实例(`Application.php`)的构造函数中,将基本绑定注册到容器中,并注册了所有的基本服务提供者,以及在容器中注册核心类别名 87 | 88 | 3.1、将基本绑定注册到容器中 89 | 90 | ```php 91 | /** 92 | * Register the basic bindings into the container. 93 | * 94 | * @return void 95 | */ 96 | protected function registerBaseBindings() 97 | { 98 | static::setInstance($this); 99 | $this->instance('app', $this); 100 | $this->instance(Container::class, $this); 101 | $this->singleton(Mix::class); 102 | $this->instance(PackageManifest::class, new PackageManifest( 103 | new Filesystem, $this->basePath(), $this->getCachedPackagesPath() 104 | )); 105 | # 注:instance方法为将...注册为共享实例,singleton方法为将...注册为共享绑定 106 | } 107 | ``` 108 | 109 | 3.2、注册所有基本服务提供者(事件,日志,路由) 110 | 111 | ```php 112 | protected function registerBaseServiceProviders() 113 | { 114 | $this->register(new EventServiceProvider($this)); 115 | $this->register(new LogServiceProvider($this)); 116 | $this->register(new RoutingServiceProvider($this)); 117 | } 118 | ``` 119 | 120 | 3.3、在容器中注册核心类别名 121 | ![](https://gitee.com/yefangyong/blog-image/raw/master/png/20201222145306.png) 122 | 123 | 4、上面完成了类的自动加载、服务提供者注册、核心类的绑定、以及基本注册的绑定 124 | 125 | 5、开始解析 `http` 的请求 126 | 127 | ```php 128 | index.php 129 | //5.1 130 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 131 | //5.2 132 | $response = $kernel->handle( 133 | $request = Illuminate\Http\Request::capture() 134 | ); 135 | ``` 136 | 137 | 5.1、make方法是从容器解析给定值 138 | 139 | ```php 140 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 141 | 142 | 中的Illuminate\Contracts\Http\Kernel::class 是在index.php 中的$app = require_once __DIR__.'/../bootstrap/app.php';这里面进行绑定的,实际指向的就是App\Http\Kernel::class这个类 143 | ``` 144 | 145 | 5.2、这里对 http 请求进行处理 146 | 147 | ```php 148 | $response = $kernel->handle( 149 | $request = Illuminate\Http\Request::capture() 150 | ); 151 | ``` 152 | 进入 `$kernel` 所代表的类 `App\Http\Kernel.php` 中,我们可以看到其实里面只是定义了一些中间件相关的内容,并没有 handle 方法 153 | 154 | 我们再到它的父类 `use Illuminate\Foundation\Http\Kernel as HttpKernel`; 中找 handle 方法,可以看到 handle 方法是这样的 155 | 156 | ```php 157 | public function handle($request) 158 | { 159 | try { 160 | $request->enableHttpMethodParameterOverride(); 161 | // 最核心的处理http请求的地方【6】 162 | $response = $this->sendRequestThroughRouter($request); 163 | } catch (Exception $e) { 164 | $this->reportException($e); 165 | $response = $this->renderException($request, $e); 166 | } catch (Throwable $e) { 167 | $this->reportException($e = new FatalThrowableError($e)); 168 | $response = $this->renderException($request, $e); 169 | } 170 | $this->app['events']->dispatch( 171 | new Events\RequestHandled($request, $response) 172 | ); 173 | return $response; 174 | } 175 | ``` 176 | 177 | 6、处理 `Http` 请求(将 `request` 绑定到共享实例,并使用管道模式处理用户请求) 178 | 179 | ```php 180 | vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php的handle方法 181 | // 最核心的处理http请求的地方 182 | $response = $this->sendRequestThroughRouter($request); 183 | 184 | protected function sendRequestThroughRouter($request) 185 | { 186 | // 将请求$request绑定到共享实例 187 | $this->app->instance('request', $request); 188 | // 将请求request从已解析的门面实例中清除(因为已经绑定到共享实例中了,没必要再浪费资源了) 189 | Facade::clearResolvedInstance('request'); 190 | // 引导应用程序进行HTTP请求 191 | $this->bootstrap();【7、8】 192 | // 进入管道模式,经过中间件,然后处理用户的请求【9、10】 193 | return (new Pipeline($this->app)) 194 | ->send($request) 195 | ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) 196 | ->then($this->dispatchToRouter()); 197 | } 198 | ``` 199 | 200 | 7、在 `bootstrap` 方法中,运行给定的 引导类数组 `$bootstrappers`,加载配置文件、环境变量、服务提供者、门面、异常处理、引导提供者,非常重要的一步,位置在 `vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php` 201 | 202 | ```php 203 | /** 204 | * Bootstrap the application for HTTP requests. 205 | * 206 | * @return void 207 | */ 208 | public function bootstrap() 209 | { 210 | if (! $this->app->hasBeenBootstrapped()) { 211 | $this->app->bootstrapWith($this->bootstrappers()); 212 | } 213 | } 214 | ``` 215 | 216 | ```php 217 | /** 218 | * 运行给定的引导类数组 219 | * 220 | * @param string[] $bootstrappers 221 | * @return void 222 | */ 223 | public function bootstrapWith(array $bootstrappers) 224 | { 225 | $this->hasBeenBootstrapped = true; 226 | foreach ($bootstrappers as $bootstrapper) { 227 | $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]); 228 | $this->make($bootstrapper)->bootstrap($this); 229 | $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]); 230 | } 231 | } 232 | 233 | /** 234 | * Get the bootstrap classes for the application. 235 | * 236 | * @return array 237 | */ 238 | protected function bootstrappers() 239 | { 240 | return $this->bootstrappers; 241 | } 242 | 243 | /** 244 | * 应用程序的引导类 245 | * 246 | * @var array 247 | */ 248 | protected $bootstrappers = [ 249 | // 加载环境变量 250 | \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, 251 | // 加载config配置文件【重点】 252 | \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, 253 | // 加载异常处理 254 | \Illuminate\Foundation\Bootstrap\HandleExceptions::class, 255 | // 加载门面注册 256 | \Illuminate\Foundation\Bootstrap\RegisterFacades::class, 257 | // 加载在config/app.php中的providers数组里所定义的服务【8 重点】 258 | \Illuminate\Foundation\Bootstrap\RegisterProviders::class, 259 | // 记载引导提供者 260 | \Illuminate\Foundation\Bootstrap\BootProviders::class, 261 | ]; 262 | 263 | ``` 264 | 265 | 8、加载 `config/app.php` 中的 `providers` 数组里定义的服务 266 | 267 | ```php 268 | Illuminate\Auth\AuthServiceProvider::class, 269 | Illuminate\Broadcasting\BroadcastServiceProvider::class, 270 | ...... 271 | 272 | /** 273 | * 自己添加的服务提供者 274 | */ 275 | \App\Providers\HelperServiceProvider::class, 276 | ``` 277 | 278 | 可以看到,关于常用的 `Redis、session、queue、auth、database、Route` 等服务都是在这里进行加载的 279 | 280 | 9、使用管道模式处理用户请求,先经过中间件进行处理和过滤 281 | 282 | ```php 283 | return (new Pipeline($this->app)) 284 | ->send($request) 285 | // 如果没有为程序禁用中间件,则加载中间件(位置在app/Http/Kernel.php的$middleware属性) 286 | ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) 287 | ->then($this->dispatchToRouter()); 288 | } 289 | ``` 290 | 291 | app/Http/Kernel.php 292 | 293 | ```php 294 | /** 295 | * 应用程序的全局HTTP中间件 296 | * 297 | * These middleware are run during every request to your application. 298 | * 299 | * @var array 300 | */ 301 | protected $middleware = [ 302 | \App\Http\Middleware\TrustProxies::class, 303 | \App\Http\Middleware\CheckForMaintenanceMode::class, 304 | \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, 305 | \App\Http\Middleware\TrimStrings::class, 306 | \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, 307 | ]; 308 | ``` 309 | 310 | 10、经过中间件处理后,再进行请求分发(包括查找匹配路由) 311 | 312 | ```php 313 | /** 314 | * 10.1 通过中间件/路由器发送给定的请求 315 | * 316 | * @param \Illuminate\Http\Request $request 317 | * @return \Illuminate\Http\Response 318 | */ 319 | protected function sendRequestThroughRouter($request) 320 | { 321 | ... 322 | return (new Pipeline($this->app)) 323 | ... 324 | // 进行请求分发 325 | ->then($this->dispatchToRouter()); 326 | } 327 | ``` 328 | 329 | ```php 330 | /** 331 | * 10.2 获取路由调度程序回调 332 | * 333 | * @return \Closure 334 | */ 335 | protected function dispatchToRouter() 336 | { 337 | return function ($request) { 338 | $this->app->instance('request', $request); 339 | // 将请求发送到应用程序 340 | return $this->router->dispatch($request); 341 | }; 342 | } 343 | ``` 344 | 345 | ```php 346 | /** 347 | * 10.3 将请求发送到应用程序 348 | * 349 | * @param \Illuminate\Http\Request $request 350 | * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse 351 | */ 352 | public function dispatch(Request $request) 353 | { 354 | $this->currentRequest = $request; 355 | return $this->dispatchToRoute($request); 356 | } 357 | ``` 358 | 359 | ```php 360 | /** 361 | * 10.4 将请求分派到路由并返回响应【重点在runRoute方法】 362 | * 363 | * @param \Illuminate\Http\Request $request 364 | * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse 365 | */ 366 | public function dispatchToRoute(Request $request) 367 | { 368 | // 369 | return $this->runRoute($request, $this->findRoute($request)); 370 | } 371 | ``` 372 | 373 | ```php 374 | /** 375 | * 10.5 查找与给定请求匹配的路由 376 | * 377 | * @param \Illuminate\Http\Request $request 378 | * @return \Illuminate\Routing\Route 379 | */ 380 | protected function findRoute($request) 381 | { 382 | $this->current = $route = $this->routes->match($request); 383 | $this->container->instance(Route::class, $route); 384 | return $route; 385 | } 386 | ``` 387 | 388 | ```php 389 | /** 390 | * 10.6 查找与给定请求匹配的第一条路由 391 | * 392 | * @param \Illuminate\Http\Request $request 393 | * @return \Illuminate\Routing\Route 394 | * 395 | * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException 396 | */ 397 | public function match(Request $request) 398 | { 399 | // 获取用户的请求类型(get、post、delete、put),然后根据请求类型选择对应的路由 400 | $routes = $this->get($request->getMethod()); 401 | // 匹配路由 402 | $route = $this->matchAgainstRoutes($routes, $request); 403 | if (! is_null($route)) { 404 | return $route->bind($request); 405 | } 406 | $others = $this->checkForAlternateVerbs($request); 407 | if (count($others) > 0) { 408 | return $this->getRouteForMethods($request, $others); 409 | } 410 | throw new NotFoundHttpException; 411 | } 412 | ``` 413 | 414 | 到现在,已经找到与请求相匹配的路由了,之后将运行了,也就是 10.4 中的 runRoute 方法 415 | 416 | ```php 417 | /** 418 | * 10.7 返回给定路线的响应 419 | * 420 | * @param \Illuminate\Http\Request $request 421 | * @param \Illuminate\Routing\Route $route 422 | * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse 423 | */ 424 | protected function runRoute(Request $request, Route $route) 425 | { 426 | $request->setRouteResolver(function () use ($route) { 427 | return $route; 428 | }); 429 | $this->events->dispatch(new Events\RouteMatched($route, $request)); 430 | return $this->prepareResponse($request, 431 | $this->runRouteWithinStack($route, $request) 432 | ); 433 | } 434 | ``` 435 | 436 | ```php 437 | 438 | /** 439 | * Run the given route within a Stack "onion" instance. 440 | * 10.8 在栈中运行路由,先检查有没有控制器中间件,如果有先运行控制器中间件 441 | * 442 | * @param \Illuminate\Routing\Route $route 443 | * @param \Illuminate\Http\Request $request 444 | * @return mixed 445 | */ 446 | protected function runRouteWithinStack(Route $route, Request $request) 447 | { 448 | $shouldSkipMiddleware = $this->container->bound('middleware.disable') && 449 | $this->container->make('middleware.disable') === true; 450 | $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route); 451 | return (new Pipeline($this->container)) 452 | ->send($request) 453 | ->through($middleware) 454 | ->then(function ($request) use ($route) { 455 | return $this->prepareResponse( 456 | $request, $route->run() 457 | ); 458 | }); 459 | } 460 | ``` 461 | 462 | ```php 463 | /** 464 | * Run the route action and return the response. 465 | * 10.9 最后一步,运行控制器的方法,处理数据 466 | * @return mixed 467 | */ 468 | public function run() 469 | { 470 | $this->container = $this->container ?: new Container; 471 | 472 | try { 473 | if ($this->isControllerAction()) { 474 | return $this->runController(); 475 | } 476 | 477 | return $this->runCallable(); 478 | } catch (HttpResponseException $e) { 479 | return $e->getResponse(); 480 | } 481 | } 482 | ``` 483 | 484 | 11、运行路由并返回响应(重点) 485 | 可以看到,10.7 中有一个方法是 `prepareResponse`,该方法是从给定值创建响应实例,而 `runRouteWithinStack` 方法则是在栈中运行路由,也就是说,`http` 的请求和响应都将在这里完成。 486 | 487 | ## 总结 488 | 到此为止,整个 `Laravel` 框架的运行流程就分析完毕了,揭开了 `Laravel` 框架的神秘面纱,其中为了文章的可读性,只给出了核心代码,需要大家结合文章自行去阅读源码,需要注意的是必须了解文章中提到的准备知识,这是阅读框架源码的前提和基础,希望大家有所收获,下车!!! 489 | 490 | ## 参考资料 491 | - https://www.haveyb.com/article/298 492 | - https://learnku.com/docs/laravel-kernel **重点,最好读完这些文章,再来看这篇文章** 493 | - https://learnku.com/docs/laravel-core-concept/5.5 --------------------------------------------------------------------------------