├── .gitignore ├── README.md ├── PHP ├── 老项目的迁移手记.md ├── 冷门PHP函数汇总.md ├── PHP GD库解析一张简单图片并输出.md ├── 运行调试你的PHP代码.md ├── PHP程序员必备工具.md ├── PHP程序员必看书籍.md ├── PHP程序员必须知道的两种日志.md ├── PHP程序员如何优雅的搬砖.md ├── PHP程序员如何简单的开展服务治理架构(二).md ├── PHP程序员如何简单的开展服务治理架构(一).md ├── Swoole难上手?从EasySwoole开始.md ├── PHP使用Oracle数据库的准备工作.md ├── PHP程序员如何简单的开展服务治理架构(三).md ├── 老项目重构手记之用户系统.md ├── 一道看似简单的面试题.md ├── 2019 PHP程序员发展路线.md ├── 来!狂撸一款PHP现代化框架 (准备工作).md ├── 不一样的PHP基础知识汇总.md ├── 举枪消灭"烂代码"的实战案例.md ├── 取代PHP原生函数的一些扩展包.md ├── 2020 PHP程序员修炼秘籍.md ├── 使用GrumPHP来纠正代码“毛病”.md └── 来!狂撸一款PHP现代化框架 (路由的设计).md ├── 不要被集成环境束缚住你前进的脚步.md ├── “生于忧患,死于安乐”之程序员人生.md ├── 基于业务设计数据表的总结.md ├── 程序员自省录.md ├── Go ├── PHP To Go 转型手记 (一).md ├── PHP To Go 转型手记 (三).md ├── PHP To Go 转型手记 (二).md ├── PHP To Go 转型手记 (终).md └── Beego 文件上传至七牛云的玩法.md ├── 浅谈架构是为了什么 (上).md ├── Laravel ├── 你可能需要了解下Laravel集合.md ├── Laravel5.4队列简单配置与使用.md ├── Laravel-Action 对代码的改造.md ├── Laravel源码解析之从入口开始.md ├── Laravel源码解析之Model.md └── Laravel源码解析之反射的使用.md ├── 八年以后,我选择了创业.md ├── Shop ├── 电商系统设计之商品[番外篇].md ├── [还魂篇] 初来乍到如何致人于死地.md ├── 电商系统设计之购物车.md ├── 电商系统设计之商品 (中).md ├── 通用系统设计之优惠卷.md ├── 电商系统设计之商品接口.md └── 电商系统设计之用户系统.md ├── Nginx ├── NGINX日志配置总结.md ├── 安装Nginx要从娃娃抓起.md └── GitLab搭建并接入自建Nginx.md ├── MySQL ├── MySQL SQL模式特点汇总.md ├── MySQL常用函数汇总.md └── MySQL常用系统表汇总.md ├── 浅谈重构造成的灾难性毁灭.md ├── 是时候了解下Travis CI是什么了.md ├── 用MAC还安装集成环境可就OUT喽.md ├── RabbitMQ 初体验.md ├── Supervisor 从入门到放弃.md ├── 浅谈架构是为了什么 (下).md ├── Docker ├── 五分钟快速了解Docker.md └── Docker构建程序员的日常.md ├── 论某教育机构考试系统设计.md ├── 优化你的PHP代码,从现在做起.md ├── 我与Jetbrains的这些年.md └── 日常划水:短信验证码开发实例.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | ## About Me 4 | Hi, I am CrazyCodes, born in 1996. I am a PHP development engineer. 5 | 6 | 你好,我是CrazyCodes,出生在1996年。我是一名PHP开发工程师。 7 | 8 | My whole life article will be put here and my blog, I hope every line of code, every text can help you. Although not a big god, but I have been in the pursuit of the big god on the road 9 | 10 | 我一生的文章都会放在这里,我的博客,我希望每一行代码,每一段文字都能帮助你。虽然不是大神,但我一直在追寻大神的路上 11 | 12 | ## Url 13 | The following is a list of all the addresses where I can be found 14 | 15 | 这里列出可以找到我的所有地址 16 | 17 | - segmentfault https://segmentfault.com/u/crazycodes 18 | - blog https://blog.fastrun.cn 19 | - 掘金 https://juejin.im/user/5b4eb3856fb9a04fdd7d516b 20 | - Email crazycodes@yeah.net 21 | - QQ 919342864 22 | - 微信 php365t -------------------------------------------------------------------------------- /PHP/老项目的迁移手记.md: -------------------------------------------------------------------------------- 1 | 收到一个朋友的求救,他现在遇到一个难题。 2 | 3 | 就是刚刚入职的公司,项目没有GIT,用的老版本的TP,CI,代码也很凌乱,目录也是非常的多,有的没的都在服务器上放着,服务器上还有将近30G的用户头像存着,总之就是一个字 “乱” 4 | 5 | 我准备对这个乱到无法整理的公司项目、服务器及架构进行重新整理,接下来,请看我的表演~ 6 | 7 | # 备份 8 | 既然没有git,那就不用git了,直接在线上打包,ftp链接上下载,在毫无头绪的情况下,我建议直接重装。。如果你没有十足把握,建议不要送死。 9 | 10 | #### 公司业务总结构 11 | * 新接口应用程序 12 | * 后台应用程序 13 | * 老接口应用程序 14 | 15 | 仔细探查了下,资源文件只有头像存在本地,其他的都存到了七牛上。这样打包就好办了。不会影响到用户的正常使用。 16 | 17 | 打包后将项目下载到本地。 18 | 19 | 数据库用的阿里云的,所以直接忽略。 20 | 21 | > 此过程做了5个小时。。。东西确实不少。 22 | 23 | # 配置 24 | 公司用的是Apache,找到vhost文件,拷贝一份到本地,整理一下所有的配置文件,目录什么的。 25 | 他公司要求使用Nginx,所以我需要一个个整理出来配置文件,随后再找到ssl for nginx的证书,一切准备就绪。 26 | 27 | # 无感知转发 28 | 我自己有一台4核4g的阿里云ECS,他们公司的量也不大,所以我准备先将项目部署到我服务器上,在公司服务器上进行负载均衡,将我方IP权重调高,将用户流量全部引入到我方服务器上。 29 | > Demo 如下: 30 | ``` 31 | ProxyPass / balancer://proxy/ 32 | 33 | BalancerMember http://192.168.6.37:6888/ loadfactor=3 34 | BalancerMember http://192.168.6.38:6888/ loadfactor=1 35 | 36 | ``` 37 | 38 | 将用户流量转发后,开始重置敌方服务器,准备进行重新部署。 39 | 40 | -------------------------------------------------------------------------------- /PHP/冷门PHP函数汇总.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 整理一些日常生活中基本用不到的PHP函数,也可以说在框架内基本都内置了,无需我们去自行使用的函数。量不多。后续在日常开发中如遇到更多的冷门,会更新本文章 3 | 4 | # sys_getloadavg 5 | 6 | > 获取系统的负载 7 | 8 | ``` 9 | 80) { 12 | header('HTTP/1.1 503 Too busy, try again later'); 13 | die('Server too busy. Please try again later.'); 14 | } 15 | ?> 16 | ``` 17 | # compact 18 | > 创建一个包含变量名和它们的值的数组 19 | ``` 20 | 29 | ``` 30 | # uniqid 31 | > 基于以微秒计的当前时间,生成一个唯一的 ID。 32 | ``` 33 | 36 | ``` 37 | # pack 38 | > 把数据装入一个二进制字符串。 39 | ``` 40 | pack(format,args+) 41 | ``` 42 | | 参数 | 描述 | 43 | | -- | -- | 44 | | format | 必需。规定在包装数据时所使用的格式。 | 45 | | args+ | 可选。规定被包装的一个或多个参数。 | 46 | 47 | ``` 48 | 51 | ``` 52 | # exif_imagetype 53 | > 判断一个图像的类型 54 | ``` 55 | 62 | ``` 63 | 64 | # 致谢 65 | 这篇文章很短,感谢你看完这篇文章。如果有什么冷门的函数,可在评论区留言。有时冷门函数也会帮上大忙不是嘛? -------------------------------------------------------------------------------- /不要被集成环境束缚住你前进的脚步.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2543013788-5bd1724fd8291_articlex.png) 2 | 3 | # 前言 4 | PHP之所以被称为入门快的一门开发语言,其中一个原因是因为他有很完善的集成开发环境,无论是在Windows还是Mac os 上都可一键安装,开启PHP学习之路。我依旧记得当年入门时,仅仅的几分钟则开启了 5 | ``` 6 | echo "Hello World" 7 | ``` 8 | 的不归路,但这也是快捷中存在的“弊端”。 9 | 之所以说其存在“弊端”,是因为“集成“两字造成的,因“集成”而“集成”,会将初学者的思路封装到其中,如果这时候需要学习swoole或者安装一个rtmp的扩展。则瞬间懵逼(至少我当年是懵逼的)。 10 | 11 | 我之前写过一篇源码安装Nginx的文章,被人称为“多此一举”,我依旧认为作为一名程序员,了解其本质是必修功课。 12 | 13 | 本篇文章则讲解下如何在集成环境内安装你需要的扩展。 14 | 15 | # 思路 16 | 集成环境也是将Mysql,PHP,Nginx || Apache封装,写一个脚本将其启动、停止等操作统一化,如果需要安装扩展,这时候你需要先做几件事情。 17 | 18 | 1. 寻找集成环境内的PHP安装目录 19 | 2. 判断操作环境是linux还是windows 20 | 3. 去下载你需要安装的扩展,如果是windows就很简单了,一般都是编译好的dll 21 | 4. 安装好扩展重启你的集成环境即可完成 22 | 23 | # Mac || Linux 安装 24 | mac的内核与linux都是unix,这里统一称为linux。在linux的集成环境中安装php首先与上述我讲解的思路一样开始你的“表演” 25 | 26 | 1.查询PHP安装目录 27 | 一般都会在集成环境目录下的PHP目录 28 | 29 | 2.下载一个同版本的PHP源码包 30 | 这样做是为了避免不必要的版本冲突 31 | 32 | 3.编译将需要安装的扩展加入,例如(伪代码) 33 | ``` 34 | ./configure --with=swoole 35 | ./configure --with=rmtp 36 | ``` 37 | 4.修改PHP配置文件 38 | ``` 39 | extension=swoole.so 40 | ``` 41 | 5.重启集成环境,安装完成 42 | 43 | # 其他 44 | 当然你也可以选择使用Docker去搭建你的本地开发环境,不过他的概念与集成环境一样。 45 | https://segmentfault.com/a/1190000016436478 46 | 47 | 不过你需要先了解下Docker 48 | https://segmentfault.com/a/1190000015407534 49 | 50 | 你也可以了解如何去写一个集成环境 51 | https://segmentfault.com/l/1500000010954416 52 | 53 | # 致谢 54 | 感谢你看到这里,希望本篇文章可以帮到你。 -------------------------------------------------------------------------------- /“生于忧患,死于安乐”之程序员人生.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/2709492982-5b97cfc213a95_articlex.png) 2 | 3 | 4 | # 前言 5 | 这本是《孟子》的一首诗词,拿来用有些惭愧。 6 | 7 | 废话不多讲,本章我们聊聊 8 | 9 | > “生于忧患,死于安乐” 10 | # 忧患 11 | 12 | > 上天要把重任降临在某人的身上,一定先要使他心意苦恼,筋骨劳累,使他忍饥挨饿,身体空虚乏力,使他的每一行动都不如意,这样来激励他的心志,使他性情坚忍,增加他所不具备的能力。 13 | 14 | 程序员是如何成为程序员的大伙有考虑过吗?我自认为是这样的 15 | 16 | > 小学 -> 初中 -> 网吧 -> 高中 -> 网吧 -> 大学 -> 实习 -> 初级 -> 中级 -> 高级 -> 架构师 17 | 18 | 没错,大多人的经历都是如此!这样艰苦的奋斗,不断的努力,使我们在这个行业立足。正是这份兴趣、这份毅力、这份坚持支撑着我们,才让我们走到了现在。 19 | 20 | 现在,当我们工作了很长一段时间后,加班熬夜、受人欺负的日子都随之而去,可能你在想,总算是熬出头了。算是在某某城市扎根了。 21 | 22 | 但其实困难才刚开始,当年的热血消逝的已不剩多少,醒醒各位工程师。 23 | 24 | 25 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/2215176456-5b97cffb13204_articlex.jpeg) 26 | 27 | 28 | # 安乐 29 | 新进入一家公司,从小员工做起,当做到总监的职位时,是否该休息一下了?“不能” 30 | 31 | 如果在一家公司,拿着高薪,顶着乌纱帽,每天悠哉悠哉的无所事事时,你该“自主驱动”了。 32 | 33 | 这个词很好,“自主驱动”并不代表“自我冥想”,如果真如上面所说,你该考虑换个新环境了。 34 | 35 | 当然我并不是奉劝各位跳槽。为何“死于安乐”? 36 | 37 | 在诺大的城市,有公司愿意用你,是你的福气。但如果真到了无所事事的那一天,我希望你不念旧情,世界那么大,你确定不出去试试? 38 | 39 | 这时有人说了,是想换下工作,但是行业不景气,技术又不好,导致蠢蠢欲动变成伺机而动。 40 | 41 | 不要幻想春暖花开,你都没种花,即使春暖了,你也只是少穿一件衣服而已。 42 | 43 | 阻挡你的原因无非就以下几种 44 | - 他处工资不高,习惯了高工资,所以不跳 45 | - 技术不好,其他公司很难收留 46 | - 自己在公司职位不低,不愿去其他公司低声下气 47 | - 舍不得现在的公司 (这纯属扯淡) 48 | - 新面的公司离住处太远 (都奔赴了几百公里来到这座城市,不嫌远了吗?) 49 | .... 等等等等 50 | 51 | 程序员这份职业从接触那天起,就注定一生永不停止的学习。如果因为以上原因,停住了脚。 52 | 53 | **注定会失败** 54 | 55 | 56 | # 致谢 57 | 本章没有致谢,希望本章可以再次激起你的热血。 58 | -------------------------------------------------------------------------------- /基于业务设计数据表的总结.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/1407886238-5baee9babc382_articlex.png) 2 | 3 | # 前言 4 | 这是一篇日常开发中根据产品需求去设计数据表的总结。 5 | 6 | 抛去测试、架构来说,数据表设计是指定功能开发的一个起点,如果出现失误将会对未来开发以及运行都会有很大的影响。接下来我们聊聊应该如何根据需求去设计数据表。 7 | 8 | # 原型图 9 | 首先产品递交的绝笔是份原型图或者需求文档,这里先看原型图,根据原型图上的任意label、input汇总,再根据逻辑不同去划分为不同的块(也就意味着不同的表)。以电商优惠卷为例,原型图可能是这样的(原谅我不会画这图,就拿京东的一张图代替吧)。 10 | 11 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/562310338-5baee32c79ec4_articlex.png) 12 | 13 | 当你看到这张原型后,首先你得确认这是个什么功能是吧。瞅了一眼是优惠券的功能,首先确立有了第一张表和表名 14 | - coupons 15 | 再者优惠券是给予用户使用的,所以又有了 16 | - user_coupons 17 | 18 | 一个是优惠券存库表,一个是发送优惠券的用户表,至此表名想好了。接下来就是字段了。 19 | 20 | # 分离 21 | 根据上述原型图我们可以将字段所属逻辑区域分成以下这样 22 | 23 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/4202966-5baee5d533d05_articlex.png) 24 | 25 | 将以原型图为基准的逻辑分为两大块,一块是显示相关的字段,另一块则是控制相关的字段(如添加时间,过期时间,状态等)。 26 | 27 | 任意一家公司都想要有相关行业经验的开发者,并不是因为技术有多高深,而是可以根据给出的具有局限性的原型图去扩展字段。考虑未来业务发展所需要的,这实际也是架构的一部分。 28 | 29 | # 扩展 30 | 31 | 大概的以张图来说明所说的扩展字段。 32 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/3622964260-5baee73f77ef4_articlex.png) 33 | 34 | 通过以自己的日常生活经验和开发经验对具有局限性的原型图进行扩展。是一个业务程序员最起码的技能。 35 | 在之后,将要面临一些性能的考虑了。 36 | 37 | # 性能 38 | 本章并不会详细将数据表的设计上,就大概的根据优惠券的功能讲解下思路。 39 | - 批量发放应该考虑的技术问题 40 | - 考虑频繁被查询的字段设置索引,例如优惠券功能?标题等... 41 | - 有效期使用datetime设置,时间戳对于sql查询的局限太大。 42 | 43 | 一张原型图迁出的考虑因素很多。也是一名程序员的基础。 44 | 45 | # 致谢 46 | 感谢你看到这里,希望本篇文章可以帮助到你,谢谢。 -------------------------------------------------------------------------------- /程序员自省录.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/08/3693954934-5b8510ab07c00_articlex.png) 2 | 3 | 4 | # 前言 5 | 本文具有批判性,如有误解请移步右上角叉叉。 6 | 7 | # 为什么做程序员? 8 | 9 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/08/3533507639-5b54705af2d40_articlex.png) 10 | 11 | 我相信每位入行的童鞋 (除了高工资,有免费大桶水喝的办公室,不干体力活的各种原因外),都有想用代码改变世界的想法。我也是这样,在苦苦挣扎了五年后,我发现我并未成功改变世界,而是让代码改变了自己。为什么要做程序员?是对程序的热爱,对代码的忠诚,如果看到这里,你并非如此,请右上角叉叉离开。因为你不适合看这篇文章。Sorry。 12 | 13 | # 怎么做好程序员? 14 | 15 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/08/3642378277-5b547158f12ca_articlex.png) 16 | 17 | 好与不好,每个人的界定都不一致,我自认为好的程序员是追求完美的但从不口出狂言。作为一名程序员,应该跟科研人员一样,不断探索,不断创新,才是程序员的根本,要用脑敲代码而不是用手敲代码。(秃顶是你的目标😄) 18 | 19 | # 请相信自己 20 | 21 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/08/174788031-5b850e0c6c7e9_articlex.png) 22 | 23 | 进入这个行业,你总有学不完的知识,技术,玩法。每天都在学习都感觉跟不上大牛的脚步。别害怕,其实大牛也是这么想的。做好自己的本职工作,认真对待自己的每行代码。不要每天想着去处理高并发、大数据的方案。没碰到的时候永远都是空想。 24 | # 行业名次的误导 25 | 例如MVC架构,MVC框架,架构,框架还有各种各样的名次,新生的开源程序,队列、数据、并发处理的各种各样的方案。真正遇到的时候再去深入也不迟。当然我不是指的基础知识。 26 | # 请把复杂的事看简单 27 | 当拿到一份复杂的需求,当然绝不是“根据用户手机壳颜色变化App主题”一类的非(da)人(sha)类(bi)需求。首先尝试使用逻辑图,思维图去解刨需求,将代码设计、数据设计、扩展设计拆分出来。规划清楚,再动手,这时候你会发现,做这些设计你只用了一小时,并没有浪费那么多的时间,很多时候不去做,仅仅是嫌麻烦,并非没有时间、没有机会去做。这至少是一个架构师起步的工作。 28 | # 请尊重你看不起的人 29 | 越瞧不起的人越有可能超越你。我面试的时候非常尊重每位面试者,因为我知道今天我是面试官,明天她\他就有可能跟我互换位置。还是本着为人谦虚、诚信待人的原则去奋斗吧。 30 | # 公司的信任 31 | 对于公司对你的信任,不要滥用职权、消费信任。坑一个公司就少一条路。实际程序员的圈子很小。早晚会吃亏。 32 | # 致谢 33 | 感谢你看到这里,本篇文章是我从业五年的一些个人想法。仅供阅读。谢谢 -------------------------------------------------------------------------------- /Go/PHP To Go 转型手记 (一).md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2978289583-5bd423e85207c_articlex.png) 2 | 3 | # 前言 4 | 作为一名PHP程序员,我感到荣幸。但在时代不断的变迁中,要具备足够的知识才可生存。 5 | 6 | 那就从Go语言学起把。 7 | 8 | 希望看到本篇文章的你可以对Go有一个基本的认识。本系列文章与我自己学习Go语言的方式去描述。以PHP代码与Go代码的对比加以区分理解。 9 | # 变量 10 | PHP 11 | ``` 12 | // 初始化变量 13 | $domain = "blog.fastrun.cn"; 14 | // 批量赋值 15 | $domain = $domain1 = $domain2 = "blog.fastrun.cn"; 16 | 17 | ``` 18 | Go 19 | ``` 20 | // 初始化变量 21 | var domain string = "blog.fastrun.cn" 22 | // 批量赋值 23 | var domain,domain1,domain2 string = "blog.fastrun.cn" 24 | // 批量声明赋值 25 | var username,age,local = "zhangsan",13,"BeiJing" 26 | var( 27 | username="zhangsan" 28 | age = 13 29 | local = "BeiJing" 30 | ) 31 | ``` 32 | # 常量 33 | PHP 34 | ``` 35 | define("FOO","something"); 36 | ``` 37 | Go 38 | ``` 39 | // 单独声明 40 | const FOO [string]= something 41 | // 批量声明 42 | const ( 43 | USERNAME = "zhangsan" 44 | AGE = 30 45 | ) 46 | ``` 47 | # 打印 48 | PHP 49 | ``` 50 | // 基本输出 51 | echo "blog.fastrun.cn"; 52 | // 格式化输出 53 | printf("my blog %s","blog.fastrun.cn"); 54 | ``` 55 | Go 56 | ``` 57 | // 基本输出 58 | fmt.Println("blog.fastrun.cn") 59 | // 格式化输出 60 | fmt.Printf("my blog %s","blog.fastrun.cn") 61 | ``` 62 | # 函数 63 | PHP 64 | ``` 65 | // 基本声明 66 | function printString(string $string){ 67 | echo $string; 68 | } 69 | // 带返回值 70 | function printString(string $string) : string{ 71 | return $string; 72 | } 73 | ``` 74 | Go 75 | ``` 76 | // 基本声明 77 | func printString(s string){ 78 | fmt.Println(s) 79 | } 80 | // 带返回值 81 | func printString(s string) string{ 82 | return s 83 | } 84 | ``` 85 | # 致谢 86 | 感谢你看到这里,希望本篇文章可以帮到你。谢谢 -------------------------------------------------------------------------------- /浅谈架构是为了什么 (上).md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/08/748208072-5b851ae98fa87_articlex.png) 2 | 3 | # 前言 4 | 5 | 架构是一款软件从0到100的演变过程。并非是上来就可以承载什么亿级访问的牛x架构什么的。本篇写给那些想要成为架构师或者正在尝试成为架构师的朋友。 6 | 7 | - 陕西的城墙有架构,阻挡外来攻击 8 | - 兵马俑黄陵有架构,避免根基倒塌 9 | 10 | 这是硬性架构,在初期就应考虑清楚其稳定性。 11 | 12 | - 餐厅的人员配置,菜谱的交替更换以及管理的不断完善。 13 | 14 | 这是软性架构,考虑扩展性。 15 | 16 | # why 17 | 为什么要做架构?有一部分人是这样说的 18 | - 做软件就需要架构 19 | - 没架构的软件不靠谱 20 | - 我是架构师这软件我必须做架构 21 | - 我在学习架构,所以我接手的项目要做架构 22 | 23 | 各位朋友,生活如此多娇,不必互相残害。架构是要做。实际每日的工作中,你一直在架构,感觉到了吗?例如下面的一些日常工作 24 | - 这块的业务响应速度有些慢,我们需要想办法提升速度 25 | - PHP线程经常挂掉,单机配置到极限,我们需要方案去解决 26 | - 数据库经常出现死锁,查看哪块业务造出的并提出解决方案 27 | - 这块的业务耦合太高了。我们开会讨论如何做。 28 | 29 | 是日常工作中,你无时无刻的在架构,而你与架构师唯一的区别是你是遇到问题再想解决方案,而架构师会提前想好,例如这种方案可以去解决某个问题,但也需要考虑其弊端,弊端出现的方案是什么样的。实际程序员与架构师不分家。 30 | 31 | # 设计 32 | 架构设计覆盖一款应用运行的各个方面。包括 33 | 34 | - 物理架构 35 | - 逻辑架构 36 | - 数据架构 37 | - 代码架构 38 | 39 | 在项目开发初期,没必要将这四个名次想的过于复杂。举个例子 40 | 41 | ## 物理架构 42 | 作为一个创业公司,公司资金不足,业务也不是太多,数据也不多。那就可以选择 43 | ``` 44 | 阿里云ECS 4M带宽 4G内存 45 | ``` 46 | 就完全可以解决实际需求。多整几台服务器做负载、主从完全没必要。 47 | 48 | ## 逻辑架构 49 | 业务不复杂,将C层,V层,M层分清楚即可。不必要玩什么子系统,例如消息子系统,用户子系统,支付子系统。不仅没帮上什么忙。反而整的自己乱七八糟。很多程序员认为如果在前期不全部设计好,后期很难维护。这其实是一个错误的想法。人无完人,备不住前期设计的还不如后期设计的好呢? 50 | 51 | ## 数据架构 52 | 在前期数据量不大的时候,完全可以使用单机数据库去存储,玩各种主从,主主你自己不嫌累吗。当然也有例外,对安全特别看重的一系列业务还是需要做主从的。 53 | 54 | ## 代码架构 55 | 在模块设计上井然有序就可以了。不要出现伪代码,烂代码。 56 | 57 | # 扩展 58 | 扩展这个事一直是束缚我“放肆”的一把刀。下篇文章我们会讲这把刀的神秘之处。 59 | 60 | # 致谢 61 | 感谢你看到这里,能看到这里你一定是希望提升自己的能力,也希望自己做的每一个项目都能像巨人一样强大。当然我也希望这样。我相信每个程序员都有一个改变世界的梦想。架构并不是一个多么神秘的职业。请等待我下篇文章给朋友们去演示我公司的架构演变。虽然敌不过大厂的架构。但很实用。谢谢 62 | 63 | 我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=hbjeggsrtt0l -------------------------------------------------------------------------------- /PHP/PHP GD库解析一张简单图片并输出.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/06/73678c8cd3692cf204938a47fc1af0e7.png) 2 | 3 | > 这里只演示一下2种颜色值的图片,简单描述下概念。 4 | 5 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3158259558-5ae2851854b2f_articlex.png) 6 | 7 | 首先要安装下GD库。否则下面的代码运行不了。 8 | 9 | ```php 10 | $size = getimagesize('2.png'); // 获取图片大小 11 | $res = imagecreatefrompng('2.png'); // 获取指定图片的资源对象 12 | 13 | for ($i = 0; $i < $size[1]; ++$i) { 14 | for ($j = 0; $j < $size[0]; ++$j) { 15 | $rgb = imagecolorat($res, $i, $j); // 获取坐标索引 16 | 17 | $rgbarray = imagecolorsforindex($res, $rgb); // 获取每个坐标的rgb颜色 18 | 19 | 20 | $sum = $rgbarray['red'] + $rgbarray['green'] + $rgbarray['blue']; // rgb颜色数值相加,主要为了区分 21 | 22 | /** 23 | * 演示图片有纯黑色 rgb(0,0,0) 颜色和其他颜色组成 24 | */ 25 | if ($sum == 0) { 26 | $data[$i][$j] = 1; 27 | } else { 28 | $data[$i][$j] = 2; 29 | } 30 | 31 | } 32 | ``` 33 | 上述代码已经生成了整张图片每个像素的颜色块。 34 | 35 | ``` 36 | echo "
"; 37 | 38 | for ($i = 0; $i < count ($data); $i++) { 39 | if (array_sum ($data[$i]) != 200) { 40 | for ($j = 0; $j < count ($data[$i]); $j++) { 41 | if ($data[$i][$j] == 1) { 42 | echo '
'; 43 | } else { 44 | echo '
'; 45 | } 46 | } 47 | } 48 | } 49 | 50 | echo "
"; 51 | 52 | ``` 53 | 通过上述代码就可以生成一个与指定图片一样的通过像素块堆积出来的图片。 54 | 55 | > similar_text 函数可以判断2个值的相似度。我再考虑是否可以使用a图片的二进制码和b图片的比对。判断相似度呢。 56 | 仅仅是个概念,还再继续研究,这样就可以实现文字识别的功能了。 -------------------------------------------------------------------------------- /Laravel/你可能需要了解下Laravel集合.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/3276665162-5ba45dfac43fc_articlex.png) 2 | 3 | # 前言 4 | 集合通过 ```Illuminate\Support\Collection ``` 进行实例,Laravel的内核大部分的参数传递都用到了集合,但这并不代表集合就是好的。Laravel作为快捷并优雅的开发框架,是有他一定的道理所在的,并非因他的路由、DB、监听器等等。当你需要处理一组数组时,你可能就需要它帮助你快捷的解决实际问题。 5 | # 创建集合 6 | ``` 7 | $collection = collect([1, 2, 3]); 8 | ``` 9 | 显而易见,这是一部非常简单的操作,请打住你想说“这种操作很复杂”的话,它更类似与早起PHP5.x的版本的声明方式。 10 | ``` 11 | $collection = array(1,2,3); 12 | ``` 13 | laravel对于collection也没有做任何复杂的事情,会在下一章 《Laravel源码解析之集合》,谢谢 14 | # 打回原型 15 | 如果你想将集合转换为数据,其使用方法也非常的简单 16 | ``` 17 | collect([1, 2, 3])->all(); 18 | ------> 19 | [1, 2, 3] 20 | ``` 21 | 在不过与考虑性能的情况下,可以使用Laravel集合,毕竟它将帮你完成数组操作的百分之九十的工作。 22 | 例如我们需要通过一个水平线切分数组,将其分为2个及以上的数组个数。使用集合可以酱紫做~ 23 | ``` 24 | $collection = collect([1, 2, 3, 4, 5, 6, 7]); 25 | 26 | $chunks = $collection->chunk(4); 27 | 28 | $chunks->toArray(); 29 | 30 | // [[1, 2, 3, 4], [5, 6, 7]] 31 | ``` 32 | 并且有些还根据sql语句的查询方式来设计的方法,下面就让来看下具体都有哪些吧。 33 | # 方法列表 34 | 这里列出一些常用的集合操作方法,具体及全部请操作官方。 35 | 36 | | 方法 | 注释 | 37 | | -- | -- | 38 | | all | 将集合打回原型 | 39 | | average & avg | 计算平均值| 40 | | chunk | 将集合拆成多个指定大小的小集合 | 41 | | collapse | 将多个数组的集合合并成一个数组的集合 | 42 | | combine | 可以将一个集合的值作为「键」,再将另一个数组或者集合的值作为「值」合并成一个集合 | 43 | | concat | 将给定的数组或集合值附加到集合的末尾 | 44 | | contains | 判断集合是否包含给定的项目 | 45 | | count | 返回该集合内的项目总数 | 46 | | dd | 打印集合的项目并结束脚本执行 | 47 | | diff | 将集合与其它集合或纯 PHP 数组进行值的比较,然后返回原集合中存在而给定集合中不存在的值 | 48 | | each | 迭代集合中的内容并将其传递到回调函数中 | 49 | | filter | 使用给定的回调函数过滤集合的内容,只留下那些通过给定真实测试的内容 | 50 | | first | 返回集合中通过给定真实测试的第一个元素 | 51 | | groupBy | 根据给定的键对集合内的项目进行分组 | 52 | | push | 把给定值添加到集合的末尾 | 53 | | put | 在集合内设置给定的键值对 | 54 | | sortBy | 通过给定的键对集合进行排序。排序后的集合保留了原数组键 | 55 | | where | 通过给定的键值过滤集合 | 56 | 57 | # 致谢 58 | 感谢你看到这里,希望本篇能够帮助到你。谢谢,还不抓紧去练习下集合? -------------------------------------------------------------------------------- /PHP/运行调试你的PHP代码.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/180571913-5b6c5175aebb2_articlex.png) 2 | 3 | # 前言 4 | 没有任何一名程序员可以一气呵成、完美无缺的在不用调试的情况下完成一个功能或模块。调试实际分很多种情况。本篇文章我分享下自己在实际开发工作中的经验,我个人理解,调试分三种,注意我所讲的是调试并非**测试** 5 | 6 | - 功能调试 7 | - 组件调试 8 | - 片段调试 9 | 10 | 功能调试是指在完成一个功能或者正在完成功能的过程中进行的错误、逻辑、结果的测试 11 | 12 | 组件调试是指将一个正在开发测试的插件、类进行错误、逻辑和结果的测试 13 | 14 | 片段调试是指将一段代码片段、函数、变量的预期和结果的测试 15 | 16 | 接下来我们看下本章列出的一些调试方法 17 | 18 | # 暴力调试 19 | 这种方式简单粗暴,一般PHP程序员都会用,那就是浏览器调试,在编辑器内写完代码后随后打开万能的浏览器输入地址开始调试代码。 20 | 21 | # 断点调试 22 | 说实在的,这种方式如果仅仅是看看输出结果,完全没必要,看似高大上实际浪费时间经历,一般我都会在代码块写好之后跑一遍debug,检查一下流程和结果是否在我的预期内或者遇到了某些逻辑问题无法察觉,还是选择断点调试比较靠谱,省时省力。最厉害的程序员也又懵逼的一刻不是吗? 23 | 24 | # 命令调试 25 | 如果你在官网或者github上下载了一段代码片段,急于调试但是还不愿意将代码片段放入项目中或者新开辟一个目录,完全可以使用php命令去执行这个代码块 26 | ``` 27 | php -f filename.php 28 | ``` 29 | 如果代码片段较短你可以使用 30 | ``` 31 | php -r "code ..." 32 | ``` 33 | 执行运行PHP代码 34 | 35 | # 片段调试 36 | 当你突然想起一个方法或者算法急于去验证他,但又不愿意打开编辑器或者执行php自带的命令行的话。你可以选择使用`psysh`工具去执行验证你的想法,这个工具安装十分简单 37 | ``` 38 | Psy Shell v0.8.17 (PHP 7.1.14 — cli) by Justin Hileman 39 | New version is available (current: v0.8.17, latest: v0.9.6) 40 | >>> date('Y-m-d H:i:s',time()) 41 | => "2018-08-09 14:18:10" 42 | >>> 43 | ``` 44 | 下载地址 : https://psysh.org/ 45 | 46 | # 测试调试 47 | 这是一个严肃的调试方法,很多程序员都认为功能模块、组件开发完就结束的任务,随后交给测试人员去测试就好了。其实并不然,有些隐藏的bug或者致命错误、逻辑错误、系统错误(例如开启了无用的线程,未使用的变量、方法、类等等)是无法被测试出来的。实际作为一名程序员,开发功能的流程是这样的。 48 | ``` 49 | 理解需求 -> 分解需求 -> 整理逻辑 -> 设计代码 -> 敲代码 -> 调试 -> 测试 50 | ``` 51 | 如果最后一步你从来没有做过,那你只能是半个程序员。 测试我推荐使用PHPunit,更好的去理解自己的代码,才是一个合格的程序员。 52 | 53 | # 编辑器调试 54 | 感觉讲这个有点废话了,如果你感觉打开命令行然后输入 ```php xxxx``` 特别费劲的话,你也可以选择使用编辑器自带的调试,一般像sublime,visual Studio Code , PHPStrom 都会自带的调试工具,其原理依旧是调用 ``` php -r ``` 但总比打开命令行输入命令要快的多。 55 | 56 | # 致谢 57 | 这并不是一篇酝酿了很久的文章,是在我日常的开发中经常使用的几种调试方法。 58 | 感谢你看到这里,希望本篇文章可以帮助到你,有什么问题可以在评论区留言。谢谢 59 | -------------------------------------------------------------------------------- /Go/PHP To Go 转型手记 (三).md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2978289583-5bd423e85207c_articlex.png) 2 | # 前言 3 | 作为一名PHP程序员,我感到荣幸。但在时代不断的变迁中,要具备足够的知识才可生存。 4 | 5 | 那就从Go语言学起把。 6 | 7 | 希望看到本篇文章的你可以对Go有一个基本的认识。本系列文章与我自己学习Go语言的方式去描述。以PHP代码与Go代码的对比加以区分理解。 8 | 9 | Go没有类的概念,本章在语法使用上来对比PHP与Go之间的区别。 10 | 11 | # 声明类 12 | PHP 13 | ``` 14 | class User{} 15 | ``` 16 | Go 17 | ``` 18 | type User struct{} 19 | ``` 20 | # 成员变量 21 | PHP 22 | ``` 23 | class User{ 24 | public $name; 25 | public $age; 26 | } 27 | ``` 28 | Go 29 | ``` 30 | type User struct { 31 | username string 32 | age int 33 | } 34 | ``` 35 | # 变量修饰 36 | PHP 37 | ``` 38 | class User{ 39 | public $name; 40 | private $age; 41 | } 42 | ``` 43 | Go 44 | ``` 45 | // 没有看错,Go中没有保护(protected),变量名首字母大写为public,小写为private 46 | type User struct { 47 | Username string 48 | Age int 49 | } 50 | ``` 51 | # 成员方法 52 | PHP 53 | ``` 54 | class User{ 55 | public $name; 56 | public $age; 57 | 58 | function setName(){ 59 | 60 | } 61 | 62 | function getName(){ 63 | 64 | } 65 | } 66 | ``` 67 | Go 68 | ``` 69 | type User struct { 70 | username string 71 | age int 72 | } 73 | 74 | func (u User) setName(name string) bool { 75 | u.username = name 76 | 77 | return true 78 | } 79 | 80 | func (u User) getName() string { 81 | return u.username 82 | } 83 | ``` 84 | # 初始化 85 | PHP 86 | ``` 87 | // php 没有构造方法的话,新建对象无需传参数 88 | new User(); 89 | ``` 90 | Go 91 | ``` 92 | // go 内结构体声明的变量是可选参数,既可传可不传,go既没有类概念,所以也没有构造方法。 93 | User{"zhangsan",15} 94 | ``` 95 | # 使用 96 | PHP 97 | ``` 98 | $user = new User(); 99 | $user->getName(); 100 | ``` 101 | Go 102 | ``` 103 | u := User{"zhangsan",15} 104 | fmt.Println(u.getName()) 105 | ``` 106 | # 致谢 107 | 感谢你看到这里,希望本篇文章可以帮到你。谢谢 -------------------------------------------------------------------------------- /八年以后,我选择了创业.md: -------------------------------------------------------------------------------- 1 | 2 | # 前言 3 | 4 | 大家好,我是CrazyCodes,相信很多手握PHP大刀的朋友都认识我,在这2019年末,我为大家分享我2019年心酸历程。在2019年里 5 | 6 | - 我从安逸的公司跳槽准备找一家可以燃起斗志的公司 7 | - 最终没找到,最后集结几个小伙伴着手开始创业 8 | - 我与女友结束了长达七年的爱情长跑,最终步入婚姻殿堂 9 | - 现在,我正在为创业寻找出路 10 | 11 | # 聊聊segmentfault 12 | 我算是思否比较早的一批用户,在2015年的时候加入思否的。伴随的思否一起成长,还有几天时间,我就踏踏实实的在圈内混了八年了。我算不上什么大佬,出身野路子,摸爬滚打这些年,算是积累了一些项目经验。分享了一些个人见解。 13 | 14 | 嘚瑟一下这些年在思否的成果吧。 15 | 总共产出了76篇文章,总阅读量为180772,获得4351次各位的认可,总粉丝数8483 16 | 17 | 嘚瑟结束,回到正题 18 | 19 | # 聊聊我的经历 20 | 我本是父母眼里毫无前途的小毛孩,步入开发这个大家庭也是出于兴趣,从小学就开始跑网吧的我,不知道被揍过多少次了,我应该是最早的一波写游戏脚本的人,那时候并不是出于乐趣,单纯是想在游戏里风雨一番(幸好知错就改,早早的抛弃了这门手艺😆) 21 | 22 | 我从小唯一的乐趣就写画画(真人素描),中考那年,我以优质的105分的艺术分加100多分的文化课,成功落榜所有高中,最终只能被父母安排到市里的技校。 23 | 24 | 第一次接触编程语言,是在搬入学校宿舍后捡到的学哥的一本C语言。这是开启的编程之路的开始。因为本身在网吧里有的没的学过一些。便一直深入的学下去了,可惜本身没有基础,上学的时候也没好好学习,最终放弃了,改为做前端(那时候叫网页制作) 25 | 26 | 随后来到北京培训,就这样,一年、二年、三年...一直到现在 27 | 28 | # 聊聊为什么离职 29 | 我认为如果在一家公司工作,如果没有主人翁感,没有编码的热情,仅仅是为了拿每个月的工资,那就趁早离职吧。 30 | 31 | 我是在2016年进入的我上一家公司,这也是我人生的转折点,是这家公司彻底的改变了我。 32 | 33 | 刚开始的每位同事还是热血沸腾的,我们每天加班加点的去完成项目,当时条件还是比较艰苦的,没有空调,夏天热的要死,冬天冻的带手套敲代码,就这样持续了大半年的时间。项目总算上线了。 34 | 35 | ... 36 | 37 | 中间的心酸历程就不说了,到了第三年,也就是今年,我决定离开温室。抛掉所谓的技术总监,舒适的办公环境和友爱的小伙伴们。 38 | 39 | 其实为什么离职了,原因就俩个字:腻了 40 | 41 | 程序员需要什么?成就感与满足感。如果每段代码都敲打的毫无意义,我变认为不需要在待着了。 42 | 43 | # 为什么要创业? 44 | 创业嘛,梦而已~ 尽量让自己的理由随意点(其实不是太想说真实想法) 45 | 46 | 为了实现梦想而创业的人不在少数。成功的人屈指可数,甚至根本就没有 47 | 48 | - 2019年5月31号 公司成立,拉拢小伙伴一起搞 49 | - 2019年8月1号 第一个项目上线 50 | - 2019年9月26号 第一个项目失败 51 | - 2019年10月10号 蜗行项目启动 52 | - 2019年11月25号 蜗行上架AppStore 53 | - 2019年12月27号 我依旧在找出路 54 | 55 | 其实吧,我希望拥有这样刺激的人生,唯唯诺诺的工作一辈子不是我想要的结果,老了连吹牛逼的资本都没有,不是嘛?~ 56 | 57 | 这半年的时间,见证了什么是人心,看透了所谓的兄弟情义。 58 | 坚持未必是成功,但是缺成为接近成功的开始,敢于迈出第一步,我认为首先在意义上我是成功的,剩下的... 59 | 60 | 五味杂瓶已打翻,就讲这些吧。 61 | 62 | 创业的苦其实说不出,但说句题外话,切记不要兼职创业,梦本来就很难实现了,但却只留下仅仅几个小时去做,就根本没有可能了。 63 | 64 | 我是CrazyCodes,2020年我在创业的路上 65 | 66 | # 致谢 67 | 感谢你看到这里,希望本篇文章可以让你在创业前在想好 -------------------------------------------------------------------------------- /PHP/PHP程序员必备工具.md: -------------------------------------------------------------------------------- 1 | > 行装无兵器,怎能行走天下呢。 2 | 3 | # Postman 4 | 5 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/1382589500-5b3353003f3e7_articlex.png) 6 | 7 | Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。 8 | 9 | Postman适用于不同的操作系统,Postman Mac、Windows X32、Windows X64、Linux系统,还支持Postman 浏览器扩展程序、Postman chrome应用程序等。 10 | ## 个人想法 11 | 现代因前端不断发展,大部分公司都做到前后端分离,PHP已变成纯纯的后端语言,大部分功能的PHP都在开发接口相关的工作。所以你不可缺少它。 12 | ## 官网 13 | https://www.getpostman.com/ 14 | 15 | # PhpStorm 16 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/853038487-5b3352d7688f5_articlex.png) 17 | 18 | PhpStrom 是一款php程序开发的开发工具,提供windows,linux,mac版本 19 | ## 个人想法 20 | 你无法拒绝的万能编辑器,官网吹牛逼的一句话 Lightning-smart PHP IDE , PhpStrom是收费的,请支持正版 21 | ## 官网 22 | http://www.jetbrains.com/phpstorm/ 23 | # Lantern 24 | 25 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/124486391-5b3353db58dde_articlex.png) 26 | 27 | 遇到问题请谷歌,勿百度。 28 | 这是一款轻量级的*墙软件 29 | ## 个人想法 30 | 作为一名开发者,还是需要经常的去国外的技术论坛、博客去了解最新技术。光吃人家吃剩下的也不好是吧~ 31 | ## 官网 32 | http://www.getlandeng4.org/ 33 | # Sequel Pro 34 | 35 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2205918792-5b33542e26448_articlex.png) 36 | 37 | Sequel Pro是一个快速、易用的Mac数据库管理应用程序,用于使用MySQL数据库。完美的Web开发伙伴,无论您是…… 38 | 39 | ## 个人想法 40 | 如果你使用的是mac,我建议你用Sequel Pro,这是我在mac上使用bug最少的数据库管理软件了 41 | ## 官网 42 | http://www.sequelpro.com/ 43 | # VLC & OBS 44 | VLC 和 OBS 都是屏幕录制和视频直播软件,如果你在开发直播相关业务时,他俩是好帮手 45 | # Charles 46 | 47 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3821770827-5b3354edef21b_articlex.png) 48 | 49 | Charles是一个HTTP代理/ HTTP监视器/反向代理,使开发人员能够查看他们的机器和Internet之间的所有HTTP和SSL / HTTPS通信。这包括请求、响应和HTTP头(其中包含cookie和缓存信息)。 50 | ## 个人想法 51 | 不仅仅是看自己的,也可以看别人的。一般我用到这个都是在摸索大厂内接口参数及开发的规范。 52 | 这可是个神奇的工具,请自行下载体验 53 | ## 官网 54 | https://www.charlesproxy.com/ 55 | # iTerm 56 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/4112197294-5b33559060aba_articlex.png) 57 | 58 | ## 个人想法 59 | iTerm是mac上自我感觉最完美的终端工具了,功能强大且不失优雅 60 | 61 | ## 官网 62 | https://www.iterm2.com/ 63 | # PsySH 64 | 一个可提示开发及学习效率的工具。这里不过多解释,自行下载去享受你的开发过程 65 | https://psysh.org/ 66 | 67 | [1]: https://segmentfault.com/img/bVbcN63 68 | [2]: https://segmentfault.com/img/bVbcN7p -------------------------------------------------------------------------------- /PHP/PHP程序员必看书籍.md: -------------------------------------------------------------------------------- 1 | > 以下都是我看过都书籍,无论是新手或中级别的工程师,我可以保证内容质量,大佬可跳过 2 | # 概述 3 | 知识无价,还是建议各位童鞋把更多的资金投入到学习中。书名排名不分前后 4 | # 《PHP7内核剖析》 5 | ![](http://img12.360buyimg.com/n1/jfs/t12220/48/766919717/65562/3d5e3839/5a1345caN378a157e.jpg) 6 | 7 | 深入理解PHP最佳良品 8 | 9 | https://item.jd.com/20376348917.html 10 | 11 | # 《鸟哥的Linux私房菜》 12 | ![](http://img11.360buyimg.com/n1/jfs/t1426/247/655452016/50976/82aa3e3b/55a37630N0987163e.jpg) 13 | 14 | PHP码农最爱的LINUX入门书籍 15 | https://item.jd.com/1551951406.html 16 | 17 | # 《Modern PHP (中文版)》 18 | ![](http://img10.360buyimg.com/n1/jfs/t2857/50/3653206891/53172/976128ed/57967f83N4093a2e9.jpg) 19 | 20 | 学习(新)PHP的好书 21 | https://item.jd.com/10006210180.html 22 | # 《Learning PHP设计模式》 23 | ![](http://img11.360buyimg.com/n1/jfs/t2311/144/2874325550/385815/ca5a3576/56f1f95aN0891de67.jpg) 24 | 25 | 不会设计模式,你跟我说你是程序员? 26 | https://item.jd.com/11421261.html 27 | # 《细说PHP》 28 | ![](http://img10.360buyimg.com/n1/g16/M00/05/1E/rBEbRVN0NJkIAAAAAACLgJl7urEAABKMgBCpGkAAIuY581.jpg) 29 | 30 | 这是我入门PHP的时候看的一本书,内容虽然有些摘抄,但知识点概括的很全 31 | https://item.jd.com/1153543995.html 32 | # 《高性能PHP 7》 33 | 34 | ![](http://img13.360buyimg.com/n1/jfs/t4240/243/111876574/210482/55eefb98/58af818bN9047cc48.jpg) 35 | 36 | PHP全球开发者大会上推荐的一本书,感觉内容很前卫、新鲜。吕毅大佬翻译的 37 | https://item.jd.com/12134218.html 38 | # 《Docker技术入门与实战》 39 | ![](http://img13.360buyimg.com/n1/jfs/t3976/60/1843619530/173929/98382b07/589987a1N224ae4fc.jpg) 40 | 41 | 你确定你不了解下Docker ? 42 | https://item.jd.com/12121728.html 43 | # 《疯狂JAVA讲义》 44 | ![](http://img12.360buyimg.com/n1/jfs/t14623/132/1628289998/267588/2d3f3c18/5a542169N44a421a4.jpg) 45 | 46 | 其实Java应该在PHP开发者的必会名单内 47 | https://item.jd.com/12261787.html 48 | # 《Redis入门指南(第2版)》 49 | ![](http://img14.360buyimg.com/n1/jfs/t1006/327/832175681/385238/4f6b866e/554ada9aN2d862d58.jpg) 50 | 51 | nosql 了解一下? 52 | https://item.jd.com/11685574.html 53 | 54 | # 《算法谜题》 55 | ![](http://img11.360buyimg.com/n1/g15/M01/0A/1C/rBEhWlMZNJYIAAAAAAHZUBcddIYAAJlZAPAgnAAAdlo461.jpg) 56 | 57 | 代码敲累了?来~换换脑子 58 | https://item.jd.com/11412901.html 59 | 60 | # 致谢 61 | 感谢各位看完本篇白话文,最近几天一直在写相当于过往经历一样的文章,并不是不去创造新的内容,而是我更希望把我所知道的,我做过的,告诉大伙,好的地方可借鉴,差的地方可预防。我也不是一个大佬。互相学习把。谢谢 -------------------------------------------------------------------------------- /Shop/电商系统设计之商品[番外篇].md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/3940286727-5b65bc3f1ec53_articlex.png) 2 | 3 | 4 | # 前言 5 | 这是电商系统设计系列在商品设计这块的最后一篇文章。以下是其他文章地址,按照逻辑顺序排列如下 6 | - 电商系统设计之用户系统 https://blog.fastrun.cn/2018/06/14/1-10/ 7 | - 电商系统设计之购物车 https://blog.fastrun.cn/2018/06/19/1-12/ 8 | - 电商系统设计之商品 (上) https://blog.fastrun.cn/2018/07/08/1-26/ 9 | - 电商系统设计之商品 (中) https://blog.fastrun.cn/2018/07/11/1-28/ 10 | - 电商系统设计之商品 (下) https://blog.fastrun.cn/2018/07/16/1-29/ 11 | - 电商系统设计之订单 https://blog.fastrun.cn/2018/07/27/1/ 12 | - 电商系统设计之商品接口 https://blog.fastrun.cn/2018/08/03/1-36/ 13 | 14 | 在以上文章中,有些地方描述的不够全面,这篇文章就当补个漏了。 15 | 16 | # 运费模版 17 | 运费模版的设计一般依照淘宝的设计来就比较完美了。 18 | 19 | | 配置 | 说明 | 20 | | -- | -- | 21 | | 模版名称 | 用于商家选择模版 | 22 | | 所在地区/详细地址 | 主要是标记货源在什么位置 | 23 | | 发货时间 | 承诺多长时间可以发货 | 24 | | 是否包邮 | 很多买家都比较关心这个| 25 | | 运费 | 如果不包邮,运费是多少| 26 | | 件数/重量/体积 | 这里与运费相关,具体可看下方配图 | 27 | | 指定条件包邮| 某些地区包邮,例如本市或者相邻的市等等 | 28 | 29 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/2154370775-5b65bd7a27f63_articlex.png) 30 | 31 | # 商家相关 32 | | 配置 | 说明 | 33 | | -- | -- | 34 | | 商家名称 | 显示在商品详情页等等 | 35 | | 商家地址 | 用于匹配用户所在地区| 36 | | 详细地址 | 用于记录 | 37 | | 所属行业 | 用于匹配 | 38 | 39 | 如果是一家创业电商公司并且是一个多商户的电商,那你至少需要收集以下商户的相关信息 40 | 41 | - 法人代表姓名 42 | - 联系方式 43 | - 代理商公司 44 | - 代理商公司地址 45 | - 代理商公司法人 46 | - 公司邮箱 47 | - 法人身份证正反面 48 | - 营业执照 49 | - 开户行相关信息 50 | - 其他资质(授权书,资格证明等等) 51 | 52 | # 商品配置 53 | | 配置 | 说明 | 54 | | -- | -- | 55 | | 收藏数 | 商品收藏数只需记录用户点击收藏即可,取消收藏收藏数其实并不需要减少 (自行理解,不能多说)| 56 | | 分享数 | 每次分享都需要记录 | 57 | | 上架设置 | 立即上架、定时上架还是不上架 | 58 | | 是否推荐 | 推荐到指定搜索关键字或者是首页类目等等| 59 | 60 | # 其他配置 61 | | 配置 | 说明 | 62 | | -- | -- | 63 | | 商品关键字(商品属性) | 手动填写,方便检索 | 64 | | 虚拟销量 | 前期总得有点假数据的嘛 | 65 | | 是否允许退换货 | 有些商品是可能无法退换货的,类似与互联网文档、源码等 | 66 | | 减库存方式(拍下减、付款减、不减) | 有些商品是无库存限制的,类似于互联网文档、源码等 | 67 | | 购买权限(单次最低购买数、一定时间段内最多购买数)| 有些商品是限购的或者某段时间只能购买指定数量 | 68 | | 是否包邮、包邮条件 | 这项并非单独的配置,一般在运费模版内设置 | 69 | | 商品付款通知 | 商品购买是否通知,如何通知,通知到哪里 | 70 | | 库存提醒 | 库存降低到指定数量提交商家补货 | 71 | | 自动下架 | 库存降低到指定数量自动下架 | 72 | 73 | 74 | # 致谢 75 | 感谢你看到这里。 76 | 77 | 电商系统商品相关的文章已经到了尾声,如果有其他商品相关的文章需要编写,可以私信联系我,毕竟我也是公司员工,写这些文章并不是我的工作,只是记录我的职业生涯。当然我也希望可以帮助到各位。例如优惠券、活动(限时优惠)等等将在其他章节为大家呈现,谢谢。 78 | 79 | **代码多变,初心不变** -------------------------------------------------------------------------------- /Nginx/NGINX日志配置总结.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/1119748353-5b5d9b8753264_articlex.png) 2 | 3 | # 前言 4 | 本来准备讲解nginx和apache的日志的,但是个人不太推荐apache(纯属个人爱好),这里就不介绍apache的日志了。 5 | 6 | 作为一名程序员,比码代码还重要那么一点点的东西就是日志的分析和查询。下面列出常见日志及设置方法。 7 | 8 | # 配置文件 9 | 10 | > nginx分access_log和error_log两种日志 11 | 12 | 设置需要在nginx.conf中,默认通过源码包编译安装nginx目录应在 13 | ``` 14 | /usr/local/nginx 15 | ``` 16 | 目录下,如果你通过yum或者其他方式安装,不清楚或不知道nginx具体安装目录,可以使用 17 | ``` 18 | find / -name nginx.conf 19 | ``` 20 | or 21 | ``` 22 | nginx -V | grep prefix 23 | ------------- 24 | nginx version: nginx/1.13.9 25 | built by gcc 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC) 26 | built with OpenSSL 1.0.2k-fips 26 Jan 2017 27 | TLS SNI support enabled 28 | configure arguments: --prefix=/usr/local/nginx --with-http_ssl_module 29 | ``` 30 | # 开启访问日志 31 | 如果是你源码包默认安装的,打开路径如下 32 | ``` 33 | vim /usr/local/nginx/nginx.conf 34 | ``` 35 | 找到如下内容 36 | ``` 37 | http { 38 | include mime.types; 39 | default_type application/octet-stream; 40 | 41 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 42 | '$status $body_bytes_sent "$http_referer" ' 43 | '"$http_user_agent" "$http_x_forwarded_for"'; 44 | 45 | access_log logs/access.log main; 46 | 47 | ... 48 | } 49 | ``` 50 | 将log_format到access_log的注释打开即可,log_format可定义nginx的日志规格。 51 | 52 | ## log_format默认规格参数表 53 | | 名称 | 注解 | 54 | | -- | -- | 55 | |$remote_addr | 客户端/用户的IP地址| 56 | | $time_local | 访问时间 | 57 | | $request|请求方式 + 请求地址 | 58 | | $status | 请求状态码 与HTTP状态码一致 | 59 | | $body_bytes_sent | 请求的地址大小 以bytes格式计算| 60 | | $http_referer | 请求来源,从什么地方访问的 | 61 | | $http_user_agent | 用户信息(浏览器信息) | 62 | | $http_x_forwarded_for |转发IP地址 | 63 | 64 | # 开启错误日志 65 | 如果是你源码包默认安装的,打开路径如下 66 | ``` 67 | vim /usr/local/nginx/nginx.conf 68 | ``` 69 | 找到如下内容 70 | ``` 71 | error_log logs/error.log; 72 | #error_log logs/error.log notice; 73 | #error_log logs/error.log info; 74 | ``` 75 | 将注解删除即可,你可以将不同的错误类型分开存储如 76 | ``` 77 | error_log logs/error.log notice; 78 | ``` 79 | notice既为错误类型,不写则是全部。 80 | 81 | # 致谢 82 | 感谢你看到这里,日志操作与分析的相关文章后面我还会写一些,希望可以帮助到你。谢谢 83 | 84 | ** 代码多变,初心不变 ** 85 | -------------------------------------------------------------------------------- /Shop/[还魂篇] 初来乍到如何致人于死地.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3812961837-5b23317380e92_articlex.jpeg) 2 | 3 | > 逗乐篇,本章的代码无论大牛还是菜🐔都写过 4 | > 统计一些狗血的代码 5 | 6 | # 前言 7 | 初来乍到如何致人于死地,这个标题起的有点血腥恐怖了,代码写不好,吃饭吃不好。本章我们一起来回忆下那些年的“烂代码”,整理不全请见谅,以下都是我写过的😄 8 | 9 | # 箭头 10 | 望京soho,前方左拐、右拐然后右拐 11 | ``` 12 | if(){ 13 | if(){ 14 | if(){ 15 | if(){ 16 | if(){ 17 | 18 | } 19 | } 20 | } 21 | } 22 | } 23 | ``` 24 | # 面条 25 | 来碗拉面 26 | ``` 27 | if(){ 28 | $data = []; 29 | if(){ 30 | foreach ($variable as $key => $value) { 31 | # code... 32 | } 33 | if(){ 34 | if(){ 35 | for ($i=0; $i < ; $i++) { 36 | # code... 37 | } 38 | if(){ 39 | foreach ($variable as $key => $value) { 40 | # code... 41 | } 42 | } 43 | } 44 | } 45 | foreach ($variable as $key => $value) { 46 | # code... 47 | } 48 | } 49 | } 50 | ``` 51 | # 乱炖 52 | 精通各种写法的你,请低调 53 | ``` 54 | $UserName = $_POST['user_name']; 55 | $passWord = $_POST['Password']; 56 | 57 | if($passWord == DB->pass_word){ 58 | echo '...' 59 | } 60 | ``` 61 | # 嵌套 62 | 少用一个是一个的 63 | ``` 64 | foreach ($variable as $key => $value) { 65 | # code... 66 | foreach ($variable as $key => $value) { 67 | # code... 68 | } 69 | } 70 | ``` 71 | 72 | # 提前 73 | 事要提前做,能用sql用sql 74 | ``` 75 | $result = $this->db->get(self::$newModel)->row_array(); 76 | if ($result) { 77 | $result['img'] = $this->getImg($result['NewsMatter']); 78 | 79 | $result['state'] = 3; 80 | 81 | $result['type'] = 0; 82 | 83 | $result['click'] = strlen($result['click']); 84 | 85 | unset($result['NewsMatter']); 86 | } 87 | ``` 88 | 89 | # 同姓 90 | 切勿改名换姓 91 | 92 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3943279781-5b232e62c2870_articlex.png) 93 | 94 | # 开发规范 95 | 我在开发中的命名规范如下 96 | - 模型 Member[Model] 97 | - 控制器 MemberController 98 | - 公共文件 Common 99 | - 模版(html) member 100 | - JS及其他资源文件 member.js logo.png 101 | - 数据库表名 member member_data member_address 102 | - 字段名 tel sex city_name 103 | 104 | # 致谢 105 | 感谢你看到这里,希望这篇文章能帮助到你和你身边的程序员。有什么问题可在评论区讨论。谢谢 106 | 107 | 108 | [1]: /img/bVbcl2A -------------------------------------------------------------------------------- /MySQL/MySQL SQL模式特点汇总.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/2932010282-5b978902533da_articlex.png) 2 | 3 | # 前言 4 | MySQL服务器可以在不同的SQL模式下运行,并且可以针对不同的客户端以不同的方式应用这些模式,具体取决于sql_mode系统变量的值。DBA可以设置全局SQL模式以匹配站点服务器操作要求,并且每个应用程序可以将其会话SQL模式设置为其自己的要求。 5 | 6 | 模式会影响MySQL支持的SQL语法以及它执行的数据验证检查。这使得在不同环境中使用MySQL以及将MySQL与其他数据库服务器一起使用变得更加容易。 7 | 8 | # 设置SQL模式 9 | 要在运行时更改SQL模式,请sql_mode使用以下SET 语句设置全局或会话 系统变量 10 | ``` 11 | SET GLOBAL sql_mode = 'modes'; 12 | SET SESSION sql_mode = 'modes'; 13 | ``` 14 | 15 | # 模式列表 16 | | 模式 | 注释 | 17 | | -- | -- | 18 | | ALLOW_INVALID_DATES | 无效日期会生成错误 | 19 | | ERROR_FOR_DIVISION_BY_ZERO | 除0错误 | 20 | | NO_BACKSLASH_ESCAPES | 禁止使用反斜杠字符(\\)作为字符串中的转义字符。启用此模式后,反斜杠就像其他任何一个普通字符一样。| 21 | | NO_UNSIGNED_SUBTRACTION | 在整数值之间减去(其中一个是类型) UNSIGNED,默认情况下会产生无符号结果。如果结果否则为负,则会导致错误 | 22 | | NO_ZERO_IN_DATE | '0000-00-00' 则允许并且插入产生警告| 23 | | ONLY_FULL_GROUP_BY | select 内指定字段必须出现在 groupby 中,否则错误 | 24 | | STRICT_TRANS_TABLES | 为事务存储引擎启用严格的SQL模式,并在可能的情况下为非事务性存储引擎启用。 | 25 | | STRICT_ALL_TABLES | 为所有存储引擎启用严格SQL模式。无效的数据值被拒绝。 | 26 | 27 | 详情请参考 https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sql-mode-important 28 | 29 | 30 | # 严格SQL模式 31 | 32 | > MySQL服务器可以在不同的SQL模式下运行,并且可以针对不同的客户端以不同的方式应用这些模式,具体取决于sql_mode系统变量的值。在严格SQL模式下,服务器会将某些警告升级为错误。 33 | 34 | 严格SQL模式适用于以下语句 35 | - ALTER TABLE 36 | - CREATE TABLE 37 | - CREATE TABLE ... SELECT 38 | - DELETE 39 | - INSERT 40 | - LOAD DATA 41 | - LOAD XML 42 | - SELECT SLEEP() 43 | - UPDATE 44 | 45 | > 在存储的程序中,如果在严格模式生效时定义了程序,则列出的类型的单个语句将以严格的SQL模式执行。 46 | 47 | 48 | > 严格的SQL模式适用于以下错误,表示输入值无效或缺失的一类错误。如果值具有错误的列数据类型或可能超出范围,则该值无效。如果要插入的新行不包含其定义中NOT NULL没有显式DEFAULT子句的列的值,则缺少值。 49 | 50 | 51 | - ER_BAD_NULL_ERROR 52 | - ER_CUT_VALUE_GROUP_CONCAT 53 | - ER_DATA_TOO_LONG 54 | - ER_DATETIME_FUNCTION_OVERFLOW 55 | - ER_DIVISION_BY_ZERO 56 | - ER_INVALID_ARGUMENT_FOR_LOGARITHM 57 | - ER_NO_DEFAULT_FOR_FIELD 58 | - ER_NO_DEFAULT_FOR_VIEW_FIELD 59 | - ER_TOO_LONG_KEY 60 | - ER_TRUNCATED_WRONG_VALUE 61 | - ER_TRUNCATED_WRONG_VALUE_FOR_FIELD 62 | - ER_WARN_DATA_OUT_OF_RANGE 63 | - ER_WARN_NULL_TO_NOTNULL 64 | - ER_WARN_TOO_FEW_RECORDS 65 | - ER_WRONG_ARGUMENTS 66 | - ER_WRONG_VALUE_FOR_TYPE 67 | - WARN_DATA_TRUNCATED 68 | 69 | 70 | 71 | 72 | # 致谢 73 | 感谢你看到这里,希望本篇文章可以帮到你,谢谢。🙏 -------------------------------------------------------------------------------- /浅谈重构造成的灾难性毁灭.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/2252913064-5bee32205b05e_articlex.png) 2 | 3 | # 前言 4 | 这章我在7月20号的时候就准备好了标题,在那之前有写过一篇重构的文章,这段时间一直在等重构造成的弊端。 5 | 6 | 庆幸的是至今也没挂掉。本章我们来聊聊重构造成的灾难性毁灭。 7 | 8 | # 青铜 9 | 只要你确定你是一个真正的程序员,那当你接手一个新项目时,因为每个人的编码规范与风格不同,或者某块代码出现了问题,作为一名向上的程序员,总会想去重构这个项目更严重的都想重写一遍。例如下面的这类代码 10 | ``` 11 | $status = $_POST["status"] 12 | 13 | switch status { 14 | case 15 | ... 16 | break; 17 | case 18 | ... 19 | break; 20 | default: 21 | if(){ 22 | ... 23 | }else if(){ 24 | ... 25 | }else(){ 26 | ... 27 | } 28 | break; 29 | } 30 | ``` 31 | 我知道当你看到这段代码内心是崩溃的,如果是名新人,在没有完全理解其结构作用的情况下,绝对不敢擅自动原有的代码,除非他想加班,那怎么办呢?只好在原有的代码上继续增加代码了。 32 | ``` 33 | $status = $_POST["status"] 34 | 35 | switch status { 36 | case 37 | ... 38 | break; 39 | case 40 | ... 41 | break; 42 | default: 43 | if(){ 44 | ... 45 | }else if(){ 46 | ... 47 | }else if(){ 48 | ... 49 | }else (){ 50 | // 新人写的 51 | } 52 | break; 53 | } 54 | ``` 55 | 这块聊的新人入职,一般是不敢动原有代码的。当然这不排除有胆量并且重构的也不错的新人。 56 | 57 | # 白银 58 | 上面聊的与重构并无太大关系,但是必须存在的一段,用于表现程序员重构道路上的勇气。当你到白银差不多需要1-2年的时间,具体时间要个人处在的环境和自学能力。新人不敢动是因为他对语言的基础用法,类库,设计模式都没有特别了解,不敢擅自做动作也是比较聪明、保守的做法。 59 | 60 | 但到了白银就不一样了,我总结了程序员从入门到中级的心理变化,为什么只总结到中级?(PS:作者我自己都没有到高级)这是一个从谦虚到高傲在到什么都不会的过程。 61 | 62 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/2689045830-5bee2f40da6fc_articlex.png) 63 | 64 | 65 | 看到这里即可明白重构造成的灾难性毁灭是在2-4年的时期发生的,那个阶段在技术不够扎实但还有一股子改变世界的劲头发生的问题。 66 | 67 | 例如积分系统(这里指新接手的项目),领导让你修改签到获得积分,如果在未全面了解代码结构与功能分布时,擅自修改那必然出现一场不可避免的灾难。一个简单的签到功能的模块复杂度不亚于一个普普通通的企业站。大致有如下模块组成 68 | ``` 69 | 用户模块 -> 积分模块 -> 交易模块 -> 明细模块 70 | ``` 71 | 72 | 为什么说是必然?除非你在原有基础上改,那这样你又变回了青铜并且也不想那么做。在基础上重构可能当时不会出现问题。不断的有其他接手早晚回出问题。重构的灾难并非指的是一个人或某个人造成的,就如一个水杯,每个人看到都倒入一滴水,当溢出来时就发生了所谓的“灾难” 73 | 74 | # 黄金 75 | 到这个段位后,很多人都变得聪明了,不在基础代码上修改,更不去所谓的重写。单独拿出一个文件写功能不就好了。事情还远没有那么简单。 76 | 77 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/2591404418-5bee3257e1487_articlex.png) 78 | 79 | 就如上图那二货,认为自己可以,但最终Over。在项目开发上我们见过很多类型的情况。重构的方式方法有很多种,因人因物(项目)而异,对项目作出合理的分析后再对其作出一部分细节的重构,日月累计最终成形。 80 | 81 | # 总结 82 | 如果你正在做重构应考虑以下几点 83 | - 成本 84 | - 工期 85 | - 代码的优雅与简洁 86 | - 可扩展性等等 87 | 为什么要重构?原有代码无法更好的扩展,代码可读性差无法在其基础上修改的等等原因。希望会对你提供重构质量有一定帮助。 88 | 89 | # 致谢 90 | 感谢你看到这篇文章,希望可以帮到你,谢谢。 -------------------------------------------------------------------------------- /MySQL/MySQL常用函数汇总.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/timg-1.jpeg) 2 | 3 | # 概述 4 | MySQL常用函数汇总,希望可以帮到你,没事看看当复习 5 | # 字符串函数 6 | | 函数 | 功能 | 7 | | -- | -- | 8 | | CONCAT(s1,s2,....) | 字符串连接 | 9 | | INSERT(str,x,y,instr) | 将指定开始标记到结束的字符串替换为指定字符串 | 10 | | LOWER(str) | 将字符串所有字符转为小写| 11 | | UPPER(str) | 将字符串所有字符串转为大写| 12 | | LEFT(str,x) | 返回字符串str最左边的x个字符| 13 | | RIGHT(str,x) | 返回字符串str最右边的x个字符 | 14 | | LPAD(str,n,pad)| 在str最左边填充n个pad| 15 | | RPAD(str,n,pad)| 在str最右边填充n个pad| 16 | | LTRIM(str) | 去掉字符串str左侧的空格| 17 | | RTRIM(str) | 去掉字符串str右侧的空格| 18 | | REPEAT(str,x)| 返回str重复x次的结果| 19 | | STRCMP(s1,s2)| 比较字符串s1和s2| 20 | | REPLACE(str,a,b)|用字符串b替换字符串str中所有出现的字符串a| 21 | | TRIM(str) |去掉字符串行尾和行头的空格 | 22 | | SUBSTRING(str,x,y) | 返回从字符串str x位置起y个字符长度的字串| 23 | # 数学函数 24 | | 函数 | 功能 | 25 | | -- | -- | 26 | | ABS(x) | 返回x的绝对值| 27 | | CEIL(x) | 返回大于x的最小整数值 | 28 | | FLOOR(x) | 返回小于x的最大整数值| 29 | | MOD(x,y) | 返回x/y的模| 30 | | RAND()| 返回 0~1内的随机值| 31 | | ROUND(x,y) | 返回参数x的四舍五入的有y位小数的值| 32 | | TRUNCATE(x,y)| 返回数字x截断位y位小数的结果| 33 | # 日期和时间函数 34 | | 函数|功能| 35 | | -- | -- | 36 | | CURDATE() | 返回当前日期| 37 | | CURTIME() | 返回当前时间| 38 | | NOW() | 返回当前的日期和时间| 39 | | UNIX_TIMESTAMP(date) | 返回日期date的UNIX时间戳| 40 | | FROM_UNIXTIME| 返回UNIX时间戳的日期值| 41 | | WEEK(date) | 返回日期date为一年中的第几周 | 42 | | YEAR(date) | 返回日期date的年份| 43 | | HOUR(time) | 返回time的小时值| 44 | | MINUTE(time) | 返回time的分钟值| 45 | | MONTHNAME(date) | 返回date的月份名| 46 | | DATE_FORMAT(date,fmt) | 返回按字符串fmt格式日期date值| 47 | | DATE_ADD(date,interval expr type)| 返回一个日期或时间值加上一个时间间隔的时间值| 48 | | DATEDIFF(expr,expr2) | 返回起始时间expr和结束时间expr2之间的天数| 49 | # 流程函数 50 | | 函数 | 功能| 51 | | -- | -- | 52 | | IF(value,t f) | 如果value是真,返回t;否则返回f| 53 | | IFNULL(value1,value2)| 如果value1不为空,返回value1,否则返回value2| 54 | | CASE WHEN [value1] THEN[result1]...ELSE[default]END|如果value1是真,返回result1,否则返回result| 55 | | CASE[expr] WHEN [value1]THEN[result1]...ELSE[default]END|如果expr等于value1,返回result1,否则返回default| 56 | # 其他常用函数 57 | | 函数| 功能| 58 | | -- | --| 59 | | DATEBASE() | 返回当前数据库名| 60 | | VERSION()| 返回当前数据库版本| 61 | | USER() | 返回当前登录用户名| 62 | | INET_ATON(ip) | 返回ip地址的数字表示| 63 | | INET_NTOA(num) | 返回数字代表的ip地址| 64 | | PASSWORD(str) | 返回字符串str的加密版本| 65 | | MD5() | 返回字符串str的md5值| 66 | 67 | # 其他文章 68 | 我整理的另一篇文章 69 | MySQL常用系统表汇总 : https://blog.fastrun.cn/2016/07/10/1-27/ 70 | 71 | # 致谢 72 | 感谢你看完这篇文章,我相信初中级码农每天都在接触SQL,熟记mysql函数就跟熟记php函数一样重要。谢谢🙏 73 | 74 | 75 | [1]: /img/bVbdCe2 -------------------------------------------------------------------------------- /Nginx/安装Nginx要从娃娃抓起.md: -------------------------------------------------------------------------------- 1 | 本文翻译与2017年8月26日 2 | 3 | 使用configure命令配置构建。它定义了系统的各个方面,包括允许使用nginx进行连接处理的方法。最后它创建一个Makefile。该configure命令支持以下参数: 4 | 5 | - **--prefix=path** 定义一个nginx存储的系统目录,这个目录将存储nginx的代码和配置文件。如果不设置将默认安装到 /usr/local/nginx 目录中。 6 | 7 | - **--sbin-path=path** 设置一个nginx可执行文件的路径。默认情况下路径为 prefix/sbin/nginx 8 | 9 | - **--conf-path=path** 设置默认nginx配置文件的所在目录,你可以通过不同的配置文件启动nginx,在启动的时候加上参数 -c fileprefix/conf/nginx.conf 即可 10 | 11 | - **--pid-path=path** 设置将存储主进程ID的nginx.pid文件的路径。默认情况下文件被安装到 prefix/logs/nginx.pid。 12 | 13 | - **--error-log-path=path** 设置错误、警告、提醒等错误信息的存放文件及目录,默认会安装到 14 | prefix/logs/error.log 内。安装后可以在nginx.conf配置文件中修改error_log选项来更改。 15 | 16 | - **--http-log-path=path** 设置服务器请求日志文件的名称。安装后可通过nginx.conf内access_log选项修改,默认命名为 prefix/logs/access.log 17 | 18 | - **--build=name** 设置一个可选的nginx构建名称。 19 | 20 | - **--user=name** 设置nginx的所属用户,正常情况下我都会建立一个nginx的用户用于指定,默认的用户是nobody。 21 | 22 | - **--group=name** 设置nginx的所属群组,正常情况下我都会建立一个nginx的群组用于指定,默认为nobody组 23 | 24 | - **--without-http_gzip_module** 不编译压缩的HTTP服务器的响应模块。编译并运行此模块需要zlib库。 25 | 26 | - **--without-http_rewrite_module** 不编译重写模块。编译并运行此模块需要PCRE库支持。 27 | 28 | - **--without-http_proxy_module** 禁用构建HTTP服务器代理模块。 29 | 30 | - **--with-http_ssl_module** 编译https模块,让服务器可以支持https协议。 31 | 32 | - **--with-pcre=path** 指定PCRE的路径 33 | 34 | - **--with-pcre-jit** 使用“即时编译”支持构建PCRE库(1.1.12, pcre_jit指令)。 35 | 36 | - **--with-zlib=path** 指定zlib的路径 37 | 38 | 39 | ---------- 40 | 下面的这几项配置我也不咋懂,无法合理的去解释,了解的朋友可以私信我 41 | 42 | - **--with-select_module** --without-select_module 启用或禁用构建允许服务器使用该select()方法的模块 。如果平台似乎不支持更合适的方法(如kqueue,epoll或/ dev / poll),则会自动构建该模块。 43 | 44 | - **--with-poll_module** --without-poll_module 启用或禁用构建允许服务器使用该poll()方法的模块 。如果平台似乎不支持更合适的方法(如kqueue,epoll或/ dev / poll),则会自动构建该模块。 45 | 46 | - **--with-cc-opt=parameters** 设置将添加到CFLAGS变量的其他参数。当在FreeBSD下使用系统PCRE库时, --with-cc-opt="-I /usr/local/include" 应该指定。如果支持的文件数量select()需要增加,也可以在这里指定,如: --with-cc-opt="-D FD_SETSIZE=2048"。 47 | 48 | - **--with-ld-opt=parameters** 设置链接期间将使用的其他参数。当在FreeBSD下使用系统PCRE库时, --with-ld-opt="-L /usr/local/lib" 应该指定。 49 | 50 | 参数使用示例(所有这些都需要输入一行): 51 | 52 | ``` 53 | ./configure 54 | --sbin-path = /usr/local/nginx/nginx 55 | --conf-path = /usr/local/nginx/nginx.conf 56 | --pid-path = /usr/local/nginx/nginx.pid 57 | --with-http_ssl_module 58 | --with-pcre = ../pcre-8.41 59 | --with-zlib = ../zlib-1.2.11 60 | ``` 61 | 62 | 配置完成后,使用make进行编译和安装。 -------------------------------------------------------------------------------- /PHP/PHP程序员必须知道的两种日志.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/74072834b3e92ffc915ddd9e2cd3a936.png) 2 | 3 | # 前言 4 | 作为一名程序员,比码代码还重要那么一点点的东西就是日志的分析和查询。下面列出常见日志及设置方法。 5 | 6 | # php-fpm 慢日志 7 | php慢日志需要在php-fpm.conf设置,如果使用源码包安装默认请执行下面命令 8 | ``` 9 | cp php-fpm.conf.default php-fpm.conf 10 | ``` 11 | 默认通过源码包编译安装php目录应在 12 | ``` 13 | /usr/local/php 14 | ``` 15 | 目录下,如果你通过yum或者其他方式安装,不清楚或不知道php具体安装目录,可以使用 16 | ``` 17 | find / -name php-fpm.conf 18 | ``` 19 | or 20 | ``` 21 | php -i | grep Path 22 | ------------------------------------------ 23 | [root@xxxx etc]# php -i | grep Path 24 | Configuration File (php.ini) Path => /usr/local/php/etc 25 | XPath Support => enabled 26 | Path to sendmail => /usr/sbin/sendmail -t -i 27 | [root@xxxx etc]# 28 | ``` 29 | ## 开启慢查询日志 30 | 旧的版本是在php-fpm.conf设置 (实际是我忘记了哪个版本),php7.x版本源码包编译后需要www.conf修改慢查询配置 31 | ``` 32 | vim /usr/local/php/etc/php-fpm.d/www.conf 33 | ``` 34 | 不过配置项都一样的,如果你在php-fpm.conf找不到,就去他的同级目录php-fpm.d下面找下吧。 35 | ``` 36 | ; The log file for slow requests 37 | ; Default Value: not set 38 | ; Note: slowlog is mandatory if request_slowlog_timeout is set 39 | ;slowlog = log/$pool.log.slow 40 | 41 | ; The timeout for serving a single request after which a PHP backtrace will be 42 | ; dumped to the 'slowlog' file. A value of '0s' means 'off'. 43 | ; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) 44 | ; Default Value: 0 45 | ;request_slowlog_timeout = 0 46 | 47 | ``` 48 | - slowlog 设置慢查询日志的生成目录 49 | - request_slowlog_timeout 设置慢查询的标准时间(打开此配置就相当于开启了慢查询日志),配置以秒为单位,一般设置3s。 50 | 51 | # php-error 错误日志 52 | 在生产环境中是不允许php报错的,就算报错也是白屏或者500,所以在生产环境中的日志收集是非常重要的。 53 | ## 开启错误日志 54 | 一般情况下,php错误日志的配置都在php.ini文件中 55 | ``` 56 | /usr/local/php/etc/php.ini 57 | --------------------------- 58 | error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT 59 | display_errors = Off 60 | log_errors = On 61 | ; Log errors to specified file. PHP's default behavior is to leave this value 62 | ; empty. 63 | ; http://php.net/error-log 64 | ; Example: 65 | ;error_log = php_errors.log 66 | ; Log errors to syslog (Event Log on Windows). 67 | ;error_log = syslog 68 | ``` 69 | - error_log 错误日志的生成目录 70 | - error_reporting 生产环境错误级别应全开 71 | - display_errors 在页面上不显示错误 72 | - log_errors 开启错误日志 73 | 74 | 最终的结果是 75 | 76 | ``` 77 | error_log = /var/log/php_error.log 78 | display_errors = Off 79 | error_reporting = E_ALL 80 | log_errors = On 81 | ``` 82 | # 致谢 83 | 感谢你看到这里,日志操作与分析的相关文章后面我还会写一些,希望可以帮助到你。谢谢 84 | 85 | **代码多变,初心不变** 86 | -------------------------------------------------------------------------------- /是时候了解下Travis CI是什么了.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/42289522-5c2d6e7147769_articlex.png) 2 | 3 | # 前言 4 | 首先祝各位朋友新年快乐,工作顺利,事业有成,永无BUG。 5 | 6 | 有些朋友一直疑惑Travis CI是个什么东西,网络上搜索后得知的答案是自动测试,自动发布。自动xx 这个名词貌似非常流行,这也是DevOps的一部分,什么?DevOps是什么?我们下一章讲讲这个。 7 | 8 | 先来解释下“自动”这个词,自动顾名思义是自动完成一些事情,上述的自动测试,并不是各位认知中的人肉测试,而是通过我们自己的规则去测试,例如跑一遍tests内的所有测试,自动发布也是通过脚本对现有项目发布到生产环境中或预发布环境中。 9 | 10 | 万事开头难,咱们只玩简单的。 11 | # 使用 12 | 想玩起来TravisCi不需要安装任何软件,它的网址是 https://travis-ci.org/ , 你可以选择通过GitHub账号登录他。 13 | 14 | 15 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/3771889775-5c2d718bc8b9c_articlex.png) 16 | 17 | 随后我们可以建立一个github库,就叫它travis_ci_test吧,测试使用就随意点好了。之后点击项目管理 https://travis-ci.org/account/repositories ,会列出你所有的GitHub库 18 | 19 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/600778679-5c2d724a087cd_articlex.png) 20 | 21 | 22 | 通过点击单选按钮将库添加到TravisCi内。添加完成后并没有完事,这时候我们就该认真看看自动测试、集成、发布的脚本怎么写了。 23 | 24 | # 配置文件 25 | TravisCi为我们准备了超棒的配置文件,你可以在配置文件内随心所欲,例如打开某个目录,执行某条命令,他与dockerfile文件或者shell脚本很类似。只不过运行的容器在travisCi上,并非你本机 26 | 27 | 开发文档:https://docs.travis-ci.com/user/tutorial/ 28 | 29 | 30 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/2274073549-5c2d7345d4dbb_articlex.png) 31 | 32 | 从简单开始,在根目录建立文件 .travis.yml , 下面是具体的配置项 33 | ``` 34 | language: php 35 | php: 36 | - 7.1 37 | before_script: 38 | - composer install 39 | ``` 40 | 没错,五行配置就足够了,之后我们回到 https://travis-ci.com/dashboard,点击 trigger a build 41 | 42 | 43 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/3109802672-5c2d755158bc1_articlex.png) 44 | 45 | 46 | 点击当前项目看看详情。 https://travis-ci.org/CrazyCodes/travis_ci_test 47 | 48 | 49 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/2226091892-5c2d75c04e8ec_articlex.png) 50 | 51 | TravisCi 做了几个简单的事情 52 | 1. 开机 53 | 2. 克隆你的GITHUB项目 54 | 3. composer install 55 | 4. phpunit 56 | 57 | 通过测试了就显示success(大绿色) 失败就error喽。下面来看看这个详情页面上都有什么? 58 | 59 | 60 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/4190928006-5c2d767e71d0a_articlex.png) 61 | 62 | 上图大概展示了以下几点 63 | - 分支名称 64 | - 提交版本 65 | - 执行时间 66 | - 提交时间 67 | - 开发语言 68 | - 作者名称 69 | - 等.... 70 | 71 | 下面则是跑的命令行了。具体的配置文件还需要自己去研究,希望我这个头开的还不错吧。 72 | 73 | # 感言 74 | 当你理解了Travis CI后会感觉这是一个多么优秀的产品,程序员要写出好代码是要求,而这些产品扩展。作为一名优秀的程序员,你其实可以这样做 75 | 76 | - 写测试用例 77 | - 实现业务 78 | - 提交分支 (TravisCi会自动检测提交并测试) 79 | - 自动部署 80 | - 结束任务 81 | 82 | 看似很复杂,不妨试试? 83 | 84 | 85 | # 致谢 86 | 感谢你看到这里,希望本文可以帮到你。谢谢 -------------------------------------------------------------------------------- /PHP/PHP程序员如何优雅的搬砖.md: -------------------------------------------------------------------------------- 1 | ![clipboard.png](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/2748557248-5c4ebc16818c1_articlex.png) 2 | 3 | 4 | > 我一生的文章都会放在这里,我的博客,我希望每一行代码,每一段文字都能帮助你。https://github.com/CrazyCodes/Blog 5 | 6 | # 前言 7 | Hello , 各位Coder ! 8 | 9 | 在这里向各位工程师提前拜年 “新年快乐” , 距离年三十已经没有几天了,可能有些朋友还坚持在一线战斗着,有些已经回到家乡陪伴家人。北京每到这个时候都似一座空城,城与城之间表现的那么凄凉。 10 | 11 | 这是年前的最后一篇文章,本章来聊一聊程序员如何优雅的搬砖 12 | 13 | 搬砖既 “为达到目的,不断重复某项工作的行为,其实与造轮子一样,不谋而合” 14 | 15 | # 基础 16 | ![clipboard.png](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/3727715730-5c4ebc544468d_articlex.png) 17 | 18 | 19 | 这里的基础并非单指其技术能力,技术底蕴,更有意体现程序员在初期不断重复的工作而获得的感想与意识。想必大家都是这么过来的,第一年时根据需求不断创新,不断磨练。所有的功能都必须自己写,用其他人的不放心。但自己写的东西经常出问题,无论是思路或者代码都不够精炼。一层一层的技术债在完工后不断的涌现出来。当时你会不会有跑路的想法? 20 | 21 | # 选择 22 | 23 | ![clipboard.png](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/1762861377-5c4ebc6becb08_articlex.png) 24 | 25 | 在不断的进步中,我们积攒了很多经验,这里指的变是开发经验,并非什么技术经验。开发经验大概意思是在看到某项需求时,可以快速的根据自己的知识与经验的储备选择其开发框架、语言、数据库及流程逻辑等。这里就是在做选择,你会对该需求给出自己的几项方案,而不是现查现写。 26 | 27 | # 开源 28 | 29 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/2243375467-5c4ebcc1a16ff_articlex.png) 30 | 31 | 开源的目的是什么?建立一个更好的技术生态圈,Coder与Coder之间互相帮助,达到更好的效果(并不是结对编程哈),现如今PHP的生态圈非常健康的运转,无论是PHP7的发布或者Composer的诞生,都为贵圈提供了更好的技术与实践的支持。我们应更好的去接触、熟练的去运用各位大神费尽心血为我们准备的全新的PHP 32 | 33 | # 本机 34 | 35 | 36 | 讲过很多初学者在本地开发时,对本地的开发环境毫无关心,随随便便拿一个集成开发工具便搭建了一整套的运行环境,对其本质毫无理解,我想大部分人都有过“全干工程师”的历程,对本地环境毫无在意的程序员,敢说在生产、测试环境中依旧无法出色的表现其技术能力。 37 | 38 | # 建议 39 | 在这里,我给出一些常见业务需求的解决方案 (并非是一些高级的东西) 40 | 41 | ## 后台 42 | 如果是从头做一个后台,然而又不想从0开始搭建后台的逻辑,在这里我强烈推荐laravel-admin,laravel虽然性能方面低于其他框架,但其作为后台的开发框架来说我认为还是第一名的。 43 | 44 | - https://laravel.com/ 45 | - https://laravel-admin.org/ 46 | - https://laravel-admin.org/docs/zh 47 | 48 | laravel-admin 安装比其他的开源程序要简单的多,这都寄托于能力极佳的composer与laravel 49 | ``` 50 | composer require encore/laravel-admin 51 | 52 | ``` 53 | 54 | ## 接口 55 | 接口开发着重性能,相应速度,如果依旧喜欢laravel,可以选择lumen 56 | 57 | - https://lumen.laravel.com/ 58 | 59 | 或者使用C编写的框架 Phalcon 60 | 61 | - https://phalconphp.com/zh/ 62 | 63 | 实在感觉这些框架太过庞大,复杂也可以选择 Slim ,他一定精简到让你飞起 64 | 65 | - http://www.slimframework.com/ 66 | 67 | 不想使用框架?但从0写还嫌麻烦,这时你可以考虑 鸟哥的Yaf 或者 韩天峰的 Swoole 68 | - https://www.swoole.co.uk/ 69 | - http://php.net/manual/en/book.yaf.php 70 | 71 | ## 其他 72 | 熟练去搜集、查找适合自己业务的包,熟练去运用其优势。让自己不需要再重复造轮子,无止尽的还技术债,这才是2019年程序员应该学习的。 73 | 74 | - https://segmentfault.com/a/1190000016004756 75 | - https://segmentfault.com/a/1190000017782596 76 | - https://packagist.org/ 77 | - https://github.com/ 78 | 79 | # 致谢 80 | 感谢你看到这里,希望本篇文章可以帮助到你,谢谢。 81 | -------------------------------------------------------------------------------- /Go/PHP To Go 转型手记 (二).md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2978289583-5bd423e85207c_articlex.png) 2 | 3 | # 前言 4 | 作为一名PHP程序员,我感到荣幸。但在时代不断的变迁中,要具备足够的知识才可生存。 5 | 6 | 那就从Go语言学起把。 7 | 8 | 希望看到本篇文章的你可以对Go有一个基本的认识。本系列文章与我自己学习Go语言的方式去描述。以PHP代码与Go代码的对比加以区分理解。 9 | # 加载 10 | PHP 11 | ``` 12 | namespace Action 13 | use Action 14 | ``` 15 | Go 16 | ``` 17 | package Action 18 | import "action" 19 | ``` 20 | # 数组 21 | PHP 22 | ``` 23 | // 初始化 24 | $arr = [] 25 | $arr = array() 26 | // 初始化赋值 27 | $arr = [1,2,3] 28 | // 多维数组 29 | $arr = [][] 30 | // 获取值 31 | echo $arr[1] 32 | // 获取数组总数 33 | echo length($arr) 34 | // 获取数组区间 35 | $a=array("red","green","blue","yellow","brown"); 36 | print_r(array_slice($a,1,2)); 37 | // 设置key=>value 38 | $arr = ["username"=>"zhangsan","age"=>13] 39 | // 删除指定下标 40 | unset($arr[0]) 41 | ``` 42 | Go 数组 & 切片 (切片是数组的一个View,就例如MySQL的视图一样) 43 | ``` 44 | // 初始化 45 | var arr [5]int 46 | // 初始化赋值 47 | arr := [5]int{1, 2, 3, 4, 5} 48 | // 无需声明数组个数 49 | arr := [...]int{1, 2, 3, 4, 5, 6, 7} 50 | // 多维数组 51 | var arr [4][5]bool 52 | // 获取值 53 | fmt.Println(arr[1]) 54 | // 获取数组总数 55 | fmt.Println(len(arr)) 56 | // 获取数组区间 显而易见,Go对数组的操作更便利直观 57 | a := [...]string{"red","green","blue","yellow","brown"} 58 | fmt.Println(a[1:2]) 59 | // 设置key=>value 这里需要使用Map 60 | m := map[string]string{ 61 | "username": "zhangsan", 62 | "age" : "13" 63 | } 64 | // 删除指定下标 Go没有删除数组下标的系统方法 65 | arr := arr[1:] 66 | // 删除中间位置的下标 可通过合并的方式去除指定下标 67 | arr := append(arr[:3],arr[4:]) 68 | ``` 69 | # 循环结构 70 | PHP 71 | ``` 72 | // 基本结构 73 | for($i=0;$i<10;$i++){ 74 | echo $i; 75 | } 76 | // 死循环 77 | for($i=0;$i<10;$i++){ 78 | echo $i; 79 | $i-- 80 | } 81 | // 获取key,value 82 | foreach($arr as $key=>$value){ 83 | echo $key,$value 84 | } 85 | ``` 86 | Go 87 | ``` 88 | // 基本结构 89 | for i := 0; i < 10; i++ { 90 | fmt.Println(i) 91 | } 92 | // 死循环 可见Go写死循环非常方便 93 | for { 94 | fmt.Println("") 95 | } 96 | // 获取key,value 97 | for k, v := range arr { 98 | fmt.Println(k, v) 99 | } 100 | ``` 101 | 102 | # 控制结构 103 | PHP 104 | ``` 105 | // if 106 | if(true){ 107 | 108 | } 109 | // switch 110 | switch(true){ 111 | case true: 112 | echo true; 113 | break; 114 | } 115 | ``` 116 | Go 117 | ``` 118 | // if 119 | if true { 120 | 121 | } 122 | // switch Go语言的Switch的Case不需要break 123 | switch true { 124 | case true: 125 | fmt.Println(true) 126 | } 127 | ``` 128 | 129 | # 类 130 | PHP 131 | ``` 132 | // 声明一个类 133 | class City{} 134 | ``` 135 | Go 136 | ``` 137 | // 声明一个结构体 这里并非混淆公众,是因为Go本身没有类的概念,只是其声明及操作方法与类概念相似 138 | type City struct{} 139 | ``` 140 | Go语言的结构体会在下一个章节来做对比 141 | # 致谢 142 | 感谢你看到这里,希望本篇文章可以帮到你。谢谢 -------------------------------------------------------------------------------- /Shop/电商系统设计之购物车.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/550662601-5b27dd6a4a280_articlex.jpeg) 2 | 3 | > 本章适合初级工程师及中级工程师细看,大佬请随意 4 | # 前言 5 | 6 | - 问 [不存价格字段不行吗?直接查询商品表获取价格] 7 | - 答 [如果价格更新,应提示用户,商品的浮动信息。可以选择直接更新购物车,或者单独建立一个表,来记录更新的价格和信息,类似京东] 8 | - 问 [联表查询可以从商品表中知道商品是否上架] 9 | - 答 [商品不存在了如何联,只会将逻辑整复杂,未来包括降价提醒,无货提醒,下架提醒,购物车该如何查询就成了一个问题] 10 |
11 | 上一篇文章在对于购物车业务及数据表设计中,有位童鞋在评论区与我讨论许久,特此独立一篇文章来详解下我的想法及我为什么这么做,以下为在业务层面、逻辑层面、未来功能的可扩展性、编码的复杂度、数据统计层面来解释下我的设计。 12 | 13 | # 业务 14 | 15 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2662689313-5b28add53d2fc_articlex.png) 16 | 17 | 业务上来看,无论是多表查还是单表存都是合理的,列出以下在购物车上的相关部分业务 18 | - 库存不足提醒 (提高付款概率) 19 | - 降价提醒 (提高付款概率) 20 | - 商品下架提醒 21 | - 有关商品的商品优惠券或其他活动 (提高付款概率) 22 | 以技术角度说明 23 | ## 降价提醒 24 | 多表的降价提醒需要第三张表支撑 <商品修改记录表> 25 | 26 | ### 多表 27 | 28 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2534558087-5b28aecc0fb63_articlex.png) 29 | 30 | 这时购物车内的商品与商品表存在关联,检测降价的系统就需要在商家修改价格时将检测结果后查询加入本商品的购物车,顺便去查询商家修改前价格,算出差价,发送到队列或者其他的手段,用户接收到降价通知,刺激消费。这时你发现,这貌似没有什么地方有问题,如果这时候需要增加一个业务,按照用户加入购物车的时间,提示他在加入购物车后这段时间降价多少?这时是否需要在来个加入购物车的记录表,这样不断的多级关联,看似没有问题,实际将业务耦合,一次sql要关联N个表,如果这时增加sku和spu那就更不用说了。在未来量级上升后是支撑不住的,并且也不方便扩展。 31 | 32 | ### 单表 33 | 34 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3769763978-5b28af38ad752_articlex.png) 35 | 36 | [我的设计并不是最好的,仅此参考] , 在考虑到未来业务不断增加的问题,我是将价格与标题和商品的SKU加入到购物车表内,在商户修改时无需关心其他表,直接检索与修改商品相关的购物车,拿出价格,计算差价,提示用户。如果计算加入购物车这段实际降价多少,这其实与上述操作一样,对于单表的设计上,这2种需求实为一种解决方案。在查询上也是一条sql语句的实现。 37 | 38 | > 当然,我们还是需要关联上,不知道未来的某一天就用的上了呢? 39 | > 有很多场景,都要将标题呀,内容呀直接存储,类似与收藏的店铺和商品,无论卖家怎么做,用户购物车,订单不能动,这是基准。 40 | 41 | ## 商品下架 42 | 商品下架,用户的购物车实际是不能动的,某猫的做法是使其变灰,让用户自行删除。 43 | 商家分很多种,商品的标题,图片或者分类修改了,都属于下架,这时的多表关联查询就彻彻底底的失效了。 44 | 其实商品的下架应该直接通知购物车下架 (变灰),并非关联查询是否下架。如果你非要这样做,那你依旧需要做一些表去记录。 45 | 46 | 我并不是说不需要做记录。而是记录的表实际是不参与业务查询的。 47 | 48 | 49 | # 逻辑 50 | 逻辑这里特指代码的架构编写。以php为例,可以参考我之前的文章 http://blog.fastrun.cn/2018/06/13/1-9/ 51 | 在逻辑方面,要考虑方面比较多,类似sql的性能,代码的性能,服务器的性能等。尽量避免多表查询吧。 52 | 53 | ## 可复用性 54 | 百度百科的定义是 55 | > 可复用性(Reuseability)复用又叫重用,是重复使用的意思。目前,一般软件的复用率并不高,尤其在国内。复用的好处可以得到 较高的生产效率以及随之而来的成本降低、较高的软件质量(错误可以更快的被纠正)以及 恰当的使用复用可以改善系统的可维护性。 56 | 57 | 在购物车的设计上,重用主要提现在商品信息的存储方式上,避免多次去联表查询,在业务量大后的份表分库提现会更明显。 58 | ## 可扩展性 59 | 百度百科的定义是: 60 | > 设计良好的代码允许更多的功能在必要时可以被插入到适当的位置中。这样做的目的的是为了应对未来可能需要进行的修改,而造成代码被过度工程化地开发。 61 | 62 | 正常购物车、商品、优惠券都是独立的系统及功能,不要看做商品在购物车内。现实和逻辑并非是一脉相承的。就假设在实际生活中,物品仅仅是放在购物车中,如果不结账,依旧不属于自己。为了方便扩展更多业务,尽量在设计之初,功能与功能之间不要“粘”在一起。 63 | ## 可维护性 64 | 百度百科的定义是: 65 | > 系统的可维护性是衡量一个系统的可修复(恢复)性和可改进性的难易程度。所谓可修复性是指在系统发生故障后能够排除(或抑制)故障予以修复,并返回到原来正常运行状态的可能性。而可改进性则是系统具有接受对现有功能的改进,增加新功能的可能性。 66 | 67 | 购物车的设计之初也是考虑未来商品的业务功能各种变更。不如简单点,直接将其属性存到购物车。 68 | 69 | # 复杂度 70 | 初期的设计,决定未来开发及重构的复杂度。功能与功能,系统与系统之间尽量避免直接关联。 71 | # 统计 72 | 后期的数据统计、计算也会受到前期设计的影响。 73 | # 致谢 74 | 感谢你们看到这里,下一篇我会讲一下关于电商系统的商品设计的部分。有什么问题可以评论区提问。谢谢 -------------------------------------------------------------------------------- /PHP/PHP程序员如何简单的开展服务治理架构(二).md: -------------------------------------------------------------------------------- 1 | 服务治理 治理的绝笔是服务,在一家公司有玩各种语言的程序员,如何去统一管理他们开发的服务,这是一个问题。 2 | 3 | 上一章主要讲了下服务治理需要什么,如何实现,这章我们详细的“肢解”一下服务治理的一个非常重要的组员 Thrift 4 | 5 | 上一章说明他的时候是这样写的 6 | 7 | > 暂时大可理解为可以通过它去调用其他开发语言的方法 8 | 9 | 10 | > 本猿人已经写好的服务治理 https://github.com/CrazyCodes/Service-Govern 11 | 12 | 13 | # 名词解释 14 | thrift其实是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。 15 | 16 | 这个时候你就疑惑了?,如何跨语言调用 17 | 18 | # 基本概念 19 | 如何调用这就需要讲一下我们强大的通信协议了。 20 | 21 | ## http (tcp) 22 | 超文本传输协议,正常访问浏览器啥看新闻、购物的时候必定使用,需要客户端和服务端握手?成功才可以正常显示,这中间握手的流出很复杂,执行各种各样的解码编码(为了方便理解,暂时这么想吧) 23 | 24 | ## rpc 25 | > 远程过程调用协议,RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供者就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。 26 | 27 | rpc的优势很多,现在你大可理解为rpc通信要比http通信快很多就是了。 28 | 29 | 这个时候facebook和apache就厉害了,它们基于rpc通信协议开发出了一套[thrift](http://thrift.apache.org/) 30 | 31 | # 实现方法 32 | * 上面假设你都没看懂,这里我们实战下。 33 | * 首先rpc我们通过使用swoole来实现,其他的手码。 34 | * 分为客户端和服务端做下演示 35 | 36 | ## 客户端 37 | 首先我们new一个client类,去调用服务端的UserSerivce这个类,并且调用UserService类中的getUserInfo方法。 38 | ``` 39 | $client = new Client('UserSerivce'); 40 | $userInfo = $client->getUserInfo(1); 41 | 42 | var_dump($userInfo); 43 | ``` 44 | Client中我们只需要干这样的一件事,使用php魔术方法__call去调用一个不存在的方法 45 | ``` 46 | class Client{ 47 | protected $serviceName; 48 | 49 | public function __construct($serviceName){ 50 | $this->serviceName = $serviceName; 51 | } 52 | public function __call($name, $arguments){ 53 | $rpcClient = new \swoole_client(SWOOLE_SOCK_TCP); 54 | $rpcClient->connect('127.0.0.1',9503,0.5); 55 | // 我们将要发送的数据是事先约定好的,跟写对外开放的Api一样 56 | $rpcClient->send(json_encode([ 57 | 'service'=>$this->serviceName, 58 | 'action'=>$name, 59 | 'params'=>$arguments[0] 60 | ])); 61 | $rpcClient->close(); 62 | } 63 | } 64 | ``` 65 | 这个时候数据就通过rpc协议以json格式发送到了服务端 66 | 67 | ## 服务端 68 | ``` 69 | $server = new swoole_server("127.0.0.1", 9503); 70 | $server->on('connect', function ($server, $fd){ 71 | echo "connection open: {$fd}\n"; 72 | }); 73 | $server->on('receive', function ($server, $fd, $reactor_id, $data) { 74 | // $data 则就是客户端发送过来的数据,我们可以这样做来做到去调用类,当然你必须遵守PSR-4 Autoloader 75 | $request = json_decode ($data, true); 76 | $className = $request['service']; 77 | $app = new $className; 78 | $response = $app->{$request['action']}($request['params']); 79 | 80 | $server->send($fd, "Swoole: {$data}"); 81 | $server->close($fd); 82 | }); 83 | $server->on('close', function ($server, $fd) { 84 | echo "connection close: {$fd}\n"; 85 | }); 86 | $server->start(); 87 | ``` 88 | 89 | # 往期文章 90 | * [PHP程序员如何简单的开展服务治理架构(一)](http://blog.fastrun.cn/2018/06/26/1/) 91 | 92 | # 鸣谢 93 | 周梦康 https://mengkang.net/ -------------------------------------------------------------------------------- /用MAC还安装集成环境可就OUT喽.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/2663373599-5e6c5b38ccd6e_articlex.png) 2 | #前言 3 | 大家好,我是CrazyCodes,今天聊聊为什么在MAC上,我不安装类似XAMPP或者Laravel的Homestead的集成开发环境 4 | 5 | #对比 6 | 我也是卸载了这些环境不到一年的时间,为何要去卸载他呢,原因其实很简单,扩展性差,这里这个扩展性指的不是集成环境这个软件本身的扩展性,当初安装的目的其实不就是**省事**嘛~,也不会过多的查看文档,而是在需要扩展的时候,学习成本会逐渐提高,这里的扩展性=学习成本。 7 | 8 | #开始 9 | MAC自带了Apache和PHP的版本,这时候直接安装一个MySQL其实就可以进入开发了。可以选择使用brew安装 10 | ``` 11 | brew install mysql 12 | ``` 13 | 当然如果希望使用其他版本的PHP或者是将Apache替换为Nginx也是很简单的时候,只要部署过单机服务器,其实在Mac上的步骤是差不多的。 14 | 15 | #php 16 | 首先还是使用brew安装一个你心仪的PHP版本,可以通过 17 | ``` 18 | brew search php7 19 | ``` 20 | 来搜索下现有的php7+都有哪些版本,这里在php@7.3上打了✔️,意思是我已经安装了这个版本,现在使用7.4版本做一些讲解。 21 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/466241470-5e6c555346980_articlex.png) 22 | ``` 23 | brew install php@7.4 24 | ``` 25 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/2672533940-5e6c55a27aaaf_articlex.png) 26 | 经过一顿猛如虎的操作后,正常状况下会看到下面这样 27 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/545534636-5e6c56cb4b177_articlex.png) 28 | 多么人性化的提示,还告诉了咱们如何配置。当然这不是主要的,看要最下面这段 29 | ``` 30 | The php.ini and php-fpm.ini file can be found in: 31 | /usr/local/etc/php/7.4/ 32 | 33 | To have launchd start php now and restart at login: 34 | brew services start php 35 | Or, if you don't want/need a background service you can just run: 36 | php-fpm 37 | ``` 38 | 安装完成后,他会说明php安装到什么位置了,可以使用命令```brew services start php```或者```php-fpm```去启动和重启PHP,这个时候就可以在指定目录下看到熟知的php相关文件 39 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/2171069275-5e6c579c8338c_articlex.png) 40 | 41 | #nginx 42 | 依旧是使用brew安装一个你心仪的nginx版本 43 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/3431562296-5e6c57fdd1c38.png) 44 | 这里我已经安装过nginx了。就不截图演示了。具体操作如下 45 | ``` 46 | brew install nginx 47 | ``` 48 | 一顿操作猛如虎后,与PHP安装完成后的提示一样,会告诉我们安装到哪个目录下了,一般默认为 49 | ``` 50 | /usr/local/etc/nginx 51 | ``` 52 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/2415719393-5e6c5856a46e0_articlex.png) 53 | 依旧也是我们熟知的NGINX相关的目录以及配置文件,servers目录是我新建的,用于存放server配置 54 | 55 | #mysql 56 | 依旧依旧是使用brew安装一个你心仪的mysql版本,通过使用命令 57 | ``` 58 | brew search mysql 59 | ``` 60 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/642420240-5e6c58d047385_articlex.png) 61 | 这时要看清楚,有些并不是mysql本体,可能会是一些链接库。要脑子清楚的选择安装,mysql8我已经安装,以5.6为例 62 | ``` 63 | brew install mysql@5.6 64 | ``` 65 | 一顿操作猛如虎后,mysql也如期安装完成,正常情况下不会报错的 66 | 67 | #补充 68 | 全部完成后,按照正常步骤 69 | 70 | 1.启动Nginx 71 | 2.启动PHP 72 | 3.启动MySQL 73 | 74 | 访问链接 http://localhost 75 | 会看到nginx友爱的欢迎界面。 76 | 77 | nginx server配置与单机配置是一致的。如果感觉配置host每次都很麻烦,可以使用一个ihost去统一管理 78 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/2072146744-5e6c5a82cea9f_articlex.png) 79 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2020/10/780687878-5e6c5a63f1390_articlex.png) 80 | 所有源码都与源码包安装一致,相信你一定会按需安装需要的扩展了把~ 81 | 82 | #致谢 83 | 感谢你看到这里,希望本篇文章可以帮到你。 -------------------------------------------------------------------------------- /PHP/PHP程序员如何简单的开展服务治理架构(一).md: -------------------------------------------------------------------------------- 1 | 不涉及其他的语言及工具,我们从PHP本身来谈如何实现服务治理 2 | > 本猿人已经写好的服务治理 https://github.com/CrazyCodes/Service-Govern.git 3 | # 治理什么? 4 | 这个专业名词很容易发现治理的是服务,而服务则是我们的项目。管理这些服务方案则叫服务治理。 5 | 6 | 现在在Server上有四项服务,分别为 7 | * UserService 8 | * ShopService 9 | * GoodsService 10 | * LiveService 11 | 这些服务我们叫它服务提供者(既提供对内服务的应用) 12 | 13 | 调用服务的应用我们称它为服务消费者,例如 14 | * User-Api 15 | * Shop-Api 16 | * Goods-Api 17 | * Live-Api 18 | 19 | Service 是对内服务的而Api是对外服务的 20 | 21 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3373537274-5a98b8a1ccbb7_articlex.png) 22 | 23 | 图片来源:https://blog.csdn.net/suifeng3051/article/details/53992560 24 | 25 | 服务治理考虑的问题就是如何管理这四项服务,让它们如何对外服务,如何监控服务进程 26 | 27 | # 依托实现 28 | 在实现服务治理之前,需要了解以下几块知识点 29 | * thrift 30 | * rpc 31 | * swoole 32 | 33 | ### thrift 34 | 暂时大可理解为可以通过它去调用其他开发语言的方法 35 | ### rpc 36 | > RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 37 | RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。 38 | 39 | 服务与服务之间通信可以通过RPC通信,当然也可以选择UDP等 40 | 41 | ### swoole 42 | PHP圈内跨世纪的产物,使用他的原因是因为Swoole本身支持RPC通信,所以我们本章通过Swoole Rpc的方法去实现消费者与服务提供者之间的通信 43 | 44 | # 实现RPC通信 45 | 服务治理非常重要的一个环节,要在无感知的情况让消费者A调用服务提供者A,B,C,当然实际情况下,这是永远不可能的,根本不在一个内存空间中,我们需要自己模拟出来这种使用方式 46 | ``` 47 | $userSerivce = $client->client ('UserService'); 48 | $result = $userSerivce->getUserInfo (['user_id' => 100]); 49 | var_dump($result); 50 | ``` 51 | 在消费者内没有UserService,更没有getUserInfo 方法,这些都在服务提供者的应用中,如何去调用它们? 52 | 53 | 首先通过php的__call 方法去截取一个不存在的方法 54 | ``` 55 | public function __call($name, $arguments) 56 | { 57 | $client = new ClientRpc($this->serviceName); 58 | 59 | $response = $client->send ($this->serviceName, $name, $arguments); 60 | 61 | return (json_decode ($response, true)); 62 | 63 | } 64 | ``` 65 | 获取后调用自己写的send 方法,swoole出场 66 | ``` 67 | class ClientRpc 68 | { 69 | protected $client; 70 | 71 | public function __construct($service_name, $centerConfig) 72 | { 73 | $this->client = new \swoole_client(SWOOLE_SOCK_TCP); 74 | 75 | $center = Dispatcher::loadBalance ($service_name, $centerConfig); 76 | $this->client->connect ($center['ip'], $center['port'], 0.5); 77 | } 78 | 79 | public function send($service, $action, $arguments) 80 | { 81 | $request = new Request(); 82 | 83 | $request->setService ($service); 84 | $request->setAction ($action); 85 | $request->setParameters ($arguments[0]); 86 | // 重组参数,组合成你希望的格式,最后转成json发送到服务提供者 87 | $this->client->send (json_encode ((array)$request)); 88 | 89 | return $this->client->recv (); 90 | } 91 | 92 | public function __destruct() 93 | { 94 | $this->client->close (); 95 | unset($this->client); 96 | } 97 | } 98 | ``` 99 | 100 | # 鸣谢 101 | 周梦康 [https://mengkang.net/] 102 | -------------------------------------------------------------------------------- /PHP/Swoole难上手?从EasySwoole开始.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/03/1167427476-5c80ebf78f3b0_articlex.png) 2 | 3 | # 前言 4 | 5 | 大家好,我是CrazyCodes,我没有消失,最近在准备考试,所以文章出的比较慢,请见谅 6 | 7 | 有些童鞋感觉对Swoole不从下手,也不知在什么业务上使用它,看它这么火却学不会也是挺让人捉急的一件事情。 8 | 9 | > Swoole:面向生产环境的 PHP 异步网络通信引擎 10 | 11 | 啥是异步网络通信? 12 | 13 | # 异步通信 14 | 15 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/03/1872214882-5c80e9751f0e7_articlex.png) 16 | 17 | 简单点来说,就是一个人和一群人的关系,一个人去做十件事,需要一件一件去做,一群人去做10件事,可以分配每个人做每件事。我们用Swoole可以做什么? 18 | 19 | - 聊天室 20 | - 并发的处理 (读大文件) 21 | - 异步MySQL 22 | - 异步Redis 23 | - 等等 24 | 25 | 当然不去做,只在看,很难理解为何选择使用Swoole去做这些。从现在开始,我们暂时不关心上面的这些概念,啥话都不说,就是干 26 | 27 | # EasySwoole 28 | EasySwoole ? 名副其实,作者为了让开发者更便捷的使用Swoole 而封装的开发框架,地址在下方 29 | > EasySwoole https://www.easyswoole.com/ 30 | 31 | 使用EasySwoole你会发现有很多很难理解的概念及用法。没关系,跟着我,慢慢来~ 32 | 33 | # 安装 34 | EasySwoole的环境要求 35 | - 保证 PHP 版本大于等于 7.1 36 | - 保证 Swoole 拓展版本大于等于 4.3.0 37 | - 需要 pcntl 拓展的任意版本 38 | - 使用 Linux / FreeBSD / MacOS 这三类操作系统 39 | - 使用 Composer 作为依赖管理工具 40 | 41 | 如果你感觉以上要求太苛刻,你可以选择使用Docker快速部署一套开发环境或者使用更简单的 homestead 42 | 43 | 在使用EasySwoole之前我们要安装Swoole,Swoole是PHP扩展,我们可以通过 44 | ``` 45 | pecl install swoole 46 | ``` 47 | 快速安装,或者使用源码编译的形式安装 48 | 49 | 安装完扩展后,接下来我们就使用万能composer来安装EasySwoole 50 | ``` 51 | composer require easyswoole/easyswoole=3.x 52 | php vendor/bin/easyswoole install 53 | ``` 54 | 55 | # 服务管理 56 | EasySwoole(Swoole)与其他框架不同,他不擅长开发Web,请将目标定位在后端服务上。以下内容为引用官方文档 57 | ``` 58 | php easyswoole start 59 | ``` 60 | # Hello World 61 | > 以下为官方文档内容 https://www.easyswoole.com/Manual/3.x/Cn/_book/Introduction/install.html 62 | 63 | 在项目根目录下创建如下的目录结构,这个目录是编写业务逻辑的应用目录,编辑 Index.php 文件,添加基础控制器的代码 64 | ``` 65 | project 项目部署目录 66 | ---------------------------------- 67 | ├─App 应用目录 68 | │ └─HttpController 应用的控制器目录 69 | │ └─Index.php 默认控制器文件 70 | ---------------------------------- 71 | ``` 72 | ``` 73 | response()->write('hello world'); 86 | } 87 | } 88 | ``` 89 | 然后编辑根目录下的 composer.json 文件,注册应用的命名空间 90 | ``` 91 | { 92 | "autoload": { 93 | "psr-4": { 94 | "App\\": "App/" 95 | } 96 | }, 97 | "require": { 98 | "easyswoole/easyswoole": "3.x-dev" 99 | } 100 | } 101 | ``` 102 | 最后执行 ``` composer dumpautoload ``` 命令更新命名空间,框架已经可以自动加载 App 目录下的文件了,此时框架已经安装完毕,可以开始编写业务逻辑 103 | ``` 104 | # 更新命名空间映射 105 | composer dumpautoload 106 | # 启动框架 107 | php easyswoole start 108 | ``` 109 | 启动框架后,访问 http://localhost:9501即可看到 Hello World 。 110 | 111 | # 组件 112 | EasySwoole提供了很多实用的组件包括 113 | - 控制台组件 114 | - 定时器 115 | - 触发器 116 | - 日志处理等等... 117 | 118 | # 致谢 119 | 从下一章开始,我们逐步使用EasySwoole的各项功能并开发一个简单的并发版爬虫系统,感谢你看到这里,希望本文可以帮到你,谢谢 -------------------------------------------------------------------------------- /PHP/PHP使用Oracle数据库的准备工作.md: -------------------------------------------------------------------------------- 1 | > 系统 : Linux Centos 7.0 2 | 3 | **前言:想让PHP可以操作Oracle数据库,那绝对是需要安装关于Oracle扩展。php的Oracle扩展叫oci** 4 | 5 | **oracle扩展包下载地址:** 6 | http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html 7 | 8 | 网页上会显示如下的下载列表: 9 | 10 | Download Oracle Database 10g Instant Client for Microsoft Windows (32-bit) 11 | 12 | Download Oracle Database 10g Instant Client for Microsoft Windows 64-bit Itanium 13 | 14 | Download Oracle Database 10g Instant Client for Microsoft Windows (x64) 15 | 16 | Download Oracle Database 10g Instant Client for Linux x86 17 | 18 | Download Oracle Database 10g Instant Client for Linux x86-64 19 | 20 | Download Oracle Database 10g Instant Client for Linux Itanium ... 21 | 22 | 如系统是32位则选择第4个,64位选择第5个。 23 | 24 | 选择错误会造成make php oci8的时候报兼容失败。 25 | 26 | 下面是64位系统的演示。 27 | 28 | **第一步:安装oracle** 29 | 30 | ``` 31 | rpm -ivh oracle-instantclient12.1-basic-12.1.0.2.0-1.x86_64.rpm 32 | 33 | rpm -ivh oracle-instantclient12.1-devel-12.1.0.2.0-1.x86_64.rpm 34 | 35 | rpm -ivh oracle-instantclient12.1-sqlplus-12.1.0.2.0-1.x86_64.rpm 36 | ``` 37 | 38 | 说明:oracle包版本要与oci8包版本兼容,php官网给出的参考文字是 39 | 40 | > Use the OCI8 extension to access Oracle Database. The extension can be 41 | > linked with Oracle client libraries from Oracle Database 10.2, 11, or 42 | > 12.1. These libraries are found in the database installation, or in the free Oracle Instant Client available from Oracle. Oracle's 43 | > standard cross-version connectivity applies. For example, PHP OCI8 44 | > linked with Instant Client 11.2 can connect to Oracle Database 9.2 45 | > onward. See Oracle's note "Oracle Client / Server Interoperability 46 | > Support" (ID 207303.1) for details. PHP OCI8 2.0 can be built with PHP 47 | > 5.2 onward. Use the older PHP OCI8 1.4.10 when using PHP 4.3.9 through to PHP 5.1.x, or when only Oracle Database 9.2 client libraries are 48 | > available. 49 | 50 | 官网的意思是在安装oci8的时候要保证 oracle扩展+oci8+php版本 要达成一致,否则就会出问题。 - - 英文不好大概就这个意思吧。 51 | 52 | **第二步:下载php oci扩展** 53 | 54 | 安装oci8扩展 下载地址:http://pecl.php.net/package/oci8 55 | 56 | 我的php版本是5.5.4的依照官网的描述我选择的是 oci8-2.0.0.tgz 包. 57 | 58 | ``` 59 | tar zxvf oci8-2.0.0.tgz 60 | 61 | cd oci8-2.0.0.tgz 62 | 63 | /usr/bin/phpize 64 | 65 | ./configure --with-php-config=/usr/bin/php-config --with-oci8=shared,instantclient,/usr/lib/oracle/11.2/client/lib 66 | 67 | make 68 | 69 | make install 70 | ``` 71 | 72 | 说明: 73 | 74 | 1.oci8-2.0.0.tgz这个是单独的扩展包,也可以下载完整的php安装包,如php-5.5.28.tar.gz,解压后,cd到ext目录下的oci8目录即可。 75 | 76 | 2.phpize和php-config都不一定在上面的路径中,因为安装lamp环境的方法每个人不尽相同,可以用which命令查找,如which phpize。 77 | 78 | 3.关键点是要保证phpize,php-config,以及oracle的安装路径要正确 79 | 80 | **第三步:配置 php.ini** 81 | 82 | 其实大部分时候是不需要第三步的,系统会默认把扩展加上 83 | 84 | 可以用find命令找到这个文件,找到类似extension = ""的配置项,加一行extension = "oci8.so" 说明:经过第二步的make,makeinstall后会生成一个oci8.so文件,可以用find命令查找一下路径,extentsion="oci8.so"要结合extention_dir="/usr/lib/php/modules" 这个配置项来看,这两句的意思就是在/usr/lib/php/modules下找oci8.so扩展,换句话说就是如果你的oci8扩展不是生成在/usr/lib/php/modules目录下,那么你就要改动extention_dir以确保oci8.so的路径是正确的 85 | 86 | 上面步骤完成后,重启代理服务。PHPINFO() 查看下。是不是已经成功了?! 87 | -------------------------------------------------------------------------------- /Laravel/Laravel5.4队列简单配置与使用.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | ## 什么是队列? 4 | 5 | 6 | 百度百科是这样说的 7 | 8 | > “队列”是在传输过程中保存数据的容器。 9 | 10 | 举几个生活中例子: 11 | * iphone手机新款发布,三里屯iphone进的新货。大家要排队买,不能说一大堆人一起冲进去,那么这店就完了。 12 | * 你有一大堆女朋友。你不得一个一个来,一起来你也受不了 ? 13 | * C语言中堆的概念也是这样,先进先出,不能起个大早赶个晚集。 14 | 15 | 回到正题: 16 | 17 | 消息队列则是为大批量处理数据而准备的一个概念,他有很多实现方式,并不是单一的代码结构。 18 | 19 | 这里有比较专业的一篇文章: 20 | http://www.cnblogs.com/xuyatao/p/6864109.html 21 | 22 | 还有适合新手揣摩的教程: 23 | http://www.imooc.com/learn/852 24 | 25 |
26 | 27 | 废话说完了,彻底进入正题: 28 | 29 | Laravel 为我们提供了一个简单并很容易配置的队列类. 30 | 31 | 引用一点官方翻译的话: 32 | > Laravel 队列为不同的后台队列服务提供统一的 API , 例如 Beanstalk,Amazon SQS, Redis,甚至其他基于关系型数据库的队列。 队列的目的是将耗时的任务延时处理,比如发送邮件,从而大幅度缩短Web请求和相应的时间。 33 | 34 | > 队列配置文件存放在 config/queue.php。 每一种队列驱动的配置都可以在该文件中找到, 包括数据库, Beanstalkd, Amazon SQS, Redis, 以及同步(本地使用)驱动。 其中还包含了一个null队列驱动用于那些放弃队列的任务。 35 | 36 | 37 | Laravel 在 5.4版本中直接提供了全局函数 dispatch(),你可以再任意地方调用。并且无需加载任何对象或者实例化类。 这个函数主要的用途就是将你的队列需求加入到指定的容器中(专业点的叫生产者,其实你大可理解为你在商城购物完排队结账的时候) 38 | 39 | 40 | # 设置驱动 41 | Laravel神奇数据库迁移我就不多说了。我相信你知道。 42 | ``` php 43 | php artisan queue:table 44 | 45 | php artisan migrate 46 | ``` 47 | 执行完上面两条命令,费力打开 config\queue.php, key=default 的数组中使用env配置文件加载方式,laravel安装后默认为sync(同步),我们需要改为异步(你现在可以暂时认为同步!=队列),这里我们选择使用关系型数据库来实现队列 48 | ```shell 49 | QUEUE_DRIVER=database 50 | ``` 51 | 52 | # 创建任务 53 | 创建任务 = 搞一个生产者 = (其实就是写一个在队列中你想执行的业务逻辑),名字随意取,但最好遵守命名规范 54 | ``` 55 | php artisan make:job SendReminderEmail 56 | ``` 57 | 这个生成的文件大概分2部分:__construct() 构造方法 , handle 队列执行方法(意思就是在队列执行的时候,就用你这里面写的代码) 58 | ``` 59 | class SendReminderEmail implements ShouldQueue 60 | { 61 | // 这块你不用搭理他 62 | use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; 63 | 64 | protected $name; 65 | 66 | /** 67 | * Create a new job instance. 68 | * 69 | * @return void 70 | */ 71 | public function __construct($name) 72 | { 73 | $this->name = $name; 74 | } 75 | 76 | /** 77 | * Execute the job. 78 | * 79 | * @return void 80 | */ 81 | public function handle() 82 | { 83 | DB::table('email')->insert([ 84 | 'name' => $this->name, 85 | 'img'=>1, 86 | 'sort'=>1 87 | ]); 88 | } 89 | } 90 | ``` 91 | # 生产者 92 | 随后在控制器内使用dispatch方法调用即可,下面我则for循环创建了100个业务 93 | ``` 94 | public function index(Request $request) 95 | { 96 | for ($i = 0; $i <= 100; $i++) { 97 | dispatch(new SendReminderEmail("email" . $i)); 98 | } 99 | } 100 | ``` 101 | 102 | 你通过数据迁移的数据表中就基本成这样了 103 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/4045092428-598c7ac72a29d_articlex.png) 104 | 105 | # 消费者 106 | 消费者 = 队列处理 = (你在商城购物已经开始付钱了),使用下面命令则你开始消费,队列也会按照你上面的业务逻辑开始处理。处理完毕后当前任务会自动删除。 107 | ``` 108 | php artisan queue:work 109 | ``` 110 | 基本就下面这个样 111 | 112 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/1645244754-598c7b6a96b37_articlex.png) 113 | 114 | 到此Laravel5.4 队列简单配置与使用就结束了。 115 | 116 | 更多专业吊炸天的教程请参考China Laravel 117 | http://d.laravel-china.org/docs/5.4/queues -------------------------------------------------------------------------------- /RabbitMQ 初体验.md: -------------------------------------------------------------------------------- 1 | ![clipboard.png](https://blog.fastrun.cn/wp-content/uploads/2018/07/1220604718-5b3cd86641ab8_articlex.png) 2 | 3 | # 概述 4 | > RabbitMQ是一款消息队列中间件。他提供了几乎覆盖所有语言的SDK与文档,简直强大的不的了。要详细的去了解学习RabbitMQ,我建议还是看官方文档吧。http://www.rabbitmq.com/getstarted.html 5 | 6 | 消息队列有以下几个基本用途 7 | 8 | - 异步处理 9 | - 应用解耦 10 | - 流量削峰 11 | - 系统架构 12 | 13 | > 消息队列的这几个用途我会在后续的文章以真实案例去表述 14 | 15 | # 生产者 16 | ## 创建RabbitMQ链接 17 | ``` 18 | $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); 19 | $channel = $connection->channel (); 20 | ``` 21 | 与链接一个数据库概念差不多 22 | ## 创建一个队列声明 23 | ``` 24 | $channel->queue_declare ('hello'); 25 | ``` 26 | 创建多个队列与创建多个数据库一样,hello则是队列名 27 | 28 | ## 创建一条消息到队列 29 | ``` 30 | $message = new AMQPMessage('Hello World!'); 31 | ``` 32 | 33 | ## 发布消息到队列 34 | ``` 35 | $channel->basic_publish ($message, '', 'hello'); 36 | ``` 37 | hello 是上面创建的队列声明 38 | 39 | ## 关闭链接 40 | ``` 41 | $channel->close (); 42 | $connection->close (); 43 | ``` 44 | 释放资源 45 | 46 | # 消费者 47 | 48 | ## 创建RabbitMQ链接 49 | ``` 50 | $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); 51 | $channel = $connection->channel (); 52 | ``` 53 | 与链接一个数据库概念差不多 54 | ## 创建一个队列声明 55 | ``` 56 | $channel->queue_declare ('hello'); 57 | ``` 58 | 创建多个队列与创建多个数据库一样,hello则是队列名 59 | 60 | ## 消费 61 | ``` 62 | $channel->basic_consume ('hello', '', false, true, false, false, function ($msg) { 63 | echo ' [x] Received ', $msg->body, "\n"; 64 | }); 65 | ``` 66 | 通过回调函数处理消息队列 67 | 68 | ## 等待 69 | ``` 70 | while (count ($channel->callbacks)) { 71 | $channel->wait (); 72 | } 73 | ``` 74 | 无消息时,挂起保持等待状态 75 | 76 | ## 关闭链接 77 | ``` 78 | $channel->close (); 79 | $connection->close (); 80 | ``` 81 | 释放资源 82 | 83 | # 完整的案例 84 | 完整的按钮则是上面所有代码的整理 85 | ## 生产者 86 | ``` 87 | $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); 88 | $channel = $connection->channel (); 89 | $channel->queue_declare ('hello'); 90 | 91 | $message = new AMQPMessage('Hello World!'); 92 | $channel->basic_publish ($message, '', 'hello'); 93 | 94 | $channel->close (); 95 | $connection->close (); 96 | ``` 97 | ## 消费者 98 | ``` 99 | $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest'); 100 | $channel = $connection->channel (); 101 | $channel->queue_declare ('hello'); 102 | // basic_consume 方法第7个方法可以直接传入函数 103 | $channel->basic_consume ('hello', '', false, true, false, false, function ($msg) { 104 | echo ' [x] Received ', $msg->body, "\n"; 105 | }); 106 | while (count ($channel->callbacks)) { 107 | $channel->wait (); 108 | } 109 | $channel->close (); 110 | $connection->close (); 111 | ``` 112 | 113 | ## 执行 114 | ``` 115 | php {生产者}.php 116 | php {消费者}.php 117 | ``` 118 | 119 | # 其他 120 | RabbitMQ支持多线程处理消息队列,所有你可以开启多个消费者去执行消息队列内的任务。你可以像我这样 121 | 122 | ![clipboard.png](https://blog.fastrun.cn/wp-content/uploads/2018/07/3389455799-5b3cd628ce5cc_articlex.png) 123 | 124 | > 如果你感觉RabbitMQ这玩意还需要安装啥的感觉特麻烦,那你可以看下我的Laravel队列如何简单的玩起来。 125 | https://segmentfault.com/a/1190000010604659 126 | 127 | # 致谢 128 | 感谢你看完我这篇文章,纯手记的一篇文章,官方文档对新手的理解造成很多的误解,所以整理此文档,尽量避免新手“进”坑吧。对文章有什么问题或疑问,欢迎在评论区留言。谢谢 -------------------------------------------------------------------------------- /Nginx/GitLab搭建并接入自建Nginx.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 前言 4 | > 我一生的文章都会放在这里,我的博客,我希望每一行代码,每一段文字都能帮助你。https://github.com/CrazyCodes/Blog 5 | 6 | 大家好,我是CrazyCodes,这篇文章的标题有些严肃了,算是非常单纯的教学文,愿可以帮助到有需要的人。 7 | 8 | # GitLab 9 | GitLab与GitHub和码云一样,都是以web形式存在的在线管理Git仓库并且都拥有优雅的可视化操作页面。当然这篇文章咱们不介绍这没用的。 10 | 11 | 因为GitLab/Hub或者码云对免费用户不是够友好,无论是在库的容量或者协作人员都有些许限制,GitHub的提交速度对中国程序员更是慢如蜗牛。 12 | 13 | 网络上关于GitLab安装的文章并不少,因为GitLab在安装过程中会默认再安装一个Nginx,这必然会与已有Nginx冲突,写这篇文章主要的目的还是对已存在Nginx服务的服务器如何配置GitLab做一个指导。 14 | 15 | # 安装 16 | 服务器版本:(阿里云) CentOS Linux release 7.6.1810 17 | GitLab版本:12.4.0-ee 18 | Nginx版本:nginx/1.17.0 19 | 20 | 基本都算是比较新的版本了,废话不多说,我们开始吧! 21 | 22 | ## 第一步:进入GitLab官网 23 | [https://about.gitlab.com/install/](https://about.gitlab.com/install/) 24 | 25 | ## 第二步: 执行一大堆牛逼的命令 26 | ``` 27 | sudo yum install -y curl policycoreutils-python openssh-server 28 | sudo systemctl enable sshd 29 | sudo systemctl start sshd 30 | sudo firewall-cmd --permanent --add-service=http 31 | sudo firewall-cmd --permanent --add-service=https 32 | sudo systemctl reload firewalld 33 | ``` 34 | ## 第三步:下载GitLab安装文件 35 | ``` 36 | curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash 37 | ``` 38 | 39 | ## 第四步:安装 40 | ``` 41 | sudo EXTERNAL_URL="https://gitlab.example.com" yum install -y gitlab-ee 42 | ``` 43 | EXTERNAL\_URL 这个配置就默认好了,毕竟咱们也不用GitLab自带的Nginx 44 | 45 | ## 第五步:完成 46 | 就是这么简单,安装完成了。第一次访问的时候会要求重新设置密码。用户名默认为 root 47 | 48 | # 配置 49 | 安装完成后,需要对现有的GitLab做一些配置更改。 50 | 51 | 官方文档:[https://docs.gitlab.com/12.4/omnibus/settings/nginx.html#using-a-non-bundled-web-server](https://docs.gitlab.com/12.4/omnibus/settings/nginx.html#using-a-non-bundled-web-server) 52 | 53 | ## 禁用NGINX 54 | 在文件 55 | ``` 56 | /etc/gitlab/gitlab.rb 57 | ``` 58 | 下,将配置设置为false 59 | ``` 60 | nginx['enable'] = false 61 | ``` 62 | 63 | ## 设置所属用户 64 | 需要将自建Nginx的所属用户加入到配置内 65 | 在文件 66 | ``` 67 | /etc/gitlab/gitlab.rb 68 | ``` 69 | 下,将配置设置为 www-data 70 | > 对于Debian/Ubuntu,默认用户是`www-data` 71 | > 对于RHEL/CentOS,NGINX用户是`nginx`。 72 | 73 | 当然如果安装nginx未指定所属用户,也有可能会是root。当然可以通过命令 74 | ``` 75 | cat /usr/local/nginx/conf/nginx.conf | grep user 76 | ``` 77 | 查看所属用户,实际就在配置文件里写着 78 | ``` 79 | user root; 80 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 81 | # '"$http_user_agent" "$http_x_forwarded_for"'; 82 | ``` 83 | 回到正题,修改配置为root(刚刚讲过了,为啥是root) 84 | ``` 85 | web_server['external_users'] = ['root'] 86 | ``` 87 | 这个设置是一个数组,可以添加多个用户。设置好后运行命令 88 | ``` 89 | sudo gitlab-ctl reconfigure 90 | ``` 91 | 配置已生效 92 | 93 | ## 添加虚拟主机 94 | GitLab已经为开发者准备好相应的vhost文件。可以通过访问下方链接获取 95 | [https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server) 96 | 配置完成后执行 97 | ``` 98 | nginx -s reload 99 | ``` 100 | 既完成,原理很简单,其他配置我也不是太明白,大概则是使用反向代理转发了一下请求这样子。 101 | 102 | # 问题 103 | 在最新版本中,如果按照上述方式配置,可能会在提交过程中出现422错误,具体原因不清楚,我遇到这个问题后配置了ssl,解决了这个问题。 104 | 105 | # 致谢 106 | 大多都是官方的文字,可能只是不太好找,所以网络上经常出现这个问题,但都无解,所以写这么个水文,帮助需要的人,当然,提倡还是去看官网的文档。 107 | 108 | 感谢你看到这里,希望本篇文章可以帮到你。 109 | -------------------------------------------------------------------------------- /Supervisor 从入门到放弃.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/963433467-5b9b1ea063ba7_articlex.png) 2 | 3 | # 前言 4 | > Supervisor是一个客户端/服务器系统,允许其用户在类UNIX操作系统上控制许多进程。(官方解释) 5 | 6 | 简单点来讲,就是一个监控脚本运行的工具,不过他可以统一化管理,laravel的队列文档上也有相关使用方式方法,例如 7 | 8 | - 定时脚本的启动、重启、关闭和日志监控 9 | - swoole的启动、重启、关闭和日志监控 (众所周知,swoole大部分的特性都只能在cli中运行) 10 | - redis的启动、重启、关闭和日志监控 (redis自身未提供类似phpmyadmin的后台可视化工具) 11 | - laravel中的队列、一些自动化的脚本、workman等等的脚本 12 | 13 | 一般都使用 ``` &test.sh ``` 让其来保证在后台运行,但在很多情况下,无法对脚本个人化的监控。这时你可能就需要 Supervisor 来帮助你。你大可把它当作你的unix系统的可视化管理后台。下面来让我们见证它的强大之处。 14 | 15 | # 安装 16 | Supervisor 有多种安装方式,我推荐其中最简单也是最容易安装的一种 17 | ``` 18 | apt-get -y install python-setuptools 19 | easy_install supervisor 20 | ``` 21 | 正如你所见,两条命令即完成安装 22 | # 配置 23 | Supervisor安装完成后,运行 ```echo_supervisord_conf```。这将打印一个示例的Supervisor配置文件到您的终端。只要你能看到打印的配置文件内容。 24 | 25 | Supervisor 不会自动生成配置文件。 26 | 27 | 请使用命令 ```echo_supervisord_conf > /etc/supervisord.conf ``` 来生成配置文件。 28 | 29 | ## 部分配置文件信息表 30 | | 名称 | 注释 | 栗子 | 31 | | -- | -- | -- | 32 | | inet_http_server[port] | 内置管理后台 |*:8888 | 33 | | inet_http_server[username] | 管理后台用户名 | admin| 34 | | inet_http_server[password] | 管理后台密码 | admin | 35 | | include[files] | 设置进程配置文件格式 | /etc/supervisor/supervisor.d/*.ini | 36 | 37 | # 运行 38 | Supervisor 启动需加载配置文件 39 | ``` 40 | supervisord -c /etc/supervisor/supervisord.conf 41 | ``` 42 | 停止命令是 43 | ``` 44 | supervisorctl shutdown 45 | ``` 46 | 重新加载配置文件 47 | ``` 48 | supervisorctl reload 49 | ``` 50 | 51 | Supervisor 以 ```[program:[your_cli_name]] ``` 以每段进程配置文件的开头,your_cli_name 则是你的进程名称,名称会显示在Supervisor后台管理工具和Supervisor cli命令输出上。我们以运行php-fpm为例 52 | ``` 53 | [program:php7] 54 | command=php-fpm 55 | ``` 56 | 哦呦,就是酱紫简单。没有过多的废话。或者运行一段shell。 57 | ``` 58 | [program:echo] 59 | command=sh echo.sh 60 | 61 | -------------------------------- 62 | 63 | echo.sh 64 | 65 | your_name="my name zhangsan" 66 | echo $your_name 67 | 68 | ``` 69 | 当然laravel队列也是依旧简单 70 | ``` 71 | [program:laravel-worker] 72 | command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 73 | ``` 74 | 当然这里只是简单的演示,让你可以快速上手,配置脚本内不仅仅只有command命令。 75 | 具体可见官方文档 http://www.supervisord.org/configuration.html#program-x-section-settings 76 | # 后台 77 | Supervisor提供的后台管理比较简单 78 | 79 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/2731681913-5b9b1b03bc8d0_articlex.png) 80 | 81 | 大致功能有 重启、启动、停止进程,打印日志,清除日志等。基本上就这么几个简单的功能,当然也没有宕机报警,日志报警什么的。不过强大的Supervisor为我们提供了接口😄 82 | 83 | # 接口 84 | 通过API能获取基本所有的信息,例如进程列表,某个进程的状态,进程日志。包括对进程的重启、停止、开启等操作,将Supervisor彻底集成到内部监控后台也是没有什么问题的。 85 | 86 | 具体内容请移步官网Api文档 http://www.supervisord.org/api.html 87 | 88 | # 脚本 89 | 最后送给朋友们一个简单的脚本,方便用于学习Supervisor 90 | ```shell 91 | #!/bin/bash 92 | set -x 93 | 94 | case $1 in 95 | 'sp') 96 | if [[ $2 == 'start' ]]; then 97 | "supervisord -c /etc/supervisor/supervisord.conf" 98 | elif [[ $2 == 'stop' ]]; then 99 | "supervisorctl shutdown" 100 | elif [[ $2 == 'restart' ]]; then 101 | "supervisorctl shutdown" 102 | "supervisord -c /etc/supervisor/supervisord.conf" 103 | elif [[ $2 == 'reload' ]]; then 104 | "supervisorctl reload" 105 | fi 106 | ;; 107 | esac 108 | ``` 109 | 你可以使用这个简单的脚本快速启动、重启、关闭Supervisor 110 | ``` 111 | sh test.sh sp start // 启动 112 | sh test.sh sp restart // 重启 113 | ``` 114 | # 致谢 115 | 感谢看到这里,希望本章可以帮到你。谢谢 -------------------------------------------------------------------------------- /PHP/PHP程序员如何简单的开展服务治理架构(三).md: -------------------------------------------------------------------------------- 1 | 服务治理所治理的服务需要合理的部署与管理,本章我们讲一下SOA(面向服务架构),本人语言文笔不好,所以本章内容使用问答模式,参考了 [SOA面试题(http://www.jdon.com/soa/soa-interview.html)] 的面试题,通过对此站复杂的描述进行简单的讲解。 2 | 3 | # 概述 4 | SOA代表了面向服务架构,仅仅是一种概念,通过这种概念而演变出的各种各样的服务架构都可称为SOA架构,SOA核心的概念就是 “松耦合”。 5 | 6 | # 非SOA的架构 7 | ### 多语言开发 8 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/210127806-5aa1089344a41_articlex.png) 9 | ### 同语言开发 10 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3365616974-5aa108aa6ab4a_articlex.png) 11 | 12 | 服务与服务之间可能会是不同的开发语言或相同语言开发,他们的调用方式依旧只可以通过http去获取,或者比较流行的Restful Api的形式,无论是在性能与开发的过程中都是很笨的办法。 13 | 14 | # 什么是SOA的服务 15 | 16 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2598402666-5aa10a7a3ffd5_articlex.png) 17 | 18 | 在现实世界中,服务是一种我们花费购买到的一种预期的服务。 19 | > 1、(来自真实世界):你去餐馆订餐,您的订单首先进入到柜台,然后在厨房进行食物准备,最后服务员提供的食物。因此,为了实现一个餐厅订购服务,您需要三个逻辑部门/服务协同工作(计帐,厨房和服务员)。在软件世界同样的方法称为业务服务。 20 | 21 | > 2、(软件世界):你去亚马逊订购了一本书,有不同的服务,如支付网关,库存系统,货运系统等共同完成一本书的订购。 22 | 23 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2690787889-5aa1097e32df6_articlex.png) 24 | 25 |   所有的服务是自包含的,合乎逻辑。他们就像黑盒子。总之,我们并不需要了解业务服务的内部工作细节。对于外部世界,它只是一个能够使用消息交互的黑盒子。例如在“支付网关”业务服务获得消息“检查信贷”后会给出输出:这个客户的信贷有或没有。对于“订单系统”,“支付网关”的服务是一个黑盒子。 26 | 27 | # 服务的主要特点是什么 28 | A) SOA组件是松耦合的。当我们说松耦合,这意味着每一个服务是自包含单独存在的逻辑。举例来说,我们采取了“支付网关”的服务,并将它附加到不同的系统。 29 | 30 | B) SOA服务是黑匣子。在SOA中,服务隐藏有内在的复杂性。他们只使用交互消息,服务接受和发送消息。通过虚拟化一个服务为黑盒子,服务变得更松散的耦合。 31 | 32 | C) SOA服务应该是自定义: SOA服务应该能够自己定义。 33 | 34 | D) SOA服务维持在一个列表中: SOA服务保持在一个中央存储库。应用程序可以在中央存储库中搜索服务,并调用相应服务。 35 | 36 | E) SOA服务可以编排和链接实现一个特定功能: SOA服务可以使用了即插即用的方式。例如,“业务流程”中有两个服务“安全服务”和“订单处理服务” 。从它的业务流程可以实现两种类型:一,您可以先检查用户,然后处理订单,或反之亦然。是的,你猜对了,使用SOA可以松散耦合的方式管理服务之间的工作流。 37 | 38 | # 什么是SOA 39 | SOA代表了面向服务的架构。 SOA是一种使用松耦合的黑盒子服务构建业务应用的体系架构,这些服务可以通过编排连接在一起以实现特定的功能。 40 | 41 | # 什么是合同,地址和绑定? 42 | 这是三个SOA的标准术语。每个服务对外开放地址,在服务开发中进行合同约定,客户端绑定服务进行开发调用。 43 | 44 | > * 合同是两方或多方之间的协议。它定义了一种客户端如何与服务通信的协议。从技术上讲,它有描述参数和返回值的方法。 45 | > * 地址表明在哪儿能找到这种服务。地址是一个URL,它指向服务的位置。 46 | > * 绑定是决定这个端点如何可以访问。它决定了如何完成通信。例如,你暴露你的服务,可以使用SOAP over HTTP或通过TCP的BINARY进行访问。因此,对于这些通信介质将被创建两个绑定。 47 | n 48 | 49 | # 什么是可重用的服务? 50 | > 服务是一个自主的,可重复使用的,可发现的,无状态的,有一定粒度的功能,并且是一个复合应用程序或一个组合服务的一部分。 51 | 52 | > 可重复使用的服务通过业务活动标识,这个业务活动是使用服务规范(设计时合同)描述的。 53 | 54 | > 一个服务约束是,包括安全性,QoS,SLA,使用策略,可以由多个运行时的合同 多个接口(WSDL中的Web服务)以及多个实现(代码)定义的。 55 | 56 | > 可重复使用的服务应在被管制在其从设计到运行整个企业级生命周期。其重用应通过规范流程来推动,重用应该是可测量的。 57 | 58 | # 在一个SOA中如何实现松耦合? 59 | > 实现松耦合一种策略是使用服务接口(WSDL中为SOAP Web服务)来限制服务之间的依赖性,对消费者隐藏服务实现。松耦合可以通过实施服务的功能封装以及限制服务接口的实现变化影响来解决。然而,在某些时候,你需要改变接口,也不会影响服务的消费者,除了管理多个安全约束,多种传输,以及其他方面的考虑。 60 | 61 | # SOA的服务无状态或有状态? 62 | > 服务应该是无状态的。它有一个无状态的执行上下文,但它不会有中间状态来等待一个事件或一个回调。状态有关的数据的保留一定不能超出的服务的请求/响应。这是因为状态管理消耗了大量的资源,这可能会影响服务的可重用 可伸缩性和可用性。 63 | 64 | 在RPC服务启动后,服务一直保持沉睡状态,只有在有请求时才会唤醒,你可以称他为无状态或有状态 65 | 66 | # 在SOA中我们是否需要从头开始构建系统? 67 | > 否。如果您需要集成现有系统为业务服务,你只需要创建松耦合的包装,包装您的现有系统,并以一种通用的方式暴露功能给外部世界。 68 | 69 | 其实并不需要重新构建,只需要将每个服务继续分解,分类出对外与对内。 70 | 71 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3373537274-5a98b8a1ccbb7_articlex.png) 72 | 73 | 74 | # 什么是服务和组件之间的区别? 75 | > 服务组件来实现业务功能的逻辑组件分组。组件是为实现服务这个目标的途径。组件可以使用Java,C#,C,但服务将以通用格式如像Web服务方式被暴露。 76 | 77 | 说的就是我们通过RPC调用其他服务 (thrift) 78 | 79 | 80 | # 预告 81 | 看到这里大概了解了SOA,这个时候我透露一个秘密,SOA其实还有另外一个名字叫“**服务治理**”,是的,就是我们一直在讲的服务治理。下一章回到正题,讲一下服务治理实现 82 | 83 | PHP程序员如何简单的开展服务治理架构(一) 84 | https://segmentfault.com/a/1190000013481688 85 | 86 | PHP程序员如何简单的开展服务治理架构(二) 87 | https://segmentfault.com/a/1190000013544601 88 | 89 | 本猿人写了一个服务治理的框架 90 | https://github.com/CrazyCodes/Service-Govern 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /浅谈架构是为了什么 (下).md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/2764268624-5b8dfddb8384f_articlex.png) 2 | 3 | # 前言 4 | 上一章对架构进行了通俗的解释,本章以图文并茂的形式对架构的演变做详细的阐述 5 | 6 | > 架构并非因高并发、大数据而生,以下的架构方式是根据业务演变而变更。 7 | 8 | 从现在开始,假设我们自己是一个创业的小团队。没资金没人脉,靠技术打天下。现在要开发一套电商系统。开始自己的表演。 9 | 10 | # 1.0 11 | 没钱没人,只能买得起一台阿里云学生机。这时我们只能选择使用下面的架构 12 | 13 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/2610865816-5b8dfba433e9c_articlex.png) 14 | 15 | 单机部署整个LNMP环境是唯一选择,这时我们还可以对1.0版本做一些优化的地方,在主从数据库这里,要注意了。单机跑主从简直是多此一举。单机流量本身就有效。主与从的承受是一致的。所以主从在单机跑?(相信你不是在开玩笑),但如果视数据与声明的话,主从还是有必要的(当备份了呗)。优化后的架构图如下 16 | 17 | 18 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/2389145605-5b8dfd222c6f3_articlex.png) 19 | 20 | 虽然依旧很勉强,但我们将文件存储转移到了其他云,比较少于n个g流量是免费的😄,对php进行了优化,改修改的都改了,主从也做了。这个1.0的版本勉强的撑过了创业初期。然后灾难再一次降临... 21 | 22 | # 1.* 23 | 这时候手头已经有点钱了。公司准备再购入一台服务器。这时架构如下所示 24 | 25 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/2207911866-5b8e1e51cc901_articlex.png) 26 | 27 | 新购入的机器与老机做了一个负载均衡,将流量分发。这时候主从数据库就派上了很大的用场。这里先将主数据库作为增删改数据库,而从数据库而是查数据库。接下来可以安逸一段时间了。但在生产环境上,我们需要预知问题,检测bug,所以无奈下又改善架构 28 | 29 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/4120730968-5b8e1f8e6c97c_articlex.png) 30 | 31 | 将日志,包括mysql慢日志,查询日志,nginx的请求日志,错误日志,php的错误日志,慢日志都暂时存入redis内,至少我存起来了。有问题我能有处可查。随后将大表切分,例如文章表,用户表的。1.x的版本再不断迭代中越来越完善。但一旦出现并发就挂掉。这事看来需要认真的去对待了。 32 | 33 | # 2.0 34 | 谈及并发,我了解的并不深入,仅仅知道是一瞬间对服务器的冲击数,解决并发的办法也很简单,将请求分流,分的越细越好(这也并不是越多越好,具体界限,我也不清楚),架构图如下 35 | 36 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/4278400467-5b8e222498254_articlex.png) 37 | 38 | 将用户请求(当然我指的是部分的),例如抢红包,限时抢购等业务,加入到队列中,挂为待处理形态,处理结束后将处理结果存储到redis然后通知用户,定时某个时段将redis数据存储到mysql,结束战斗。我想象的我们的公司已经比较牛x了。购入了很多台服务器。时候对现有的架构做出一些改变了。但并不是天翻地覆的变化。 39 | 40 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/3993613825-5b8e23edee21f_articlex.png) 41 | 42 | 当然这仅仅是物理架构,真实的业务要复杂一些。接下来通过不断的努力,我们开启了2.x时代。 43 | 44 | 45 | # 2.* 46 | 应该具备的设备及其环境都准备好了。现在我们需要做的是将业务需求补齐,当然也没有想象的那么夸张。 47 | 48 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/1507028583-5b8e25cb15646_articlex.png) 49 | 50 | 我们负载均衡作为分发调节器,将请求平均到指定服务器,通过开发语言去查询数据库数据然后返回,这一系列的操作都在监控系统与日志系统的监控中,当然说是系统,实际就是一个后台,一套程序,去监控数据时做筛选。如遇紧急情况则报警。数据正常并不是直接存储到数据库而是扔到队列,任由它翱翔。当业务不断壮大。其缺点或者漏洞并不是某种架构方案可以去解决,要就事论事,瞧病就医。这才是架构师的精髓所在。以上描述的这些套路。不好意思的说已经差不多吸光了我的架构知识库。下面的东西是正在研究的架构。 51 | 52 | # 3.0 53 | 我们预计上线的3.0版本引用了服务治理的架构思想。将业务分割,在本地通过tcp直接请求,并非通过http。这块我写过几篇文章,@周梦康 康神也有不错的直播讲解。以下为链接。我就不过多讲解了。 54 | 55 | PHP程序员如何简单的开展服务治理架构(一) [https://segmentfault.com/a/1190000013481688](https://segmentfault.com/a/1190000013481688) 56 | 57 | PHP程序员如何简单的开展服务治理架构(二) [https://segmentfault.com/a/1190000013544601](https://segmentfault.com/a/1190000013544601) 58 | 59 | PHP程序员如何简单的开展服务治理架构(三) [https://segmentfault.com/a/1190000013624228](https://segmentfault.com/a/1190000013624228) 60 | 61 | PHP 进阶之路 - 零基础构建自己的服务治理框架(上) [https://segmentfault.com/l/1500000011300619](https://segmentfault.com/l/1500000011300619) 62 | 63 | PHP 进阶之路 - 零基础构建自己的服务治理框架(下)[https://segmentfault.com/l/1500000011301018](https://segmentfault.com/l/1500000011301018) 64 | 65 | # 3.* 66 | 3.x是一个学习参考,自我反省,自我检讨的过程。参考大厂的架构设计,结合自己公司的架构设计,取其精华、去其糟粕。贴一张大厂的架构图我感觉毫无意义。这里就不多讲了。 67 | # 老一套 68 | 现在回到文章的中心思想上。到底为了什么做架构?我的答案是 “活” ,为了让产品活下去而做架构。什么时候可以做架构? 任意时间都可以做。但要看精力、财力、人力。 69 | ## 可扩展 70 | 可扩展性上篇文章我说过,是一把束缚我的刀,这把刀是什么?是“灾难”,在设计架构,包括在业务,物理或者是说部署上。这把刀一直在我脖颈处,如果这时为了省事,躲开了,那未来的某个时间,这把刀就会断头。做了五年的程序员。实际困难需求、复杂需求还有部分BUG的产品,个人认为与扩展脱不了关系。无论在任何的架构设计上,要考虑向前扩展、向后迭代。 71 | ## 可跑路 72 | 做好架构,写好代码。方能轻松跑路,否则后患无穷。 73 | 74 | # 致谢 75 | 感谢你看到这里,希望本篇可以帮到你。有问题可在评论区留言,谢谢 🙏 -------------------------------------------------------------------------------- /Go/PHP To Go 转型手记 (终).md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2978289583-5bd423e85207c_articlex.png) 2 | 3 | # 前言 4 | 作为一名PHP程序员,我感到荣幸。但在时代不断的变迁中,要具备足够的知识才可生存。 5 | 6 | 那就从Go语言学起把。 7 | 8 | 希望看到本篇文章的你可以对Go有一个基本的认识。本系列文章与我自己学习Go语言的方式去描述。以PHP代码与Go代码的对比加以区分理解。 9 | 10 | 这是转型手记的最后一章,在往下没办法再去写了,需要详细的看书去了解。本章以实战方式来对比以下PHP与Golang的写法。 11 | 12 | 这里使用Laravel与Beego(基于Go开发的MVC框架)来演示 13 | 14 | # 安装 15 | Laravel 16 | ``` 17 | // 通过composer直接安装,简单易用 18 | composer global require "laravel/installer" 19 | // 创建一个项目 20 | laravel new blog 21 | ``` 22 | Beego 23 | ``` 24 | // go自身就有包管理 25 | go get github.com/astaxie/beego 26 | // 创建项目也非常简单 27 | bee api blog 28 | ``` 29 | # 目录结构 30 | Laravel 31 | ``` 32 | // laravel 的结构这里就不再阐述 33 | | - app 34 | | - bootstrap 35 | | - config 36 | | - database 37 | | - public 38 | | - resources 39 | | - routes 40 | | - storage 41 | | - tests 42 | | - vendor 43 | ``` 44 | Beego 45 | ``` 46 | // 显而易见,beego并没有laravel那样过度设计(虽然过度设计并非指目录, 47 | // 但以看目录就知道beego真的没有太多东西) 48 | blog 49 | ├── conf 50 | │ └── app.conf 51 | ├── controllers 52 | │ └── object.go 53 | │ └── user.go 54 | ├── docs 55 | │ └── doc.go 56 | ├── main.go 57 | ├── models 58 | │ └── object.go 59 | │ └── user.go 60 | ├── routers 61 | │ └── router.go 62 | └── tests 63 | └── default_test.go 64 | ``` 65 | # 路由 66 | Laravel 67 | ``` 68 | Route::get('/user', 'UserController@index'); 69 | ``` 70 | Beego 71 | ``` 72 | // 与laravel的使用方式差不多 73 | // 这里为了统一,路由直接绑定控制器方法只有下列这种 74 | // beego 还提供了注解方式 , 详情见 https://beego.me/docs/mvc/controller/router.md 75 | beego.Router("/user",&UserController{},"get:index") 76 | ``` 77 | # 模型 (Model) 78 | Laravel 79 | ``` 80 | User::findOrFail($id)]); 136 | } 137 | } 138 | ``` 139 | Beego 140 | ``` 141 | package controllers 142 | 143 | import ( 144 | "github.com/astaxie/beego" 145 | "github.com/astaxie/beego/orm" 146 | ) 147 | 148 | // 这里相当于继承了父类 beegoController 149 | type MemberController struct { 150 | beego.Controller 151 | } 152 | 153 | 154 | func (c *CityController) Index() { 155 | var results []orm.Params 156 | 157 | orm.NewOrm().QueryTable("member"). 158 | Values(&results) 159 | 160 | c.Data["json"] = results 161 | c.ServeJSON() 162 | } 163 | ``` 164 | # 总结 165 | 学Go有1个多月的时间了。写这类的文章也遭到了很多质疑,作为一个手记去发布仅仅为了让想去学习其他语言的朋友了对新语言的一个认识,不是去对比其不同。而找其语法相似点。 166 | 167 | 语言只是工具,希望各位PHP工程师不要仅限于去使用PHP,这一年看到很多写PHP程序员未来之路一类的文章,但从未提出要去学习其他语言。 168 | 169 | 不要将自己圈在一个领域,不要做井底之蛙。 170 | # 致谢 171 | 感谢你看到这里,希望本篇文章可以帮到你。谢谢 -------------------------------------------------------------------------------- /PHP/老项目重构手记之用户系统.md: -------------------------------------------------------------------------------- 1 | > 受邀来一起重构公司的老项目 2 | # 概述 3 | 重构首先要注意几个点 4 | - 重构后功能的可扩展性 5 | - 业务互相依赖的复杂度 6 | - 脱离本身的业务进行重构 7 | - 重构后的代码可读性与可维护性 8 | - 性能的提升 9 | 以上几点是重构注意的地方也是重构的目的 10 | 11 | # 分析 12 | 13 | 本次重构的项目运营了三年之久,用户及业务量也上不来。至于重构的真正原因不清楚。 14 | 15 | - 用户注册量:107470 16 | - 日PV:1000+ 17 | 18 | 非常的惨淡 19 | 20 | 1. 关于用户ID与其他业务绑定仅仅是单纯的存储用户ID进行绑定,类似与评论,购买等。这样在重新设计用户表的时候无需考虑其他表的业务是否有冲突或者依赖。 21 | 2. 前期设计上貌似接口及数据表字段设置问题,出现了数据重复的问题。 22 | 3. 功能重新写好后,在数据迁移方便,当然没法人工操作,php脚本去迁移也不现实,考虑使用数据队列等等方式进行数据迁移 23 | 4. 功能代码绝笔是另起炉灶写,在原程序上写复杂度有提升了一倍。 24 | 25 | 26 | # 原数据表结构 27 | 28 | > 部分字段 29 | 30 | | 字段名 | 类型 | 是否为空| 默认值 | 注释 | 31 | | -- | -- | -- | -- | -- | 32 | |MemberId | bigint(20) | Y | | 自增编码 | 33 | | MemberPhone | varchar(255) | N | '' | 手机号码 | 34 | | LoginTime | int(11) | Y | NULL | 登录时间 | 35 | | QuitTime | int(11) | Y | NULL | 退出时间 | 36 | | LoginState | tinyint(2) | Y | NULL | 登录状态 | 37 | | MemberState | tinyint(2) | N | 0 | 账号状态 | 38 | | MemberExpert | tinyint(2) | N | 0 | | 39 | | MemberRegTime | int(11) | Y | NULL | 注册时间| 40 | | DeviceToken | varchar(255) | Y | NULL | 设备标示 | 41 | | WxToken | char(32) | Y | NULL | 微信标示 | 42 | 43 | 问题点有以下几个 44 | 1. 数据字段设计时不应使用驼峰命名,应使用小写,单独分割用_ ,例如 member_tel , 索引的设置也存在一些问题 45 | 2. 字段尽量避免DEFAULT NULL 46 | 3. 根据需求分表,现在所有的第三方授权都放到一个表里了 47 | 48 | # 选型 49 | 前期重构要求速度要快。所以只能选择世界上最好的语言。 50 | 51 | - 语言:PHP 52 | - 框架:Laravel 53 | - 数据库:MySQL 54 | 55 | > 考虑到数据量也不小,手动操作是不可能了,选择使用RabbitMQ进行数据迁移 56 | 57 | # 新表设计 58 | 59 | ## 用户表 60 | ``` 61 | CREATE TABLE `member` ( 62 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 63 | `tel` bigint(20) DEFAULT NULL COMMENT '手机号码', 64 | `password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '登录密码', 65 | `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '账号状态 0:正常', 66 | `created_at` timestamp NULL DEFAULT NULL, 67 | `updated_at` timestamp NULL DEFAULT NULL, 68 | PRIMARY KEY (`id`), 69 | UNIQUE KEY `member_tel_unique` (`tel`), 70 | KEY `member_tel_status_index` (`tel`,`status`) 71 | ) ENGINE=InnoDB AUTO_INCREMENT=80073 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 72 | ``` 73 | 74 | ## 用户授权表 75 | ``` 76 | CREATE TABLE `member_authorized` ( 77 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 78 | `member_id` bigint(20) NOT NULL COMMENT '用户编码', 79 | `prefix` varchar(25) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '第三方名称', 80 | `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '第三方标示', 81 | `data` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '授权获得的用户信息', 82 | `created_at` timestamp NULL DEFAULT NULL, 83 | `updated_at` timestamp NULL DEFAULT NULL, 84 | PRIMARY KEY (`id`), 85 | KEY `member_authorized_prefix_index` (`prefix`) 86 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 87 | ``` 88 | 89 | ## 用户信息表 90 | ``` 91 | CREATE TABLE `member_data` ( 92 | `member_id` bigint(20) NOT NULL COMMENT '用户编码', 93 | `sex` enum('0','1','2') COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '性别 0=>女生 1=>男生 2=>未知', 94 | `nick_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名/昵称', 95 | `img` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户头像', 96 | `created_at` timestamp NULL DEFAULT NULL, 97 | `updated_at` timestamp NULL DEFAULT NULL, 98 | UNIQUE KEY `member_data_member_id_unique` (`member_id`) 99 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 100 | ``` 101 | # 队列 102 | 通过队列,可以选择laravel内置的队列或者rabbitmq都可以。将数据迁移到新表中。当然你需要选择一个访问量最低的时间段。并不是凌晨就少,不同的行业的活跃时间段不一样。建议先使用百度统计、腾讯分析等等的查看活跃时间区间。 103 | # 迭代 104 | 重构并不是一言一语,几行代码或者一个大佬的方案就可以解决的。实际重构也是一个开发的过程。在不断的迭代中,将重构完成的部分补回到业务中。 105 | # 致谢 106 | 感谢你看到这里,希望本篇文章可以帮到你。有问题可在评论区留言。 -------------------------------------------------------------------------------- /PHP/一道看似简单的面试题.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/4069507082-5b531a1a41be2_articlex.png) 2 | 3 | # 前言 4 | > 使用PHP,给定一个数,判断这个数是否是二的N次方 5 | 6 | 这样看似简单的一个面试题, 实际牵出了很多基础知识,本章在为大家补习基础知识的情况下来解答这道题。先亮出答案 7 | ``` 8 | function exponentiation($number) 9 | { 10 | if ($number < 0) { 11 | return false; 12 | } 13 | 14 | if (($number & ($number - 1)) == 0) { 15 | return true; 16 | } else { 17 | return false; 18 | } 19 | } 20 | 21 | exponentiation (2); 22 | exponentiation (8); 23 | exponentiation (16); 24 | exponentiation (32); 25 | exponentiation (128); 26 | ``` 27 | 这是查阅后的最最标准的答案,其他类似通过循环等等答案就略过把。 28 | 29 | # 运算 30 | 实际这道面试题考的并非是算法,而是作为开发人员的你“底子”够不够。 31 | 运算符应该是每门语言hello world 后立马要学习的。运算符分很多种,赋值运算符,比较运算符等等。我想大多自学或者培训出道的没有经过系统化学习的童鞋的,对这块的知识应该很薄弱把。当然赋值、比较什么的很熟悉,因业务基本逃不过这些。但对位运算符,你真的吃透了吗? 32 | 33 | > 这道题的考点一是位运算符的使用,上面说过了本题考察的并非算法,而是你对二进制的了解,而在php中能操作二进制的运算符**貌似** (我的知识范围内) 只有位运算符 34 | 35 | ## PHP位运算符 (部分) 36 | 37 | 本道面试题用到了 & 所以不详解其他的运算符,需要更多了解请移步官方 38 | http://php.net/manual/zh/language.operators.bitwise.php 39 | 40 | | 运算符 |附加信息 | 41 | | -- | -- | 42 | | & | 按位与运算符| 43 | | ^ | 按位异或运算符 | 44 | 45 | ## 按位与 46 | 按位与以简单易懂的方式来讲就是二进制位不相同的抵消,相同的保留 47 | 48 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2097028203-5b530d5d4e1b4_articlex.png) 49 | 50 | 举几个栗子 51 | 52 | > 公式 -> 转为二进制后的公式 -> 二进制结果 -> 十进制结果 53 | 54 | ``` 55 | 2 & 3 -> 0010 & 0011 -> 0010 = 2 56 | 10 & 7 -> 1010 & 0111 -> 0010 = 2 57 | 32 & 70 -> 0100000 & 1000110 -> 0000000 = 0 58 | ``` 59 | 60 | ## 按位异或 61 | 按位异或以简单易懂的方式来讲就是二进制位相同的抵消,不相同的保留 62 | 63 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2618903834-5b530dd93cefc_articlex.png) 64 | 65 | 举几个栗子 66 | 67 | > 公式 -> 转为二进制后的公式 -> 二进制结果 -> 十进制结果 68 | 69 | ``` 70 | 2 ^ 3 -> 0010 ^ 0011 -> 0001 = 1 71 | 10 ^ 7 -> 1010 ^ 0111 -> 1101 = 13 72 | 32 ^ 70 -> 0100000 ^ 1000110 -> 1100110 = 102 73 | ``` 74 | 75 | # 进制 76 | 借用百度百科上的一段话 77 | > 二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。 78 | 79 | 二进制本身就是为2这个数字而使用的,所以说这道面试题直指二进制的使用是没错的。2的n次方则就是 80 | 81 | | n |公式|结果| 82 | | -- | -- | -- | 83 | | 1 | 2| 2 | 84 | | 2 | 2x2 | 4 | 85 | | 3 | 2x2x2| 8 | 86 | | 4 | 2x2x2x2| 16 | 87 | 88 | 换算成二进制的表格是 89 | | n |公式|结果| 90 | | -- | -- | -- | 91 | | 1 | 2| 000010 | 92 | | 2 | 2x2 | 000100 | 93 | | 3 | 2x2x2| 001000 | 94 | | 4 | 2x2x2x2| 010000 | 95 | 96 | 由此看出2的n次方的二进制最高位是1,其余补0,(n&(n-1))==0 并且 n> 0 的情况下必定是2的n次方,为什么要-1呢?在二进制中每一位必须都不相同&后才会得出0,上述已经讲解了&的运算结果。例如十进制的16 97 | ``` 98 | 16 & (16 - 1) = 99 | 010000 & (001111) = 0 100 | ``` 101 | 不要纠结(n&(n-1))==0 这个公式是怎么来的,作为程序员,我感觉应该把更多时间放到反推上面来,去应证这个公式的正确性。 102 | > 验证是否是2的n次方,笨的方法就是一直除2,除到最后等于0则就是2的次方,所以公式如上 103 | 104 | # 补位 105 | 如果是2个二进制进行运算时,计算机会统一位数,例如 106 | ``` 107 | 01 108 | 011 109 | <------> 110 | 001 111 | 011 112 | ``` 113 | 计算机会将01自动补一位为001去方便运算。 114 | 115 | # 正负 116 | 在二进制中,第一位为1的是负数,0是正数。如果没有补零的情况下 117 | ``` 118 | 10000000000 119 | 01111111111 120 | ``` 121 | 虽然计算后也是0,但它并不是2的n次方,因为第一组二进制是负数。 122 | 123 | # 补充 124 | 由上述题补充的另外一道题 125 | > 给定任意数,计算是2的几次方? 126 | 127 | ``` 128 | function power($number){ 129 | if ($number < 0) { 130 | return false; 131 | } 132 | 133 | if (($number & ($number - 1)) == 0) { 134 | 135 | // 数学不好的,就看下面的方法 136 | // $number = decbin($number); 137 | // return (mb_strlen($number)-1); 138 | // 数学可以的就看下面的方法 139 | return floor(log($number,2)); 140 | } else { 141 | return false; 142 | } 143 | } 144 | ``` 145 | 1. 判断是否是2的n次方 146 | 2. 如果是则将十进制数字转为二进制 147 | 3. 计算总长度-1获取到是2的几次方,按照0的个数来计算 148 | 149 | # 致谢 150 | 感谢你看到这里,我也是文中提起的没好好学基础的一名程序员,但当你看到我这篇文章后,希望你也可以提起精神,去重温下基础,对你未来的职业生涯会起作用的。本章内容纯属自己理解,如有出入,请大佬们监督批评,谢谢🙏 -------------------------------------------------------------------------------- /PHP/2019 PHP程序员发展路线.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/2717341011-5c31b6cb3f5c1_articlex.png) 2 | 3 | # 前言 4 | 新的一年,新气象。在2012年我参加LAMP兄弟连的培训,成为一名PHP程序员。那个时候PHP盛行,简直有称霸世界的迹象。当然现在我大PHP也不差。我认为成为PHP程序员的有以下三种途径。 5 | 6 | 1. 科班出身,找了一份PHP开发相关的工作 7 | 2. 自学成才 (毕竟在当时PHP入门是所有语言里最简单的) 8 | 3. 培训班出身 (就像我,不是科班,自学能力也不强,所以参加的培训学校 注:2012年的LAMP兄弟连其实不错的,现在的就不评论了) 9 | 10 | 就我这近六年的职业生涯。为大家准备了一份2019年程序员发展路线。 11 | 12 | # 跟上潮流 13 | 14 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/377909040-5c31b6ff78519_articlex.png) 15 | 16 | 2012年的PHP是web开发的强者,我记得当年的PHP微信开发简直是火到不行,经过这几年的不断发展,PHP实际更偏向后端了。我已经很久没有动过前端的东西了,当年都是混合开发乱的不行,所以作为一个PHP程序员不要太计较前端的那些技能,注重后端该会的东西。 17 | 18 | # 框架 19 | 20 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/781387003-5c31b714cce4f_articlex.png) 21 | 22 | - [Laravel][1] 一款过度设计的,优雅的,复杂的 PHP开发框架 , 这个框架在我多年实践中证明只适合写后台,如果用他写接口你会发现性能与原生PHP差距很大,具体比对数据可自行Google。建议把Laravel的设计方式认真学习一下,并非必须去学习使用这款框架 23 | - [Lumen][2] 这是一款Laravel的Api框架,其速度要比Laravel快很多,是一款精简的Laravel 24 | - [Symfony][3] 没怎么看过这款框架,Symfony即是一款框架,也是一组PHP组件库,要知道Laravel的DB,Dump,Route,Response其实都是在Symfony组件基础上做的。可见laravel composer.json https://github.com/laravel/framework/blob/5.7/composer.json 25 | - [CodeIgniter][4] 也可以关注下上个时代框架霸主,CodeIgniter 他的新版本可能会有奇迹发生 26 | 27 | # 扩展 28 | 29 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/1047033209-5c31b73a96358_articlex.png) 30 | 31 | - [swoole][5] PHP异步编程框架,这个就不必多说了。自4.1.0 Swoole加入Coroutine,使并发开发更简单。语法非常类似Goroutine 32 | - [rabbitmq][6] 消息队列,数据过多的时候就知道有什么用了 33 | - [docker][7] 不要告诉我2019年你还不听过docker,容器技术泛滥,该看下了老铁,https://segmentfault.com/a/1190000016436478 , https://segmentfault.com/a/1190000015407534 34 | 35 | 36 | # DevOps 37 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/128687049-5c31b7544f60b_articlex.png) 38 | 39 | 这是一个看起来高端但很切合实际的话题。如何做到DevOps? 可以先了解下下面的知识 40 | 41 | - [travisCi][8] 一款基于Github的自动发布,自动集成,自动测试的平台 ,https://segmentfault.com/a/1190000017742260 42 | - [teamcity][9] jetbrains推出的一款自动发布、集成、测试的平台,https://segmentfault.com/a/1190000017117035 43 | - [phpunit][10] 当然做前面两个之前你必须学会如何有效的写测试 44 | - [composer][11] 学习强有力的搬砖技巧,板巧砖,要学会找各种组件包去实现自己的应用 45 | 46 | # 算法 47 | 48 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/2867389774-5c31b77b4bf82_articlex.png) 49 | 50 | 算法是程序开发的基础,(大厂更看重基础),可以适当在下方平台去联系 51 | 52 | - [LintCode][12] 53 | - [力扣][13] 54 | 55 | 什么?上面的题根本做不出来?没思路?乱七八糟的一些算法书我就不推荐的,首先判定你与我当年一样 (我们数学就没学好),虽然计算机算法与数学有些许出入,不过还是建议继续看我下面的建议。 56 | 57 | # 基础 58 | 从小就不爱学习的我,选择了这个职业,无奈基础没打牢(实际就是没打),我选择这样强补知识。作为山东人(北方人),我选择了人教版《数学》,如果你有这样的勇气,那么跟我一起来补基础吧。 59 | 60 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/3903592890-5c31b481e0cdf_articlex.png) 61 | 62 | 我是从初中数学开始到高中数学。在学习的过程中买很多试卷做,巩固练习。在这之后再考虑大学期间学习的知识吧。其他相关阅读书籍可参考下方 63 | 64 | - https://segmentfault.com/a/1190000015424521 65 | 66 | 除了数学外,则应该是计算机相关的线程,通信协议等等.... 67 | 68 | # 语言 69 | 70 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/324563442-5c31b7c930f82_articlex.png) 71 | 72 | 之所以把学习其他语言放到这里,是感觉并不是太重要,如果是一位长期战斗的程序员,我相信他的学习另外一门语言是手到擒来的。不过你也可以选择几门当做业余爱好。 73 | 74 | - Go 75 | - Java 76 | - C+ 77 | - Python 78 | 79 | 随意选择,学什么语言都一样,不过只是学学语法而已(至少大多人都是这样) 80 | 81 | 82 | # 致谢 83 | 84 | 感谢你看到这里,希望2019年的你比2018年更上一层楼,希望我的文章可以从根本上帮助到你。谢谢 85 | 86 | 87 | [1]: https://laravel.com/ 88 | [2]: https://lumen.laravel.com/ 89 | [3]: https://symfony.com 90 | [4]: https://github.com/bcit-ci/CodeIgniter 91 | [5]: https://www.swoole.co.uk/ 92 | [6]: https://www.rabbitmq.com/ 93 | [7]: https://www.docker.com/ 94 | [8]: https://travis-ci.org 95 | [9]: https://www.jetbrains.com/teamcity/?fromMenu 96 | [10]: https://phpunit.de/ 97 | [11]: https://getcomposer.org/ 98 | [12]: https://www.lintcode.com/ 99 | [13]: https://leetcode-cn.com/ -------------------------------------------------------------------------------- /Docker/五分钟快速了解Docker.md: -------------------------------------------------------------------------------- 1 | > 这些是我的笔记,都是记录的核心概念和使用方法 2 | 3 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2055627634-5b335ff4100b6_articlex.png) 4 | 5 | # 概述 6 | 使用docker搭建开发环境将近1年了,自我感觉docker的强大并非如此,不过没有机会将docker部署生产环境,有位架构师曾说过,最新的未必是最好的,架构要选择最佳的,请大家也慎重。 7 | 8 | # 核心概念 9 | ## 镜像 Image 10 | Docker镜像类似于虚拟机镜像。每个镜像包括了一个基本的操作系统环境 11 | 12 | ## 容器 Container 13 | Docker容器类似于一个轻量级的沙箱,Docker利用容器来运行和隔离应用。可以把容器看做一个简易版的linux系统环境 14 | 15 | ## 仓库 Repository 16 | Docker仓库类似于代码仓库,它是Dokcer集中存放镜像文件的场所 17 | 18 | # 数据管理 19 | > 生产环境中使用Docker的过程中,往往需要对数据进行持久化,或者需要在多个容器之间进行数据共享,这必然要涉及容器的数据管理操作 20 | 21 | 容器中管理数据主要有两种方式: 22 | * 数据卷 23 | * 数据卷容器 24 | 25 | 26 | ## 挂载一个主机目录作为数据卷 27 | ``` 28 | docker run -d -P --name web -v /localhost-path:/server-path NAME:TAG 29 | ``` 30 | 31 | # 端口映射 32 | 33 | ## 端口映射实现访问容器 34 | ## 从外部访问容器应用 35 | > 要让外部访问这些应用时,可以通过-P或-p参数来指定端口映射。当使用-P标记时,docker会随机映射一个49000~49900的端口到内部容器开放的网络端口 36 | ``` 37 | docker run -d -P NAME:TAG 38 | ``` 39 | ## 映射所有接口地址 40 | ``` 41 | docker run -d -p 5000:5000 NAME:TAG 42 | ``` 43 | > -p 可以标记多个端口 44 | ``` 45 | docker run -d -p 5000:5000 -p 3000:80 NAME:TAG 46 | ``` 47 | ## 映射到指定地址的指定端口 48 | ``` 49 | docker run -d -p 127.0.0.1:5000:5000 NAME:TAG 50 | ``` 51 | ## 映射到指定地址的任意端口 52 | > 使用IP::ContainerPort 绑定localhost的任意端口到容器的5000端口,本地主机会自动分配一个端口 53 | ``` 54 | docker run -d -p 127.0.0.1::5000 NAME:TAG 55 | ``` 56 | 还可以使用udp标记来指定udp端口 57 | ``` 58 | docker run -d -p 127.0.0.1:5000:5000/udp NAME:TAG 59 | ``` 60 | ## 查看映射端口配置 61 | ``` 62 | docker port nostalgic_morse PORT 63 | ``` 64 | 65 | # 容器互联 66 | 67 | ## 自定义容器命名 68 | > 使用--name标记可以为容器自定义命名 69 | ``` 70 | docker run -d -P --name web NAME:TAG 71 | ``` 72 | ## 查看容器名字 73 | ``` 74 | docker inspect -f "{{ .Name }}" 容器id 75 | ``` 76 | 77 | ## 容器互联 78 | > 使用--link参数可以让容器之间安全的进行交互 79 | ``` 80 | docker run -d --name db NAME:TAG 81 | docker run -d -P --name web --link db:db NAME:TAG 82 | ``` 83 | 84 | # 使用Docker镜像 85 | ## 下载镜像 86 | ```shell 87 | docker pull NAME[:TAG] 88 | ``` 89 | ## 创建容器 90 | ```shell 91 | docker run -it NAME:TAG SERVICE 92 | ``` 93 | 94 | ## 查看镜像 95 | ```shell 96 | docker images 97 | ``` 98 | * REPOSITOPY 来自于哪个仓库 99 | * TAG 镜像的标签信息 100 | * IMAGE ID 镜像的ID 101 | * CREATED 创建时间 102 | * SIZE 镜像大小 103 | 104 | ## 给镜像打标签 105 | ```shell 106 | docker tag NAME:TAG NEWNAME:TAG 107 | ``` 108 | 109 | ## 查看镜像详细信息 110 | ```shell 111 | docker inspect NAME:TAG 112 | ``` 113 | 114 | ## 查看镜像历史 115 | ```shell 116 | docker history NAME:TAG 117 | ``` 118 | 119 | ## 搜索镜像 120 | ```shell 121 | docker search NAME 122 | ``` 123 | 124 | ## 删除镜像 125 | ```shell 126 | docker rmi IMAGE 127 | ``` 128 | 129 | ## 删除容器 130 | ```shell 131 | docker rm 容器ID 132 | ``` 133 | ## 创建镜像 134 | 135 | ## 基本已有镜像的容器创建 136 | ``` 137 | docker commit [OPTIONS] CONTAINER [REPOSITOPUY[:TAG]] 138 | 139 | -a , --author="" // 作者信息 140 | -c , --change=[] 141 | -m , --message="" // 提交消息 142 | -p , --pause=true // 提交时暂停容器运行 143 | 144 | docker commit -m "added a new file " -a "crazy" 58fe3bd5b3e6 test:0.1 145 | ``` 146 | 147 | ## 基于本地模板导入 148 | ``` 149 | cat 本地包 | docker import - 导入的镜像名称 150 | ``` 151 | 152 | ## 导出镜像 153 | ``` 154 | docker save -o 导出文件名 导出的镜像 155 | ``` 156 | ## 载入镜像 157 | ``` 158 | docker load --input 本地镜像名称 159 | // 或者 160 | docker load < 本地镜像名称 161 | ``` 162 | ## 查看镜像详细信息 163 | ``` 164 | docker inspect NAME:TAG 165 | ``` 166 | 167 | # Docker相关的快捷命令 168 | 169 | ## 停用全部运行中的容器: 170 | ``` 171 | docker stop $(docker ps -q) 172 | ``` 173 | ## 删除全部容器 174 | ``` 175 | docker rm $(docker ps -aq) 176 | ``` 177 | 178 | ## 一条命令实现停用并删除容器 179 | ``` 180 | docker stop $(docker ps -q) & docker rm $(docker ps -aq) 181 | ``` 182 | ## 删除所有未打 tag 的镜像 183 | ``` 184 | docker rmi $(docker images -q | awk '/^/ { print $3 }') 185 | ``` 186 | ## 删除所有镜像 187 | ``` 188 | docker rmi $(docker images -q) 189 | ``` 190 | 191 | # 致谢 192 | 感谢看完我的这篇白话文,这是我学习docker的过程,都是一些关键的命令和概念,希望可以帮到你,有什么问题可以评论区讨论,互相学习 -------------------------------------------------------------------------------- /Shop/电商系统设计之商品 (中).md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/4153231859-5b41c3e1db55b_articlex.png) 2 | 3 | 4 | > 电商大伙每天都在用,类似某猫,某狗等。 5 | 电商系统设计看似复杂又很简单,看似简单又很复杂 6 | 本章适合初级工程师及中级工程师细看,大佬请随意 7 | 8 | # 前言 9 | 上一篇文章我们讲了关于电商SPU,SKU的概念,以及为何要设计自定义属性与自定义规格并解释了何时可以用到它们。我一直在说电商是一个既简单又复杂的东西,本章我们再一次深度解析电商系统商品设计的更多逻辑与实现。 10 | 11 | # 关联 12 | SPU对应多个SKU,SPU实际就是主商品表,类似于iphonex这款手机,而SKU则是这个商品绑定的规格表,类似与iphonex 红色款,iphonex 黑色款等。 13 | 14 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/22142436-5b459f819d80b_articlex.png) 15 | 16 | 而主表与规格表也关联了其他表 17 | 18 | ## 专辑 19 | 在淘宝的逻辑中,商家可为商品添加视频和图片,可为每个sku添加图片。我们称为专辑。将一组图片及视频类似歌手作家出专辑一样,绑定到商品表和sku表上 20 | 21 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3831208324-5b45a0a652673_articlex.png) 22 | 23 | ### product_album 24 | ``` 25 | CREATE TABLE `product_album` ( 26 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 27 | `product_id` int(11) NOT NULL COMMENT '商品编号', 28 | `name` varchar(25) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品名称', 29 | `url` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '图片地址', 30 | `size` int(11) DEFAULT NULL COMMENT '视频大小', 31 | `intro` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图片介绍', 32 | `sort` int(11) NOT NULL DEFAULT '999' COMMENT '排序', 33 | `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '图片状态', 34 | `state` tinyint(4) NOT NULL DEFAULT '0' COMMENT '资源类型 0=>图片 1=>视频', 35 | `created_at` timestamp NULL DEFAULT NULL, 36 | `updated_at` timestamp NULL DEFAULT NULL, 37 | PRIMARY KEY (`id`) 38 | ) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 39 | ``` 40 | ## 品牌 41 | 每个商品都归属与一个品牌,例如iphonex归属与苹果公司,小米8归属与小米公司一样。品牌无需关联到sku内,道理很简单,当前的sku是iphonex归属与苹果公司,自然而然iphonex下面的规格都属于苹果了。 42 | 43 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/984110366-5b45a172d80f5_articlex.png) 44 | 45 | 46 | ### product_brand 47 | ``` 48 | CREATE TABLE `product_brand` ( 49 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 50 | `product_category_id` int(11) NOT NULL COMMENT '商品类别编号', 51 | `name` varchar(25) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌名称', 52 | `image_url` varchar(125) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图片url', 53 | `sort` int(11) NOT NULL DEFAULT '999' COMMENT '排列次序', 54 | `status` tinyint(4) NOT NULL COMMENT '状态', 55 | `created_at` timestamp NULL DEFAULT NULL, 56 | `updated_at` timestamp NULL DEFAULT NULL, 57 | PRIMARY KEY (`id`), 58 | UNIQUE KEY `product_brand_name_unique` (`name`) 59 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 60 | ``` 61 | 62 | ## 类目 63 | 有时品牌不仅仅归属与一个类目,还是以iphonex举例,他是**一部手机**又是**苹果产品**但他又是一个**音乐播放器**。注意,这个时候不要将当前品牌绑定到三个类目上,如果你这样做了,未来的可维护性会很低。应该每个类目中绑定相同的品牌名称,你一定会问那这样数据垃圾不就产生了吗?我没有具体数据给你展现这样做的好处。 64 | 65 | 但从业务说起,现在我需要统计每个类目下商品的购买数去做用户画像,你时你要如何区分当前这个商品到底是哪个类目下呢?无法区分,因为你将品牌绑定到了3个类目下,不知用户到底是通过哪个类目点击进去购买的。 66 | 67 | 再者很多品牌公司不仅仅是做一个商品,类似索尼做mp3也做电视,手机,游戏机等。所以类目对应多个品牌,品牌应**对应**多个类目并非**关联**多个类目 68 | 69 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3774769229-5b45a4197772d_articlex.png) 70 | 71 | ### product_category 72 | 73 | ``` 74 | CREATE TABLE `product_category` ( 75 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 76 | `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类表', 77 | `pid` int(11) NOT NULL COMMENT '父分类编号', 78 | `cover` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '封面图', 79 | `index_block_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '首页块级状态 1=>显示', 80 | `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态 1=>正常', 81 | `sort` int(11) NOT NULL DEFAULT '999' COMMENT '排序', 82 | `created_at` timestamp NULL DEFAULT NULL, 83 | `updated_at` timestamp NULL DEFAULT NULL, 84 | PRIMARY KEY (`id`) 85 | ) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 86 | ``` 87 | # 致谢 88 | 下一节我们讲用户购买商品后的商品设计及后台操作的设计,一个好的程序员应该考虑到自己人如何去添加商品及管理它们。不能乐了用户苦了运营把?😄 89 | 90 | 谢谢你看到这里,希望我的文章能够帮助到你。有什么问题可以在评论区留言,我看到会第一时间回复。谢谢 -------------------------------------------------------------------------------- /Laravel/Laravel-Action 对代码的改造.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/3565416334-5b7686efb4abc_articlex.png) 2 | 3 | # 前言 4 | 以往写过俩篇文章 5 | [积德篇] 如何少写PHP "烂"代码 6 | [https://blog.fastrun.cn/2018/08/15/1-51/](https://blog.fastrun.cn/2018/08/15/1-51/) 7 | 举枪消灭"烂代码"的实战案例 8 | [https://blog.fastrun.cn/2018/08/15/1-51/](https://blog.fastrun.cn/2018/08/15/1-51/) 9 | 10 | 感觉文章中对Action的操作没有一个规范性的调用及编写。特此写了一个laravel-action包 11 | [https://github.com/CrazyCodes/Laravel-Action](https://github.com/CrazyCodes/Laravel-Action) 12 | 希望可以帮到你。 13 | 14 | # 安装 15 | 下载composer包: ```composer require crazycodes/laravel-action``` 16 | 17 | 注入提供者到 ```config/app.php``` 18 | 19 | ``` 20 | 'providers' => [ 21 | // [...] 22 | CrazyCodes\ActionServiceProvider::class, 23 | ], 24 | ``` 25 | 注册 ```Action``` facade: 26 | ``` 27 | 'aliases' => [ 28 | // [...] 29 | 'Action' => CrazyCodes\Facades\Action::class, 30 | ], 31 | ``` 32 | 发布配置文件 33 | ``` 34 | php artisan vendor:publish --provider=CrazyCodes\ActionServiceProvider 35 | ``` 36 | 配置项就一个 37 | ``` 38 | actionNamespace //设置你的action所在的命名空间 39 | ``` 40 | 41 | # 使用 42 | 继承Action方法获取规范的命名 43 | ``` 44 | namespace CrazyCodes\Action; 45 | 46 | class CreateUser extends Action 47 | { 48 | 49 | } 50 | ``` 51 | 52 | 继承的Action准备了两个方法 53 | 54 | ## before 55 | ``` 56 | public function before($request) 57 | { 58 | return $request; 59 | } 60 | ``` 61 | Action被调用的同时会直接调用before方法执行。 62 | 63 | ## after 64 | ``` 65 | public function after($request) 66 | { 67 | return []; 68 | } 69 | 70 | ``` 71 | 可以选择不声明after方法。after主要用于调用其他Action 72 | 73 | ## 成员变量 74 | ``` 75 | public $beforeResultName = 'beforeResult'; 76 | public $afterResultName = 'afterResult'; 77 | ``` 78 | 用于获取返回的结果 79 | 80 | # 调用 81 | 可以通过Facade调用 82 | ``` 83 | Action::use('YourAction',发送的参数); 84 | ``` 85 | 或者使用全局函数 86 | ``` 87 | laravel_action('YourAction',发送的参数); 88 | ``` 89 | 90 | # 获取结果 91 | 得到的结果默认是对象。可以转换格式 92 | ``` 93 | function toJson(); 94 | function toArray(); 95 | ``` 96 | 结果展示 97 | 98 | Array 99 | ``` 100 | array:2 [ 101 | "beforeResult" => array:1 [ 102 | 0 => "aaa" 103 | ] 104 | "afterResult" => [] 105 | ] 106 | ``` 107 | JSON 108 | ``` 109 | {"beforeResult":["aaa"],"afterResult":[]} 110 | ``` 111 | # Demo 112 | 依旧以创建用户为例 113 | ## UserController 114 | ``` 115 | 'test', 131 | 'password' => 'test', 132 | ]; 133 | 134 | $result = Action::use ('CreateUser', $request); 135 | 136 | //全局方法 137 | //laravel_action ('CreateUser', $request); 138 | 139 | 140 | // return $result->toArray(); 141 | return $result->toJson (); 142 | } 143 | } 144 | ``` 145 | ## CreateUser 146 | ``` 147 | 'success']; 165 | } 166 | 167 | public function after($request) 168 | { 169 | var_dump ($request); 170 | 171 | return Action::use ('CreateWallet', $request); 172 | } 173 | } 174 | ``` 175 | ## CreateWallet 176 | ``` 177 | 'success']; 195 | } 196 | 197 | } 198 | ``` 199 | 200 | # 致谢 201 | 感谢你看到这里,希望这篇文章让你的代码更优雅。谢谢 -------------------------------------------------------------------------------- /Laravel/Laravel源码解析之从入口开始.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/3276665162-5ba45dfac43fc_articlex.png) 2 | 3 | # 前言 4 | 提升能力的方法并非使用更多工具,而是解刨自己所使用的工具。今天我们从Laravel启动的第一步开始讲起。 5 | # 入口文件 6 | laravel是单入口框架,所有请求必将经过index.php 7 | ``` 8 | define('LARAVEL_START', microtime(true)); // 获取启动时间 9 | ``` 10 | 使用composer是现代PHP的标志 11 | ``` 12 | require __DIR__.'/../vendor/autoload.php'; // 加载composer -> autoload.php 13 | ``` 14 | 加载启动文件 15 | ``` 16 | $app = require_once __DIR__.'/../bootstrap/app.php'; 17 | ``` 18 | 获取```$app```是laravel启动的关键,也可以说$app是用于启动laravel内核的钥匙🔑。随后就是加载内核,载入服务提供者、门面所映射的实体类,中间件,最后到接收http请求并返回结果。 19 | ``` 20 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); // 加载核心类 21 | 22 | $response = $kernel->handle( 23 | $request = Illuminate\Http\Request::capture() 24 | ); 25 | 26 | $response->send(); 27 | 28 | $kernel->terminate($request, $response); 29 | ``` 30 | 看似短短的4行代码,这则是laravel的优雅之处。我们开始深层次解刨。 31 | # bootstrap\\app.php 32 | 这个启动文件也可以看作是一个服务提供者,不过他并没有boot,register方法。因为入口文件直接加载他,所有这些没必要的方法就不存在了。 33 | 34 | 作为启动文件,首页则是加载框架所有必须的要要件,例如 35 | - registerBaseBindings 36 | - registerBaseServiceProviders 37 | - registerCoreContainerAliases, 38 | 这其中包括了很多基础性的方法和类,例如 39 | - db ```[\Illuminate\Database\DatabaseManager::class]``` 40 | - auth ``` [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class] ``` 41 | - log ``` [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class] ``` 42 | - queue ``` [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class] ``` 43 | - redis ``` [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class] ``` 44 | - 等等 ``` ... ``` 45 | 而$app这个在服务提供者的核心变量则就是Application实例化所得,而你在服务提供者内使用的make,bind,singleton来自他的父类Container,都说容器是laravel的核心概念。这块的概念后续我们会详细的讲解。 46 | ``` 47 | $app = new Illuminate\Foundation\Application( 48 | realpath(__DIR__.'/../') 49 | ); 50 | ``` 51 | 上面我们已经获得$app的实例化了,现在通过$app来注册核心类、异常类,并将$app返回给```index.php``` 52 | ``` 53 | $app->singleton( 54 | Illuminate\Contracts\Http\Kernel::class, 55 | App\Http\Kernel::class 56 | ); 57 | 58 | $app->singleton( 59 | Illuminate\Contracts\Console\Kernel::class, 60 | App\Console\Kernel::class 61 | ); 62 | 63 | $app->singleton( 64 | Illuminate\Contracts\Debug\ExceptionHandler::class, 65 | App\Exceptions\Handler::class 66 | ); 67 | ``` 68 | # App\\Http\\Kernel 69 | 核心类了所有的 70 | - 系统中间件 71 | - 群组中间件 72 | - 路由中间件 73 | 当然你需要使用中间件也是在这个类中加载,是经常被使用的一个文件。 74 | ``` 75 | protected $middleware = [ 76 | \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, 77 | \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, 78 | \App\Http\Middleware\TrimStrings::class, 79 | \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, 80 | \App\Http\Middleware\TrustProxies::class, 81 | ]; 82 | 83 | /** 84 | * The application's route middleware groups. 85 | * 86 | * @var array 87 | */ 88 | protected $middlewareGroups = [ 89 | 'web' => [ 90 | \App\Http\Middleware\EncryptCookies::class, 91 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 92 | \Illuminate\Session\Middleware\StartSession::class, 93 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 94 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 95 | \App\Http\Middleware\VerifyCsrfToken::class, 96 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 97 | ], 98 | 99 | 'api' => [ 100 | 'throttle:60,1', 101 | 'bindings', 102 | ], 103 | ]; 104 | ``` 105 | 这个核心类继承自他的父类```Illuminate\Foundation\Http\Kernel::class```,核心类做了很多事情,它会将所有的中间件全部存储到一个指定的数组,方便内核调用及其他类调用。 106 | ``` 107 | namespace App\Http; 108 | 109 | use App\Api\Middleware\VerifyApiToken; 110 | use Illuminate\Foundation\Http\Kernel as HttpKernel; 111 | 112 | class Kernel extends HttpKernel 113 | ``` 114 | # 回到起点 115 | Laravel的启动经历了很繁琐的一个过程。这也是Laravel优雅的关键点。 116 | ``` 117 | $response = $kernel->handle( 118 | $request = Illuminate\Http\Request::capture() 119 | ); 120 | 121 | $response->send(); 122 | 123 | $kernel->terminate($request, $response); 124 | ``` 125 | 将请求传入则完成了整个laravel的启动,至于结果的返回则有开发者自行通过控制器或其他可访问类返回。 126 | 127 | # 致谢 128 | 感谢你看到这里,本篇文章源码解析靠个人理解。如有出入请拍砖。 129 | 130 | 希望本篇文章可以帮到你。谢谢 -------------------------------------------------------------------------------- /Shop/通用系统设计之优惠卷.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/12/625836230-5c1c5be492afc_articlex.png) 2 | 3 | # 前言 4 | 本应该继续连载手撸框架系列文章的。但最近有一个需求 -> 优惠卷,之前很多朋友让我出一篇优惠卷相关的文章。这不,本章应了大伙的愿。开始我自己的表演 🔥🔥 5 | 6 | 额,这里还要插一句,有很多新人感觉在使用框架的过程中根本用不到PHP的很多概念,例如abstract,final 部分人感觉protected,private 都没有太大用处。更别提interface在框架中的使用了,感觉好无用处的举爪~ 7 | 8 | # 策略模式 9 | 优惠卷的存在到消亡至少要经历三个步骤(创建->使用->失效),以下为优惠卷完整生命周期图, 10 | 11 | 12 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/12/1364859415-5c1c545a1ec8c_articlex.png) 13 | 14 | 15 | 优惠卷有几百种几千种的优惠(骗人)方式(姿势),结合PHP代码来解决优惠卷应如何创建更合适,首先先创建一个类作为优惠卷的模版 16 | 17 | ``` 18 | class UserCouponTem 19 | { 20 | } 21 | ``` 22 | 这个模版则是一个树根,未来所有优惠卷都要通过这个根去扩展,接下来创建一系列的优惠卷参数,例如与设计数据表一样,如下所示,通过成员变量的方式,束缚了优惠卷的具体字段。 23 | ``` 24 | /** 25 | * @var $couponName 26 | * @content 优惠券名称 27 | */ 28 | public $couponName; 29 | 30 | /** 31 | * @var $alidityv 32 | * @content 有效期 33 | */ 34 | public $alidityv; 35 | 36 | /** 37 | * @var $userId 38 | * @content 绑定的用户编码 39 | */ 40 | public $userId; 41 | 42 | /** 43 | * @var $price 44 | * @content 抵扣金额 45 | */ 46 | public $price; 47 | 48 | /** 49 | * @var $type 50 | * @content 类型 0 通用红包 1 查看扩展字段 51 | */ 52 | public $type; 53 | 54 | /** 55 | * @var $extend 56 | * @content 扩展字段 57 | */ 58 | public $extend; 59 | 60 | /** 61 | * @var $numbers 62 | * @content 卷号 63 | */ 64 | public $number; 65 | 66 | /** 67 | * @var $content 68 | * @content 卷内容 69 | */ 70 | public $content; 71 | ``` 72 | 优惠卷的模版创建完成后,接下来需要创建两个方法,第一个为服务提供者,规定每个创建优惠卷的类都必须存在create方法,没错,这是在写一个策略模式。 73 | ``` 74 | interface CouponInterface 75 | { 76 | public function create($userId, $price); 77 | } 78 | 79 | public function provider(CouponInterface $coupon, $userId, $price) 80 | { 81 | return $coupon->create($userId, $price); 82 | } 83 | ``` 84 | 最后是一个消费者 85 | ``` 86 | public function consumer($number) 87 | { 88 | // $number 是卷号,这里一般都是操作redis,mysql的统一逻辑。 89 | } 90 | ``` 91 | 写好了一个简单的策略模式,那开始写一个策略吧。 92 | 93 | # 使用策略 94 | 下方代码创建了一个通用红包。继承模版类中的字段并且去实现接口create方法 95 | 96 | ``` 97 | class Current extends UserCouponTem implements CouponInterface 98 | { 99 | public function create($userId, $price) 100 | { 101 | $this->couponName = "通用红包"; 102 | $this->alidityv = "2019-01-09"; 103 | $this->content = "这是一个通用红包"; 104 | $this->userId = $userId; 105 | $this->price = $price; 106 | $this->type = 0; 107 | $this->extend = []; 108 | $this->number = '123456'; 109 | 110 | return $this; 111 | } 112 | } 113 | ``` 114 | 115 | 最后通过下方代码创建一个通用红包,获得完整的一个优惠卷实例,最后将参数插入到数据库与用户表绑定则完成了一个基本的 116 | 117 | ``` 118 | $userCouponTem = new UserCouponTem(); 119 | $current = $userCouponTem->provider(new Current(), $this->request->user_id, 120 | $this->request->price); 121 | ``` 122 | 123 | # 设计思想 124 | 部分人会怀疑这种设计是多此一举,直接将逻辑设计到数据表不就OK了嘛?我们为何还要通过模版类,接口,服务提供者、服务容器去返回一个优惠卷实例? 125 | 126 | 试想不可能一次性将所有优惠卷的类型全部想到并且设计出来,数据表结构也不能频繁去更改。如何让一批代码适应整个业务并且对未来业务可扩展?这样的话则不能把所有逻辑存放到数据表中。这样做可能有以下几点好处 127 | 128 | - 可扩展性强,能够应对各种优惠卷的表达方式 129 | - 可维护性强,如果有新类型的业务可直接通过服务容器注入 130 | - 代码优雅,便于阅读,无论是新入职员工还是他人都很容易读写优惠卷的代码(比较优惠卷的业务实际很复杂) 131 | 132 | 上述实际就是Laravel的服务提供者、服务容器的概念,不明白的童鞋可去看文档并参考本例子。 133 | 134 | # 数据结构 135 | 仅供参考(不是太认真的设计) 136 | 用户优惠卷表 137 | ``` 138 | CREATE TABLE `member_coupon` ( 139 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 140 | `user_id` int(11) NOT NULL COMMENT '用户编码', 141 | `number` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '卷号', 142 | `content` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '卷内容', 143 | `price` decimal(8,2) NOT NULL COMMENT '金额', 144 | `alidityv` datetime NOT NULL COMMENT '到期时间', 145 | `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '-1 过期 0 未使用 1 已使用', 146 | `use_date` int(11) NOT NULL DEFAULT '0' COMMENT '使用时间', 147 | `created_at` timestamp NULL DEFAULT NULL, 148 | `updated_at` timestamp NULL DEFAULT NULL, 149 | PRIMARY KEY (`id`) 150 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 151 | ``` 152 | # 优惠卷记录表 153 | ``` 154 | CREATE TABLE `coupon_record` ( 155 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 156 | `user_id` int(11) NOT NULL COMMENT '用户编码', 157 | `number` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '卷号', 158 | `price` decimal(8,2) NOT NULL COMMENT '金额', 159 | `json_content` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '具体json信息', 160 | `created_at` timestamp NULL DEFAULT NULL, 161 | `updated_at` timestamp NULL DEFAULT NULL, 162 | PRIMARY KEY (`id`) 163 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 164 | ``` 165 | 166 | # 致谢 167 | 希望每篇文章并不是仅仅讲一件问题,我会把问题的扩展思想一并告诉大家,希望可以帮助到你。谢谢 -------------------------------------------------------------------------------- /论某教育机构考试系统设计.md: -------------------------------------------------------------------------------- 1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2888525365-5bd67ae95ffa3_articlex.png) 2 | 3 | # 前言 4 | 近期在做一套答题系统,参考了某教育机构的设计。本章跟大家聊聊考试系统中的核心 - 如何考试? 5 | 6 | 简单点说,所谓考试系统就是答题系统,通过答题完成进行判分后返回其答题结果即完成整个流程。 7 | 8 | 当然过程中有些数据需要存储,有些则可在后期查询计算(个人理解),如有误导请速喷 9 | 10 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/1689458329-5bd67d767e970_articlex.png) 11 | 12 | 如上图所示,一套试卷有N道试题,每道试题又是由题干与选项、答案组成,这样才汇聚了完整的一套答题(考试)系统。 13 | 14 | # 试卷 15 | 一次考试(问卷)则使用一套试卷,考试(问卷)与考试则为一对一的关系,而试卷与考试(问卷)则是多对多的关系 16 | ``` 17 | CREATE TABLE `company_paper` ( 18 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 19 | `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '试卷名称', 20 | `created_at` timestamp NULL DEFAULT NULL, 21 | `updated_at` timestamp NULL DEFAULT NULL, 22 | PRIMARY KEY (`id`) 23 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 24 | ``` 25 | 这只是一份简单的设计,具体要看实际需求,如果是考试相关需求需要添加 26 | - 考试时间 (time) 27 | - 考试及格分数 (grade) 28 | 29 | 而类似字段实际不应加在试卷上,上面已经说过了试卷与考试是多对多的关系,所以上述字段应加入在考试表中。 30 | ``` 31 | CREATE TABLE `company_examine` ( 32 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 33 | `paper_id` int(11) NOT NULL COMMENT '试卷编码', 34 | `start_time` int(11) NOT NULL COMMENT '开考时间', 35 | `time_limit` int(11) NOT NULL COMMENT '限时', 36 | `score` double NOT NULL COMMENT '通过分数', 37 | `created_at` timestamp NULL DEFAULT NULL, 38 | `updated_at` timestamp NULL DEFAULT NULL, 39 | PRIMARY KEY (`id`) 40 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 41 | ``` 42 | 上述为考试表,考试表通过paper_id绑定对应试卷。 43 | 44 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2532655738-5bd6a326e06a1_articlex.png) 45 | 46 | # 题库 47 | 题库与试卷没有关系,是以试题为对象的分类管理罢了。将试题归类后在添加试卷动作时选择试题比较方便。 48 | 49 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/1907352148-5bd6a466124d9_articlex.png) 50 | 当然,他也是一个多对多的关系。 51 | ``` 52 | CREATE TABLE `company_question_database` ( 53 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 54 | `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '题库名称', 55 | `created_at` timestamp NULL DEFAULT NULL, 56 | `updated_at` timestamp NULL DEFAULT NULL, 57 | PRIMARY KEY (`id`) 58 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 59 | ``` 60 | 如上述表结构,题库即为分类,所以没有过度设计。 61 | 62 | # 试题 63 | 试题应该算是整个系统设计过程中比较繁琐的一部分了。先来看下数据表 64 | ``` 65 | CREATE TABLE `company_question` ( 66 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 67 | `database_id` int(11) NOT NULL COMMENT '所属题库', 68 | `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '题目', 69 | `option` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '选项', 70 | `answer` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '答案', 71 | `type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '类型 0单选 1多选', 72 | `created_at` timestamp NULL DEFAULT NULL, 73 | `updated_at` timestamp NULL DEFAULT NULL, 74 | PRIMARY KEY (`id`) 75 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 76 | ``` 77 | 首先按照传统考试的试卷将试题分为三大部分 78 | - 题干(题目) 79 | - 选项 80 | - 答案 81 | 82 | 这三大部分汇总为一道题,这里的option与answer选择使用序列化方式去存储 83 | 84 | ``` 85 | // 选项 86 | >>> serialize (["A"=>"选项A","B"=>"选项B"]) 87 | => "a:2:{s:1:"A";s:7:"选项A";s:1:"B";s:7:"选项B";}" 88 | // 答案 89 | >>> serialize (["A"]) 90 | => "a:1:{i:0;s:1:"A";}" 91 | ``` 92 | 没有使用json方式存储有俩点原因,自认为mysql对json的查询做的不够完善,sql写的太复杂,其后者则是扩展性不够强,低版本不兼容。 93 | 94 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2300335963-5bd6a6e250845_articlex.png) 95 | 96 | # 判分 97 | 到判分这步算是整个考试完成了80%,那是不可能的,实际完成了不到50%的功能。依旧引用电商相关文章的那句话 98 | > 把能存储的全部存起来 99 | 100 | 判分这里是这样做的 101 | ``` 102 | CREATE TABLE `company_user_paper` ( 103 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 104 | `user_id` int(11) NOT NULL COMMENT '用户编码', 105 | `paper_id` int(11) NOT NULL COMMENT '试卷编码', 106 | `answer` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '答案', 107 | `score` double(8,2) NOT NULL DEFAULT '0.00' COMMENT '得分', 108 | `correct` double(8,2) NOT NULL DEFAULT '0.00' COMMENT '正确率', 109 | `date_length` int(11) NOT NULL DEFAULT '0' COMMENT '考试用时长', 110 | `created_at` timestamp NULL DEFAULT NULL, 111 | `updated_at` timestamp NULL DEFAULT NULL, 112 | PRIMARY KEY (`id`) 113 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 114 | ``` 115 | 见上表,将用户每次考试的动作细节全部记录,方便查询,答案这块依旧使用的序列化的方式。在检查答案时,直接反序列化计算数组差集既完成判分,获取交集也是没问题的。 116 | ``` 117 | $answerArr = unserialize($answer) 118 | $successAnswerArr = unserialize($successAnswer) 119 | array_diff($answerArr,$successAnswerArr) 120 | ``` 121 | 122 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/1471939022-5bd6a9141fc07_articlex.png) 123 | 124 | 考试毕竟不是一道题那么简单,你可以选择迭代去完成。如果题目数量比较大。或者用户相对集中,建议还是使用队列去异步完成判分操作并通过socket或者其他方式通知到客户端(web端)更保险一些。 125 | 126 | # 致谢 127 | 本章的内容到此结束,感谢你看到这里,希望本篇可以帮到你。谢谢! -------------------------------------------------------------------------------- /PHP/来!狂撸一款PHP现代化框架 (准备工作).md: -------------------------------------------------------------------------------- 1 | ![![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/12/4071558190-5c089723b0c8c_articlex.png)](https://resources.blog.fastrun.cn/wp-content/uploads/2018/12/4149909193-5c0898e0c4a99_articlex.png) 2 | 3 | 4 | 5 | # 前言 6 | 从本章开始,我们继续造轮子,去完成一款类似于Laravel的现代化PHP框架,为什么说是现代化?因为他必须具备一下几点 7 | - 遵守PSR-4编码规范 8 | - 使用Composer进行包管理 9 | - 标准的HTTP请求方式 10 | - 优雅的使用设计模式 11 | 开始我们无需关心性能问题,先考虑框架具体需要实现哪些功能,这与实现业务就大不相同了,来!开始我的表演。 12 | 13 | # 前期 14 | 做任何一件事情都要有个前期准备工作。 15 | 1. 作为PSR-4的规定,我们命名空间得有一个祖宗名字,这里我叫他神圣的 《z_framework》 16 | 2. 至少需要一个GITHUB库来存储这个项目 https://github.com/CrazyCodes/z_framework 17 | 18 | 3. 创建一个composer.json文件用于进行包管理,灰常简单,phpunit搞进来。通过psr-4加载个项目命名 19 | 20 | ``` 21 | { 22 | "name": "z framework", 23 | "require-dev": { 24 | "phpunit/phpunit": "^7.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Zero\\": "src/Zero", 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Zero\\Tests\\": "tests/" 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | 最后我们就需要考虑下目录的结构及其我们第一步要完成的功能,核心的结构(这里并非只的项目结构哦。是框架的核心结构)暂且是这样 40 | 41 | - src 42 | - Zero 43 | - Config // 可能存放一些配置文件的解析器 44 | - Container // 容器的解析器 45 | - Http // 请求处理的一些工具 46 | - Routes // 路由处理的一些功能 47 | - Bootstrap.php // 这可能是一个启动脚本 48 | - Zero.php // 可能是核心的入口文件 49 | - tests // 测试目录 50 | - .gitignore 51 | - composer.json 52 | - LICENSE 53 | - README.md 54 | 55 | 56 | # 路由 57 | 还记得第一次使用Laravel时我们第一步做的事情吗?是的,去研究路由,所以我们把路由作为框架的第一步。在研究路由前,我们要知道 58 | ``` 59 | http://www.domain.com/user/create 60 | ``` 61 | 是如何实现的,php默认是必须请求index.php或者default.php的,上述链接实际隐藏了index.php或default.php ,这是Nginx等服务代理帮我们做到的优雅的链接,具体配置如下,实际与Laravel官方提供无差别 62 | ``` 63 | server { 64 | listen 80; 65 | server_name www.zf.com; 66 | root /mnt/app/z_framework/server/public; 67 | index index.php index.html index.htm; 68 | 69 | location / { 70 | try_files $uri $uri/ /index.php?$query_string; 71 | } 72 | 73 | location ~ \.php$ { 74 | fastcgi_pass php71:9000; 75 | fastcgi_index index.php; 76 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 77 | include fastcgi_params; 78 | } 79 | } 80 | ``` 81 | 通过 82 | ``` 83 | try_files $uri $uri/ /index.php?$query_string; 84 | ``` 85 | 去解析请求,通过上述可以得出 86 | ``` 87 | http://www.domain.com/user/create 88 | ======= 89 | http://www.domain.com/index.php?user/create 90 | ``` 91 | 好了,明白了其中奥秘后,我们开始路由的编写,在src/Routes/Route.php 92 | ``` 93 | namespace Zero\Routes; 94 | 95 | class Route 96 | { 97 | } 98 | ``` 99 | # 实现 100 | 首先我们先创建一个简单的接口文件 101 | src/Routes/RouteInterface.php 102 | ``` 103 | namespace Zero\Routes; 104 | 105 | interface RouteInterface 106 | { 107 | public function Get($url, $callFile); 108 | 109 | public function Post($url, $callFile); 110 | 111 | public function Put($url, $callFile); 112 | 113 | public function Delete($url, $callFile); 114 | } 115 | ``` 116 | 从Get请求开始 117 | ``` 118 | namespace Zero\Routes; 119 | 120 | class Route implements RouteInterface 121 | { 122 | public function Get($url, $callFile) 123 | { 124 | 125 | } 126 | } 127 | ``` 128 | 最后实现Get代码块 129 | ``` 130 | if (parent::isRequestMethod("GET")) { // 判读请求方式 131 | 132 | if (is_callable($callFile)) { // 判断是否是匿名函数 133 | return $callFile(); 134 | } 135 | 136 | if ($breakUpString = parent::breakUpString($callFile)) { // 获取Get解析。既/user/create 137 | header('HTTP/1.1 404 Not Found'); 138 | } 139 | 140 | try { 141 | // 通过反射类获取对象 $breakUpString[0] = user 142 | $reflectionClass = new \ReflectionClass('App\\Controllers\\' . $breakUpString[0]); 143 | // 实例化对象 144 | $newInstance = $reflectionClass->newInstance(); 145 | // 获取对象中的指定方法,$breakUpString[1] = create 146 | call_user_func([ 147 | $newInstance, 148 | $breakUpString[1], 149 | ], []); 150 | } catch (\ReflectionException $e) { 151 | header('HTTP/1.1 404 Not Found'); 152 | } 153 | } else { 154 | header('HTTP/1.1 404 Not Found'); 155 | } 156 | 157 | return ""; 158 | ``` 159 | 如果你想测试上述代码,可使用phpunit,或者傻大粗的方式,这里便于理解使用傻大粗的方式 160 | 161 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/12/4071558190-5c089723b0c8c_articlex.png) 162 | 163 | 164 | 创建一个目录,随后按照Laravel的目录形式创建几个目录, 165 | ``` 166 | get(); 50 | ``` 51 | 这个操作首先经过laravel的门面指向文件,不过它并不在 app.php 中,而是通过内核直接加载,它在 52 | ``` 53 | Illuminate\Foundation\Application -> registerCoreContainerAliases() 54 | ``` 55 | 被注册。门面直接调用 ``` Illuminate\Database\DatabaseManager ``` 类。 56 | ``` 57 | public function registerCoreContainerAliases() 58 | { 59 | foreach ([ 60 | ... 61 | 'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class], 62 | 'db' => [\Illuminate\Database\DatabaseManager::class], 63 | 'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class], 64 | 'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class], 65 | 'files' => [\Illuminate\Filesystem\Filesystem::class], 66 | .... 67 | ) 68 | } 69 | ``` 70 | ``` Illuminate\Database\DatabaseManager ```内并没有太多的代码,大多都是处理数据库链接。当你使用 ``` DB::table()```时,会通过 71 | ``` 72 | public function __call($method, $parameters) 73 | { 74 | return $this->connection()->$method(...$parameters); 75 | } 76 | ``` 77 | 转发,调用的是 ``` Illuminate\Database\Connection ``` ,用户处理 ```table() ```方法,随后会通过 ``` table() ``` 方法指向 ``` Illuminate\Database\Query ``` 类,开头我们讲过这个类了,这里就不多说了,随后就是各种sql的拼接->执行sql->结束战斗 78 | 79 | 80 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/1413934109-5baf39bfab3cb_articlex.png) 81 | 82 | # Eloquent ORM 83 | Eloquent ORM 与DB facade 类似,首先每个 Eloquent ORM 都需要继承父类 ``` Illuminate\Database\Eloquent\Model ``` 84 | 你大概会这样写 85 | ``` 86 | User::find(1) 87 | ``` 88 | 父类是不存在这个方法的,它会通过 89 | ``` 90 | public static function __callStatic($method, $parameters) 91 | { 92 | return (new static)->$method(...$parameters); 93 | } 94 | ``` 95 | 去转发请求调用。同理 96 | ``` 97 | User::get() 98 | ``` 99 | 则是通过 100 | ``` 101 | public function __call($method, $parameters) 102 | { 103 | if (in_array($method, ['increment', 'decrement'])) { 104 | return $this->$method(...$parameters); 105 | } 106 | 107 | return $this->newQuery()->$method(...$parameters); 108 | } 109 | ``` 110 | 去调用,这个方法最终以 ``` new Builder() ``` 而告终, 111 | ``` 112 | public function newEloquentBuilder($query) 113 | { 114 | return new Builder($query); 115 | } 116 | ``` 117 | 最后我们到了 ``` Illuminate\Database\Eloquent\Builder ``` 文件下,这个类中涵盖了ORM的基本操作,例如find , findOrFail 等等。如果你在代码用到了get方法,抱歉,这里没有,它依旧会通过__call 方法将你的请求转发到 ``` Illuminate\Database\Query\Builder ``` 类中 118 | ``` 119 | $this->query->{$method}(...$parameters); 120 | ``` 121 | 122 | 至此就完成了整个数据操作。 123 | 124 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2019/01/695898890-5baf3c86c85c7_articlex.png) 125 | 126 | 127 | # 致谢 128 | 感谢你看到这里,希望本篇文章可以帮助到你,谢谢 -------------------------------------------------------------------------------- /PHP/不一样的PHP基础知识汇总.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/1508779647-5b724c892b362_articlex.png) 2 | 3 | # 前言 4 | 整理了一些关于PHP相关的基础。请大家随意摘看。 5 | # 通信协议 6 | > 网络通信协议是一种网络通用语言,为连接不同操作系统和不同硬件体系结构的互联网络引提供通信支持,是一种网络通用语言。 7 | 8 | ## 应用层 9 | | 名称 | 注释 | 10 | | -- | -- | 11 | | HTTP | Hypertext Transfer Protocol 超文本传输协议,显示网页 | 12 | | DNS | Domain Name System 域名系统 | 13 | | FTP | File Transfer Protocol 文件传输协议 | 14 | | SFTP | SSH File Transfer Protocol 安全文件传送协议| 15 | | SSH | Secure Shell | 16 | 17 | ## 通信层 18 | | 名称 | 注释 | 19 | | -- | -- | 20 | | TCP | Transmission Control Protocol 三次握手传输协议 | 21 | | UDP | | 22 | ## 网络层 23 | | 名称 | 注释 | 24 | | -- | -- | 25 | | IP | Internet Protocol | 26 | | ICMP | Internet Control Message Protocol,主要用于路由发送错误报告 | 27 | 28 | ## HTTP 29 | HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写。它的发展是万维网协会(World Wide Web Consortium)和Internet工作小组IETF(Internet Engineering Task Force)合作的结果,(他们)最终发布了一系列的RFC,RFC 1945定义了HTTP/1.0版本。其中最著名的就是RFC 2616。RFC 2616定义了今天普遍使用的一个版本——HTTP 1.1。 30 | 31 | HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。 32 | 33 | HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。 34 | 35 | ### 在TCP/IP协议栈中的位置 36 | HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。如下图所示 37 | 38 | 39 | ![clipboard.png](/img/bVLY09) 40 | 41 | 默认HTTP的端口号为80,HTTPS的端口号为443。 42 | 43 | ### HTTP的请求响应模型 44 | HTTP协议永远都是客户端发起请求,服务器回送响应。见下图 45 | 46 | ![clipboard.png](/img/bVbfiMy) 47 | 48 | 这样就限制了使用HTTP协议,无法实现在客户端没有发起请求的时候,服务器将消息推送给客户端。 49 | HTTP协议是一个无状态的协议,同一个客户端的这次请求和上次请求是没有对应关系。 50 | 51 | ### HTTP Request 52 | 客户端发送一个HTTP请求到服务器的请求消息包括以下格式 53 | - 请求行(request line) 54 | - 请求头部(header) 55 | - 空行和请求数据四个部分组成。 56 | 57 | Get请求例子 58 | ``` 59 | GET /562f25980001b1b106000338.jpg HTTP/1.1Host img.mukewang.comUser-Agent Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36Accept image/webp,image/*,*/*;q=0.8Referer http://www.imooc.com/Accept-Encoding gzip, deflate, sdchAccept-Language zh-CN,zh;q=0.8 60 | ``` 61 | POST请求例子 62 | ``` 63 | POST / HTTP1.1Host:www.wrox.comUser-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)Content-Type:application/x-www-form-urlencodedContent-Length:40Connection: Keep-Alivename=Professional%20Ajax&publisher=Wiley 64 | ``` 65 | 第一部分:请求行,第一行显示请求类型,以及http1.1版本。 66 | 第二部分:请求头部,第二行至第六行。 67 | 第三部分:空行,第七行的空行。 68 | 第四部分:请求数据,第八行。 69 | 70 | ### HTTP Response 71 | 一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息。 72 | HTTP响应也由四个部分组成 73 | - 状态行 74 | - 消息报头 75 | - 空行和响应正文。 76 | 77 | ``` 78 | HTTP/1.1 200 OKDate: Fri, 22 May 2009 06:07:21 GMTContent-Type: text/html; charset=UTF-8 79 | ``` 80 | 第一部分\状态行 : 由HTTP协议版本号、状态码、状态消息三部分组成。 81 | 第二部分\消息报头 : 用来说明客户端要使用的一些附加信息 82 | 第三部分\空行 : 消息报头后面的空行是必须的 83 | 第四部分\响应正文 : 服务器返回给客户端的文本信息。 84 | 85 | ### HTTP 状态码 86 | 状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别 87 | | 状态 | 注释 | 88 | | -- | -- | 89 | | 1xx | 指示信息--表示请求已接收,继续处理 | 90 | | 2xx | 成功--表示请求已被成功接收、理解、接受 | 91 | | 3xx | 重定向--要完成请求必须进行更进一步的操作 | 92 | | 4xx | 客户端错误--请求有语法错误或请求无法实现 | 93 | | 5xx | 服务器端错误--服务器未能实现合法的请求 | 94 | 95 | # 进程与线程 96 | 进程的概念是操作系统的结构的基础。Multics的设计者在20世纪60年代首次使用了这个技术词语,它比作业更通用一些。关于进程的定义,如下所示 97 | - 一个正在执行的程序。 98 | - 计算机中正在运行的程序的一个实例。 99 | - 可以分配给处理器并由处理器执行的一个实体。 100 | - 由单一的顺序的执行线程、一个当前状态和一组相关的系统资源所描述的活动单元。 101 | 102 | ## 为什么设计了进程? 103 | 设计出一个能够协调各种不同活动的系统软件是非常困难的。 104 | 105 | 在任何时刻都有许多作业在运行中,每个作业都包括要求按照顺序执行的很多步骤,因此分析时间的序列组合是不可的。由于缺乏能够在所有活动中进行协调和合作的系统级的方法,程序员只能基于他们对操作系统所控制的环境的理解,采用自己的特殊方法。然而这种方法是很脆弱的,尤其对于一些程序设计中的小错误,因为这些错误只有在很少见的时间序列发生时才会出现。 106 | 107 | 由于需要从应用程序软件错误和硬件错误中区分出这些错误,因而诊断工作是很困难的。及时检测出错误,也很难确定原因,因为很难在线错误产生的精确场景。一般而言,产生这类错误的4个主要原因如下: 108 | - 不正确同步 109 | - 失败互斥。 110 | - 不确定的程序操作 111 | - 死锁 112 | 113 | 解决这些问题需要一种系统级别的方法监控处理器中不同程序的执行。进程的概念为此提供了基础。 114 | 115 | 因此进程可以看做是由三部分组成的 116 | 117 | - 一段可以执行的程序 118 | - 程序所需要的相关数据 119 | - 程序的执行上下文 120 | 121 | ## 进程的创建 122 | 123 | 传统地,操作系统创建进程的方式对用户和应用程序都是透明的,这在当代操作系统中也很普遍。但是允许一个进程引发另一个进程的创建将是很有用的。 124 | 125 | 例如一个程序进程可以产生另一个进程,以接受应用程序产生的数据,并将数据组织成适合以后分析的格式。新进程与应用程序并行的运行,并当得到新的数据时被激活。 126 | 127 | 这个方案对于构造应用程序是非常有用的,例如,服务器进程(如打印服务器、文件服务器)可以为它处理的每个请求产生一个新进程。当操作系统为另一个进程的显式请求产生一个新进程时,这个动作称为进程派生。 128 | 129 | 当一个进程派生另一个进程时,前一个称作父进程,被派生的进程称作子进程。在典型的情况下,相关进程需要像话之间通信和合作。对程序员来说,合作是一个非常困难的任务。 130 | 131 | ## 什么是线程 132 | > 线程是进程的一个执行流,线程不能分配系统资源,它是进程的一部分,比进程更小的独立运行的单位 133 | 134 | ## 进程和线程的关系 135 | 进程就像地主,有土地(系统资源),线程就像佃户(线程,执行种地流程)。每个地主(进程)只要有一个干活的佃户(线程)。 136 | 137 | 进程-资源分配的最小单位,相对健壮,崩溃一般不影响其他进程,但是切换进程时耗费资源,效率差些。 138 | 139 | 线程-程序执行的最小单位,没有独立的地址空间,一个线程死掉可能整个进程就死掉,但是节省资源,切换效率高。 140 | 141 | ## PHP常见的进程和线程 142 | - 在web应用中,我们每次访问php,就建立一个PHP进程,当然也会建立至少一个PHP线程 143 | - PHP使用pcntl来进行多进程编程 144 | - PHP中使用pthreads来进行多线程编程 145 | - nginx的每个进程只有一个线程,每个线程可以处理多个客户端的访问 146 | - php-fpm使用多进程模型,每个进程只有一个线程,每个线程只能处理一个客户端访问 147 | - apache可能使用多进程模型,也可能使用多线程模型,取决于使用哪种SAPI 148 | 149 | 150 | 151 | # 致谢 152 | 感谢你看到这里,有时一些底层的知识会对日常开发起到很大的作用,去理解它,并非彻底了解它。有什么问题可在评论区留言,谢谢 -------------------------------------------------------------------------------- /PHP/举枪消灭"烂代码"的实战案例.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/3708796996-5b72e78604548_articlex.png) 2 | 3 | # 前言 4 | 之前我写过一篇如何少写PHP "烂"代码 https://blog.fastrun.cn/2018/06/13/1-9/ 5 | 感觉很多新人对此不太理解。今天以打卡功能为例,去讲解其中的奥秘。那篇文章讲过代码开发的过程中分几种类型。 6 | 7 | ## 增删改的需求 8 | ``` 9 | Route -> Controller -> Service -> Action 10 | ``` 11 | ## 查的需求 12 | ``` 13 | Route -> Controller -> Service -> Repository 14 | ``` 15 | 经过多次实际开发验证后,发现Repository完全是多次一举。所以在这里更正下,取消Repository。 16 | ``` 17 | Route -> Controller -> Service 18 | ``` 19 | # 打卡系统逻辑架构图 20 | 21 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/3489252114-5b72e44c8c273_articlex.png) 22 | 23 | 需求是这样的,用户每天打卡获得积分,积分计入用户账户,并且需记录用户积分的获取及消费情况。如图所示,请求到控制器后,通过控制器去调用服务,服务又调用创建用户打卡模块完成打开,在用户打卡过程中对用户账户积分进行变更及记录用户积分获取记录。 24 | 25 | # 数据表结构 26 | ## 打卡数据表 27 | ``` 28 | CREATE TABLE `member_attendance` ( 29 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 30 | `member_id` int(11) NOT NULL COMMENT '用户编码', 31 | `status` enum('0','1') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '1签到成功', 32 | `created_at` timestamp NULL DEFAULT NULL, 33 | `updated_at` timestamp NULL DEFAULT NULL, 34 | PRIMARY KEY (`id`) 35 | ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 36 | ``` 37 | ## 用户钱包表 38 | ``` 39 | CREATE TABLE `wallet` ( 40 | `user_id` bigint(20) NOT NULL COMMENT '用户标示', 41 | `balance` decimal(12,2) NOT NULL COMMENT '钱包余额', 42 | `integral` decimal(12,2) NOT NULL DEFAULT '0', 43 | `add_time` int(11) NOT NULL COMMENT '添加时间', 44 | `update_time` int(11) NOT NULL COMMENT '更改时间', 45 | UNIQUE KEY `wallet_user_id_unique` (`user_id`), 46 | KEY `wallet_user_id_index` (`user_id`) 47 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 48 | ``` 49 | ## 用户积分交易记录表 50 | ``` 51 | CREATE TABLE `member_integral_detail` ( 52 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 53 | `member_id` int(11) NOT NULL COMMENT '用户编码', 54 | `title` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '来源或消费', 55 | `integral` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '积分数', 56 | `type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '类型 0收入 -1支出', 57 | `created_at` timestamp NULL DEFAULT NULL, 58 | `updated_at` timestamp NULL DEFAULT NULL, 59 | PRIMARY KEY (`id`) 60 | ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 61 | ``` 62 | # 具体业务实现 63 | 64 | ## Route 65 | ``` 66 | $api->post ('user/attendance','UserController@attendance'); 67 | ``` 68 | ## MemberController 69 | ``` 70 | public function attendance() 71 | { 72 | $result = $this->userService->attendance ($this->request); 73 | 74 | if ($result) { 75 | return $this->response->array (Response::return (200, '打卡成功')); 76 | } 77 | 78 | return $this->response->array (Response::return (0, "打卡失败或已打卡")); 79 | } 80 | ``` 81 | ## MemberService 82 | ``` 83 | public function attendance($request) 84 | { 85 | return (new CreateUserAttendance())->execute ($request); 86 | } 87 | ``` 88 | ## CreateUserAttendance 89 | ``` 90 | public function issetToday($userId) 91 | { 92 | $result = MemberAttendance::where ([ 93 | ['member_id', '=', $userId], 94 | ]) 95 | ->whereDate ('created_at', date ('Y-m-d', time ())) 96 | ->exists (); 97 | return $result; 98 | } 99 | // -------------------- 上述是下方issetToday方法,写在MemberModel中 100 | class CreateUserAttendance 101 | { 102 | public function execute($data) 103 | { 104 | 105 | if ((new MemberAttendance())->issetToday ($data->user_id)) { 106 | return false; 107 | } 108 | 109 | $models = new MemberAttendance(); 110 | $models->member_id = $data->user_id; 111 | $models->status = (string)"1"; 112 | 113 | $result = $models->save (); 114 | 115 | if ($result) { 116 | (new CreateUserIntegralDetail())->execute ($data->user_id, '打卡', 10, 0); 117 | return true; 118 | } 119 | 120 | return false; 121 | } 122 | } 123 | ``` 124 | ## CreateUserIntegralDetail 125 | ``` 126 | interface integralDetail 127 | { 128 | public function execute($userId, $title, $integral, $type); 129 | } 130 | 131 | class CreateUserIntegralDetail extends UpdateUserWalletIntegral implements integralDetail 132 | { 133 | public function execute($userId, $title, $integral, $type) 134 | { 135 | parent::exec ($userId, $integral, $type); 136 | 137 | $models = new MemberIntegralDetail(); 138 | $models->member_id = $userId; 139 | $models->title = $title; 140 | $models->integral = $integral; 141 | $models->type = $type; 142 | 143 | return $models->save (); 144 | } 145 | } 146 | ``` 147 | 上述代码继承了更新用户积分的动作,在每次打卡成功后,我们调用父类方法直接更新用户积分。 148 | ## UpdateUserWalletIntegral 149 | ``` 150 | class UpdateUserWalletIntegral 151 | { 152 | public function exec($userId, $integral, $type) 153 | { 154 | if ($type == 0) { 155 | Wallet::where (['user_id', '=', $userId])->increment ('integral', $integral); 156 | } else { 157 | Wallet::where (['user_id', '=', $userId])->decrement ('integral', $integral); 158 | } 159 | } 160 | } 161 | ``` 162 | 163 | # 致谢 164 | 感谢你看到这里,希望本篇文章可以帮到你。有什么问题可在下方评论区留言。谢谢🙏 -------------------------------------------------------------------------------- /PHP/取代PHP原生函数的一些扩展包.md: -------------------------------------------------------------------------------- 1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/67807839-5b727ad687555_articlex.png) 2 | 3 | 4 | # 前言 5 | 虽然程序员无时无刻都在造轮子,但造轮子也有效率之分,用好轮子才能造出好“🚗” 6 | 7 | # guzzlehttp/guzzle 8 | > composer require guzzlehttp/guzzle 9 | 10 | 你可以用guzzlehttp完全取代curl,file_get_content,fopen等函数。这个扩展包使用起来极为顺手。我们在代码量上看下对比。 11 | 12 | ## php_curl 13 | ``` 14 | "coder", 28 | "password" => "12345" 29 | ); 30 | curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); 31 | //执行命令 32 | $data = curl_exec($curl); 33 | //关闭URL请求 34 | curl_close($curl); 35 | //显示获得的数据 36 | print_r($data); 37 | ``` 38 | ## guzzlehttp 39 | ``` 40 | use GuzzleHttp\Client; 41 | 42 | $client = new GuzzleHttp\Client(); 43 | 44 | $response = $client->request('POST', 'http://www.baidu.com', [ 45 | 'form_params' => [ 46 | 'username' => 'coder', 47 | 'password' => '12345' 48 | ] 49 | ]); 50 | 51 | print_r($response); 52 | ``` 53 | # jenssegers/date 54 | > composer require jenssegers/date 55 | 56 | 使用这个扩展包,让php程序员对date相关的需求实现更简洁、简单了。请看下方对比 57 | 58 | ## php_date 59 | ``` 60 | date("Ym", strtotime("-1 day")); //获取前一天的日期 61 | 62 | date("Ym", strtotime("+1 day")); //获取后一天的日期 63 | ``` 64 | ## jenssegers_date 65 | ``` 66 | (new Date('-1 day'))->format ('Ym'); // 获取前一天的日期 67 | 68 | (new Date('+1 day'))->format ('Ym'); //获取后一天的日期 69 | ``` 70 | 显而易见,新的方法对date的处理呈现更加直观。当然这是简单的应用,复杂的日期计算中,它会更显优势。 71 | 72 | # chumper/zipper 73 | > composer require chumper/zipper 74 | 使用这个包可以简化php本身zip函数使用的复杂度 75 | 76 | ## php_zip 77 | ``` 78 | '; 88 | }else { 89 | echo $file_name . '
';  
 90 |                     $file_size = zip_entry_filesize($zip);
 91 |                     $file = zip_entry_read($zip, $file_size);
 92 |                     file_put_contents($save_path, $file);
 93 |                     zip_entry_close($zip);
 94 |                 }
 95 |                  
 96 |             }
 97 |         }
 98 |     }
 99 |     zip_close($resource);
100 | ```
101 | ## chumper/zipper
102 | ```
103 | Zipper::make('test.zip')->folder('test')->extractTo('foo');
104 | ```
105 | 显而易见,我想我不用去解释什么了。
106 | 
107 | # anchu/ftp
108 | > composer require anchu/ftp
109 | 本包可以简化php自身ftp上传代码的流程
110 | 
111 | ## php_ftp
112 | ```
113 |  '',
183 |    'username' => '',
184 |    'password'   => '',
185 |    'passive'   => false,
186 |    'secure'   => false,
187 | ));
188 | FTP::uploadFile($fileFrom,$fileTo,$mode)
189 | ```
190 | 
191 | 
192 | # 致谢
193 | 
194 | 感谢你看到这里,希望本篇文章可以帮助到你。
195 | 
196 | **向这些具有开源精神的工程师致敬**


--------------------------------------------------------------------------------
/Docker/Docker构建程序员的日常.md:
--------------------------------------------------------------------------------
  1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/07/2055627634-5b335ff4100b6_articlex.png)
  2 | 
  3 | # 前言
  4 | 
  5 | 抛开Docker那些强大的功能,今天我们来部署下本地的开发环境。并写上几个脚本来提高开发效率。
  6 | 
  7 | 本章以MacOs系统的Docker演示,其他系统作者为接触过。不知是否有差别。
  8 | 
  9 | # 安装
 10 | > 傻瓜式安装,这里就不再阐述了。下载地址如下
 11 | 
 12 | https://www.docker.com/products/docker-desktop
 13 | 
 14 | # 目录
 15 | 创建一些目录,就如在项目开发中创建Controller,Model,Service一样。我们将本地的Docker开发环境先从目录开始整理以下。
 16 | | 目录名 | 用途 |
 17 | | -- | -- |
 18 | | app | 项目目录,源程序存放的地方 |
 19 | | services | 服务目录,例如mysql,php等|
 20 | | ssh | 远程服务器目录,用于链接服务器 |
 21 | | web | 前端目录,正常node会指向它|
 22 | 
 23 | 部分文件列表
 24 | | 文件名 | 用途 |
 25 | | -- | -- |
 26 | | .env | 配置公共文件 |
 27 | | docker-compose.yml | docker-composer 配置文件 |
 28 | | hosts | 系统 hosts 文件 | 
 29 | | php.sh | php 相关操作文件 |
 30 | | start.sh | 本地环境操作脚本 |
 31 | 
 32 | 因是个人使用,所以对命名和规范稍有出入。请谅解
 33 | 
 34 | # 配置服务
 35 | 配置你所需要的服务到Docker容器内
 36 | ## MySQL
 37 | docker-composer.yml
 38 | ```
 39 | db:
 40 |     container_name: 'dev_db'
 41 |     build: ./services/mysql // 指向dockerfile文件
 42 |     environment:
 43 |       MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} // .env文件的配置项
 44 |     ports:
 45 |       - "3306:3306"
 46 |     volumes:
 47 |       - ${MYSQL_DATA_PATH}:/var/lib/mysql
 48 | ```
 49 | 创建```mysql-dockerfile```到```services\mysql```
 50 | Dockerfile
 51 | ```
 52 | FROM mysql:5.6
 53 | MAINTAINER crazycodes <919342864@qq.com>
 54 | 
 55 | ADD ./config/default.cnf /etc/mysql/conf.d/default.cnf
 56 | ```
 57 | ## Nginx
 58 | docker-composer.yml
 59 | ```
 60 | web:
 61 |     container_name: 'dev_nginx'
 62 |     build: ./services/nginx
 63 |     ports:
 64 |       - "80:80"
 65 |     depends_on:
 66 |       - php
 67 |     volumes_from:
 68 |       - php
 69 |     volumes:
 70 |       - ${NGINX_VOLUME_CONFIG_PATH}:/etc/nginx/conf.d
 71 |     dns: 8.8.8.8
 72 | ```
 73 | Dockerfile
 74 | ```
 75 | FROM nginx:latest
 76 | MAINTAINER crazycodes <919342864@qq.com>
 77 | 
 78 | RUN apt-get update && apt-get install -y vim sudo gcc make unzip wget mercurial libpcre3-dev zlib1g-dev libssl-dev devscripts debhelper dpkg-dev quilt lsb-release
 79 | 
 80 | COPY nginx.conf /etc/nginx/nginx.conf
 81 | COPY nginx-rtmp-module /etc/nginx/nginx-rtmp-module
 82 | COPY nginx-1.13.9 /etc/nginx/nginx-1.13.9
 83 | 
 84 | WORKDIR /etc/nginx/nginx-1.13.9
 85 | RUN ./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.13.7/debian/debuild-base/nginx-1.13.7=. -specs=/usr/share/dpkg/no-pie-compile.specs -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-specs=/usr/share/dpkg/no-pie-link.specs -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' --add-module=../nginx-rtmp-module
 86 | RUN make && make install
 87 | ```
 88 | 站点配置在```services\nginx\config```内,你可以正常配置
 89 | ```
 90 | server {
 91 |   listen       80;
 92 |   server_name  localhost;
 93 |   root          /mnt/app/flarum;
 94 |   index         index.php index.html index.htm;
 95 | 
 96 |   location / { try_files $uri $uri/ /index.php?$query_string; }
 97 |   location /api { try_files $uri $uri/ /api.php?$query_string; }
 98 |   location /admin { try_files $uri $uri/ /admin.php?$query_string; }
 99 | 
100 |   location /flarum {
101 |     deny all;
102 |     return 404;
103 |   }
104 |   location ~ .php$  {
105 |      fastcgi_split_path_info ^(.+.php)(/.+)$;
106 |      fastcgi_pass   php:9000; // 容器内,此处要填 容器名称:容器映射端口
107 |      fastcgi_index  index.php;
108 |      include        fastcgi_params;
109 |   }
110 | }
111 | ```
112 | ## PHP
113 | dockerfile文件内容过多。这里就不贴代码了。文章最后会贴出源码地址。
114 | # 其他
115 | 活学活用,代码不仅仅是帮助你开发,也可以帮你提示开发效率。将所有工作变得自动化,更能体现你的本领。
116 | ## ssh
117 | ssh中放着所有服务器链接文件
118 | ```
119 | set -x
120 | ssh root@xxx.xxx.xxx.xxx
121 | ```
122 | 每次当你需要链接服务器时,直接使用
123 | ```
124 | sh dev.sh
125 | ```
126 | 即可
127 | ## php.sh
128 | 如果想要操作容器内php的命令。命令过长,不方便。我们可以将代码放入sh文件中
129 | ```
130 | set -x
131 | 
132 | docker exec -it dev_php /bin/sh -c "cd /mnt/app/${1} && ${2}"
133 | ```
134 | 这时你如果需要操作容器内的PHP,就可以这样写
135 | ```
136 | sh php.sh My_Blog artisan migrate
137 | ```
138 | ## start.sh
139 | start.sh文件中存放着一些基本命令,当然你也可以继续扩展你想要的。例如对composer的操作,php的操作,mysql的操作等等。具体你可以去点击下方链接查看。
140 | # 致谢
141 | 习惯将许多命令封装到执行文件内。方便自己使用。提升开发效率和质量是每位程序员必备的技能。
142 | 
143 | https://github.com/CrazyCodes/Http-Developers
144 | 这并不是一个非常认真的docker“操作”,请勿使用到生产环境。
145 | 很高兴你看到这里,希望本篇文章可以帮到你。谢谢。


--------------------------------------------------------------------------------
/Go/Beego 文件上传至七牛云的玩法.md:
--------------------------------------------------------------------------------
  1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/10/2978289583-5bd423e85207c_articlex.png)
  2 | 
  3 | 
  4 | # 前言
  5 | 
  6 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/beego_purple.png)
  7 | 
  8 | Beego是一款GO语言开发的传统MVC的框架,beego对上传这块的代码封装的也非常简单易用。
  9 | 
 10 | 
 11 | # beego的上传
 12 | 
 13 | 贴出官方的一段代码
 14 | 
 15 | https://beego.me/docs/mvc/controller/params.md#%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0
 16 | 
 17 | ```
 18 | func (c *FormController) Post() {
 19 |     f, h, err := c.GetFile("uploadname")
 20 |     if err != nil {
 21 |         log.Fatal("getfile err ", err)
 22 |     }
 23 |     defer f.Close()
 24 |     c.SaveToFile("uploadname", "static/upload/" + h.Filename) // 保存位置在 static/upload, 没有文件夹要先创建
 25 |     
 26 | }
 27 | ```
 28 | 
 29 | 这里beego通过 GetFile方法获取文件的name,既设置的 ```input name="uploadname"``` 可以通过其第二个返回值获得文件的详细详细
 30 | ```
 31 | h.Filename
 32 | h.Header
 33 | h.size
 34 | ```
 35 | 上面的例子是一个传统上传,既将文件拷贝到本地项目目录中。
 36 | 
 37 | # 七牛云
 38 | 关于注册、登录、获取key什么的本文就不废话了。官方提供了Go的Sdk
 39 | 
 40 | 文档地址:https://developer.qiniu.com/kodo/sdk/1238/go
 41 | 
 42 | ## 安装
 43 | 通过go get 安装包
 44 | ```
 45 | go get -u github.com/qiniu/api.v7
 46 | ```
 47 | 
 48 | ## 使用
 49 | 这里演示两种上传方式
 50 | 七牛官方的SDK提供了几个上传方法,如下所示
 51 | ```
 52 | // 本地文件上传至七牛云
 53 | func (p *FormUploader) PutFile
 54 | func (p *FormUploader) PutFileWithoutKey
 55 | // 数据流方式上传至七牛云
 56 | func (p *FormUploader) Put
 57 | func (p *FormUploader) PutWithoutKey
 58 | ```
 59 | 
 60 | 这个SDK也允许你自定义返回结果,通过重写结构体的方式
 61 | ```
 62 | type PutRet struct {
 63 | 	Hash         string `json:"hash"`
 64 | 	PersistentID string `json:"persistentId"`
 65 | 	Key          string `json:"key"`
 66 | }
 67 | ```
 68 | 
 69 | ### 本地上传
 70 | 这是官方的一个demo,本地上传这里就不多阐述了,大概都能看懂
 71 | ```
 72 | // 设置上传文件
 73 | localFile = "/Users/jemy/Documents/github.png"
 74 | // 设置上传空间名
 75 | bucket = "if-pbl"
 76 | // 上传的文件名称
 77 | key = "github-x.png"
 78 | 
 79 | putPolicy := storage.PutPolicy{
 80 |     Scope:               bucket,
 81 | }
 82 | mac := qbox.NewMac(accessKey, secretKey)
 83 | upToken := putPolicy.UploadToken(mac)
 84 | 
 85 | cfg := storage.Config{}
 86 | // 空间对应的机房
 87 | cfg.Zone = &storage.ZoneHuadong
 88 | // 是否使用https域名
 89 | cfg.UseHTTPS = false
 90 | // 上传是否使用CDN上传加速
 91 | cfg.UseCdnDomains = false
 92 | 
 93 | // 构建表单上传的对象
 94 | formUploader := storage.NewFormUploader(&cfg)
 95 | ret := storage.PutRet{}
 96 | 
 97 | // 可选配置
 98 | putExtra := storage.PutExtra{
 99 |     Params: map[string]string{
100 |         "x:name": "github logo",
101 |     },
102 | }
103 | err := formUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
104 | if err != nil {
105 |     fmt.Println(err)
106 |     return
107 | }
108 | fmt.Println(ret.Key,ret.Hash)
109 | 
110 | ```
111 | 
112 | ### 流信息上传
113 | 这是我自行封装的流上传代码
114 | ```
115 | package qiniu
116 | 
117 | import (
118 | 	"github.com/qiniu/api.v7/storage"
119 | 	"github.com/qiniu/api.v7/auth/qbox"
120 | 	"context"
121 | 	"io"
122 | )
123 | 
124 | const (
125 | 	bucket    = "avatars" // 
126 | 	accessKey = ""
127 | 	secretKey = ""
128 | )
129 | 
130 | func config() storage.Config {
131 | 	cfg := storage.Config{}
132 | 
133 | 	cfg.Zone = &storage.ZoneHuadong
134 | 	// 是否使用https域名
135 | 	cfg.UseHTTPS = false
136 | 	// 上传是否使用CDN上传加速
137 | 	cfg.UseCdnDomains = false
138 | 
139 | 	return cfg
140 | }
141 | 
142 | func Upload(localFile io.Reader, size int64, filename string) (string, error) {
143 | 
144 | 	putPolicy := storage.PutPolicy{
145 | 		Scope: bucket,
146 | 	}
147 | 
148 | 	mac := qbox.NewMac(accessKey, secretKey)
149 | 	upToken := putPolicy.UploadToken(mac)
150 | 
151 | 	cig := config()
152 | 
153 | 	formUploader := storage.NewFormUploader(&cig)
154 | 
155 | 	ret := storage.PutRet{}
156 | 
157 | 	putExtra := storage.PutExtra{}
158 | 
159 | 	err := formUploader.Put(context.Background(), &ret, upToken, filename, localFile, size, &putExtra)
160 | 
161 | 	if err != nil {
162 | 		return "", err
163 | 	}
164 | 
165 | 	return ret.Key, nil
166 | }
167 | 
168 | ```
169 | 注意```localFile io.Reader```这个参数,调用upload方法需要传一个byte。下面演示调用方法的代码
170 | 
171 | ```
172 | func (u *UserController) UploadImage() {
173 | 	f, h, err := u.GetFile("image")
174 | 
175 | 	if err != nil {
176 | 		u.Data["json"] = common.Response{
177 | 			Data:    [0]int{},
178 | 			Message: "上传失败",
179 | 			Code:    0,
180 | 		}
181 | 
182 | 		u.ServeJSON()
183 | 
184 | 		return
185 | 	}
186 | 
187 | 
188 | 	defer f.Close()
189 | 
190 | 	file, _ := h.Open() // 这里获得的实际就是一个io,通过源码看到这个open方法最终返回的是一个结构体,其内部包含了 io.Reader的接口
191 | 	
192 | 	// type File interface {
193 | 		// io.Reader
194 | 		// io.ReaderAt
195 | 		// io.Seeker
196 | 		// io.Closer
197 | 	// }
198 | 
199 | 
200 | 	unix := time.Now().Unix()
201 | 
202 | 	timeByte := []byte(strconv.Itoa(int(unix)))
203 | 
204 | 	filename := function.Md5(timeByte) // 这里是计算了一个md5(time())的字符串作为文件名
205 | 
206 | 	if filename, err = qiniu.Upload(file, h.Size, filename); err != nil { // 通过h.size 即可获得文件大小
207 | 		u.Data["json"] = common.Response{
208 | 			Data:    [0]int{},
209 | 			Message: "上传失败",
210 | 			Code:    0,
211 | 		}
212 | 	} else {
213 | 		u.Data["json"] = common.Response{
214 | 			Data: map[string]string{
215 | 				"filename": filename,
216 | 			},
217 | 			Message: "上传成功",
218 | 			Code:    200,
219 | 		}
220 | 	}
221 | 
222 | 	u.ServeJSON()
223 | }
224 | 
225 | 
226 | ```
227 | 
228 | # 致谢
229 | go 自身的特点不应是做web,web使用php,java就够了。一般用go都是写接口。所以上传文件时不应先传到本地再传到七牛,我想很多初学者都在这里被坑。特此写一篇来解释其使用方法。
230 | 
231 | 希望本篇文章可以帮到你。谢谢
232 | 
233 | 
234 | 
235 | 


--------------------------------------------------------------------------------
/Laravel/Laravel源码解析之反射的使用.md:
--------------------------------------------------------------------------------
  1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/09/3276665162-5ba45dfac43fc_articlex.png)
  2 | 
  3 | # 前言
  4 | PHP的反射类与实例化对象作用相反,实例化是调用封装类中的方法、成员,而反射类则是拆封类中的所有方法、成员变量,并包括私有方法等。就如“解刨”一样,我们可以调用任何关键字修饰的方法、成员。当然在正常业务中是建议不使用,比较反射类已经摒弃了封装的概念。
  5 | 
  6 | 本章讲解反射类的使用及Laravel对反射的使用。
  7 | # 反射
  8 | 反射类是PHP内部类,无需加载即可使用,你可以通过实例化 ``` ReflectionClass ```类去使用它。
  9 | ## 方法
 10 | 这里列举下PHP反射类常用的方法
 11 | 
 12 | | 方法名 | 注释 |
 13 | | -- | -- |
 14 | | ReflectionClass::getConstant | 获取定义过的一个常量 |
 15 | | ReflectionClass::getConstants | 获取一组常量 |
 16 | | ReflectionClass::getConstructor | 获取类的构造函数 |
 17 | | ReflectionClass::getDefaultProperties | 获取默认属性 |
 18 | | ReflectionClass::getDocComment | 获取文档注释 |
 19 | | ReflectionClass::getEndLine | 获取最后一行的行数 |
 20 | | ReflectionClass::getFileName | 获取定义类的文件名 |
 21 | | ReflectionClass::getInterfaceNames | 获取接口(interface)名称 |
 22 | | ReflectionClass::getMethods | 获取方法的数组 |
 23 | | ReflectionClass::getModifiers | 获取类的修饰符|
 24 | | ReflectionClass::getName | 获取类名 |
 25 | | ReflectionClass::getNamespaceName | 获取命名空间的名称 |
 26 | | ReflectionClass::getParentClass | 获取父类 |
 27 | 
 28 | 
 29 | 等等等等.... 所有关于类的方法、属性及其继承的父类、实现的接口都可以查询到。
 30 | 详细文档请参考官网: http://php.net/manual/zh/class.reflectionclass.php
 31 | ## 栗子
 32 | ```
 33 | inNamespace());
 41 |     var_dump($function->getName());
 42 |     var_dump($function->getNamespaceName());
 43 |     var_dump($function->getShortName());
 44 |     
 45 |     $function = new \ReflectionClass('A\\B\\Foo');
 46 |     
 47 |     var_dump($function->inNamespace());
 48 |     var_dump($function->getName());
 49 |     var_dump($function->getNamespaceName());
 50 |     var_dump($function->getShortName());
 51 | ?>
 52 | ```
 53 | 输出结果
 54 | ```
 55 | bool(false)
 56 | string(8) "stdClass"
 57 | string(0) ""
 58 | string(8) "stdClass"
 59 | 
 60 | bool(true)
 61 | string(7) "A\B\Foo"
 62 | string(3) "A\B"
 63 | string(3) "Foo"
 64 | ```
 65 | 
 66 | # Laravel
 67 | Laravel在实现服务容器加载时使用了反射类。现在我们开启“解刨”模式
 68 | ## 入口文件
 69 | 
 70 | ### index.php
 71 | ```
 72 | $app = require_once __DIR__.'/../bootstrap/app.php';
 73 | 
 74 | /*
 75 | |--------------------------------------------------------------------------
 76 | | Run The Application
 77 | |--------------------------------------------------------------------------
 78 | |
 79 | | Once we have the application, we can handle the incoming request
 80 | | through the kernel, and send the associated response back to
 81 | | the client's browser allowing them to enjoy the creative
 82 | | and wonderful application we have prepared for them.
 83 | |
 84 | */
 85 | 
 86 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
 87 | 
 88 | $response = $kernel->handle(
 89 |     $request = Illuminate\Http\Request::capture()
 90 | );
 91 | 
 92 | $response->send();
 93 | 
 94 | $kernel->terminate($request, $response);
 95 | ```
 96 | 是引用语句发生的下一行调用了make方法。各位很清楚,make方法用于解析类,所有make方法的实现一定是在引用的文件内。
 97 | ### bootstrap\\app.php
 98 | ```
 99 | $app = new Illuminate\Foundation\Application(
100 |     realpath(__DIR__.'/../')
101 | );
102 | ```
103 | laravel开始加载它的核心类,所有的实现从 ``` Illuminate\Foundation\Application ``` 开始。
104 | 
105 | ### Illuminate\\Foundation\\Application
106 | ```
107 | public function make($abstract, array $parameters = [])
108 | {
109 |         $abstract = $this->getAlias($abstract);
110 | 
111 |         if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
112 |             $this->loadDeferredProvider($abstract);
113 |         }
114 | 
115 |         return parent::make($abstract, $parameters);
116 | }
117 | 
118 | ```
119 | 在核心类中你可能准确的查找到make方法的存在,它加载了服务提供者随后调用了父类的方法make,要知道作为独立的模块 “服务容器”是绝对不能写在核心类的。懂点设计模式的都很清楚。
120 | ### Illuminate\\Container\\Container
121 | 以``` $api = $this->app->make('HelpSpot\API',['id'=>1]); ``` 为例来讲解
122 | 
123 | ```
124 | // 真正的make方法,它直接调用了resolve继续去实现make的功能
125 | // $abstract = 'HelpSpot\API'
126 | public function make($abstract, array $parameters = [])
127 | {
128 |     // $abstract = 'HelpSpot\API'
129 |     return $this->resolve($abstract, $parameters);
130 | }
131 | 
132 | ...
133 | 
134 | protected function resolve($abstract, $parameters = [])
135 | {
136 |     ...
137 |     // 判断是否可以合理反射
138 |     // $abstract = 'HelpSpot\API'
139 |     if ($this->isBuildable($concrete, $abstract)) {
140 |         // 实例化具体实例 (实际并不是实例化,而是通过反射“解刨”了)
141 |         $object = $this->build($concrete);
142 |     } else {
143 |         $object = $this->make($concrete);
144 |     }
145 |     ...
146 | }
147 | 
148 | public function build($concrete)
149 | {
150 |         // $concrete = 'HelpSpot\API'
151 |         if ($concrete instanceof Closure) {
152 |             return $concrete($this, $this->getLastParameterOverride());
153 |         }
154 |         // 实例化反射类
155 |         $reflector = new ReflectionClass($concrete);
156 | 
157 |         // 检查类是否可实例化
158 |         if (! $reflector->isInstantiable()) {
159 |             return $this->notInstantiable($concrete);
160 |         }
161 | 
162 |         $this->buildStack[] = $concrete;
163 | 
164 |         // 获取类的构造函数
165 |         $constructor = $reflector->getConstructor();
166 |         
167 |         if (is_null($constructor)) {
168 |             array_pop($this->buildStack);
169 | 
170 |             return new $concrete;
171 |         }
172 | 
173 |         $dependencies = $constructor->getParameters();
174 | 
175 |         $instances = $this->resolveDependencies(
176 |             $dependencies
177 |         );
178 | 
179 |         array_pop($this->buildStack);
180 |            
181 |         //  从给出的参数创建一个新的类实例。
182 |         return $reflector->newInstanceArgs($instances);
183 | }
184 | ```
185 | 可见一个服务容器就加载成功了。
186 | 
187 | # 致谢
188 | 感谢你看到这里,本篇文章源码解析靠个人理解。如有出入请拍砖。
189 | 
190 | 希望本篇文章可以帮到你。谢谢


--------------------------------------------------------------------------------
/PHP/2020 PHP程序员修炼秘籍.md:
--------------------------------------------------------------------------------
  1 | 
  2 | # 前言
  3 | 大家好,我是CrazyCodes,一名正在创业路上的程序员,今天我为各位整理2020年PHP程序员修炼秘籍,希望可以帮到你。
  4 | 
  5 | # 语言
  6 | 2019年是不安分的一年,身为程序员的我们也是恐慌不已,大厂各种裁员,整的程序员界人心惶惶。就算是这样,依旧有很多喷子攻击我大PHP阵营
  7 | 
  8 | > 张三喷子:“php已经不行了”,马上要被什么什么替代了。
  9 | > 李四喷子:“php这个技术不如这门语言了,不如那门语言了”
 10 | > 王五喷子:“身边朋友都去学某某语言了,php不行了”
 11 | 
 12 | 其实吧,作为多年php圈子里面的混混,我想说两句
 13 | 
 14 | 首先,奉上最新的世界开发语言排行榜
 15 | 
 16 | 
 17 | 
 18 | 至今为止,其实PHP没有跑出前十名,其实会不会跑出前十名这并不重要。JavaScript在PHP前面,也并不会代表什么。
 19 | 
 20 | 发表下个人意见吧
 21 | 1. 开发语言的存在即合理
 22 | 2. PHP算是一门老语言,在互联网发展长河里,PHP语言承担着历史上的重要
 23 | 3. 无论出现什么形式的新开发语言,都无法代替PHP本身在界内的影响力
 24 | 
 25 | # 开发框架
 26 | 2019年并没有出现特别火爆的新开发框架。
 27 | 位于榜首的依旧是万能脚手架 - Laravel
 28 | 
 29 | ![Laravel](https://laravel.com/img/logotype.min.svg)
 30 | 
 31 | 在过去的一年,我见过太多太多PHP的项目在重构过程中慢慢引进Laravel,并将Laravel作为项目首选开发框架。
 32 | 
 33 | 当然我也是如此,作为Laravel框架的早期使用者,我由衷的感觉Laravel在近些年内的地位还是很稳定的。
 34 | 
 35 | 有些人要说了,Laravel框架太重、运行速度太慢、blblblbl的各种原因,我只能说,羊毛出在羊身上,Laravel是组件化开发早期框架,感觉哪个慢就不加载哪个呗,哪个重就删掉不就行了,一天天不找解决方案,只会抛出问题,难道Laravel框架的开发者会按照小众个人意愿去改嘛😆
 36 | 
 37 | 其他的框架我很少了解,在这里就着重讲解下Laravel
 38 | 
 39 | 框架本身没什么好说的,讲些Laravel社区生态相关的事。
 40 | 
 41 | ## 文档
 42 | 首先说的是完整的框架中文相关文档,这里必须提到一个大佬Summer,是这位大佬建立起Laravel中文社区,让所有Laravel框架使用者不再对英文发愁
 43 | https://learnku.com/laravel
 44 | 
 45 | ## 开发
 46 | 让开发者更快速的构建属于自己的PHP程序,Laravel做了很多。例如一键启动PHP程序
 47 | ```
 48 | php artisan serve
 49 | ```
 50 | 这让PHP程序员在任意的一台电脑上都可以轻松编写PHP代码并直接运行
 51 | 
 52 | 当然还有专属于Laravel的开发环境 Homestead
 53 | 
 54 | ## 扩展
 55 | 最后是各种第三方扩展不断丰富着Laravel社区,例如
 56 | 
 57 | Horizon,它为Laravel提供了队列可视化的仪表盘,为什么列出Horizon呢,是因为对于程序员来说,看不到或者无法断言的任何事情,会让程序员没有安全感,而恰恰Horizon让你看到所有你想看到的事件
 58 | 
 59 | ![](https://res.cloudinary.com/dtfbvvkyp/image/upload/v1537195039/photos/Test.png)
 60 | 
 61 | 还需要强调的一个Telescope,我现在的项目内也使用了Telescope
 62 | 
 63 | ![](https://res.cloudinary.com/dtfbvvkyp/image/upload/v1539110860/Screen_Shot_2018-10-09_at_1.47.23_PM.png)
 64 | 
 65 | 它会检测框架内所有的请求,并列出相关信息,当程序出现问题的时候,你会发现Telescope简直是救命神器
 66 | 
 67 | 还有很多很多的工具等待你的使用
 68 | 
 69 | 
 70 | 
 71 | 当然选择什么框架,用什么方式来去做PHP语言的相关开发,还是要根据需求和个人喜好去选择。都是PHP大营的产品,这里不做太多描述。(怕被自己人打😆)
 72 | 
 73 | # Swoole
 74 | ![](https://www.swoole.com/static/files/swoole-logo.svg)
 75 | Swoole算是重新托起了PHP在开发语言中的地位。将以往无法实现或者实现困难的问题几乎全部解决。
 76 | 
 77 | Swoole已经众所周知,本篇就不过多描述了。
 78 | https://www.swoole.com/
 79 | 
 80 | # 工具
 81 | 众所周知,在开发过程中,仅仅熟练使用自己的本命语言是完全不够的。还要依托一些三方工具,去不断提升开发效率和编码质量。
 82 | 
 83 | ## Sequel Pro
 84 | 
 85 | 
 86 | Sequel Pro 是一款数据库管理软件,在使用过不会因数据量过大而卡死,UI精美小巧。是我这些年来一直使用数据库管理软件。
 87 | 
 88 | ## VLC
 89 | 
 90 | 这是一款测试推拉流的工具,至今是没有找到比它更好的软件代替
 91 | 
 92 | ## PhpStorm
 93 | 每位程序员都有自己钟爱的开发工具,PhpStorm则是我的首选。可能本身体积略大,耗费内存也比较高,对电脑本身是有一些要求的。
 94 | 
 95 | 但当你真正熟练使用它的时候,会发现开发速度不是一般的快。
 96 | 
 97 | ## ShadowsocksX
 98 | 
 99 | 这个少说为好,如果是一名真正的程序员,我相信翻qiao对TA来说比媳妇还重要
100 | 
101 | ## PostMan
102 | 
103 | 在移动互联网时代,每位PHP程序员都必须要开发接口。而PostMan在开发过程中作为调试神奇,是每位程序员装机必备之佳品
104 | 
105 | ## 有道云笔记
106 | 
107 | 其实吧,这个也属于程序员必备。人生漫漫,在从事开发事业的过程中,要记录的东西还是很多的吧。
108 | 
109 | ## Alfred
110 | 
111 | Mac必备神器,当用了它后,我感觉生活水平都提高了😆
112 | 
113 | ## GitKraken
114 | 
115 | GitKraken是我迄今为止见过ui做的最美的git客户端
116 | 
117 | 还有很多很多工具有待发现,当然一把趁手的刀才是好刀,各位朋友按需选择
118 | 
119 | # 前端
120 | 简单聊一下前端,近些年来,前端发展迅速,我认为作为一名程序,无论是做前端、后端、服务端还是PC端开发的。都应去了解其“火”的原因,去了解,去实践,不要被时代抛弃。
121 | 
122 | 前端技术我的建议是:还是需要去学习的,要不要深学要看个人需求
123 | 
124 | # 最后
125 | 
126 | 当我发出思否[2019年总结文章](https://segmentfault.com/a/1190000021420447)的时候,相信大家已经知道我不再是公司员工了。
127 | 
128 | 对于创业,我只想说,提升自己全方面的能力,不仅仅是技术大拿那么简单的事情了。创业是另外一种生活方式,如要选择,便是不归路。
129 | 
130 | 各位准备创业的程序员朋友,请三思而后行。
131 | 
132 | 顺便宣传下我创业的产品,是一款习惯养成的APP,多年来我都是严格要求自己,去年我做了这款产品,希望可以让更多的人自律起来。真正的自由,是自律带给你的选择权
133 | 
134 | 
135 | 
136 | # 致谢
137 | 感谢你看到这里,希望本篇文章可以帮助到你,谢谢。


--------------------------------------------------------------------------------
/优化你的PHP代码,从现在做起.md:
--------------------------------------------------------------------------------
  1 | ![clipboard.png](https://resources.blog.fastrun.cn/wp-content/uploads/2019/05/2588740952-5ce6624e0ca28_articlex.png)
  2 | 
  3 | # 前言
  4 | > 我一生的文章都会放在这里,我的博客,我希望每一行代码,每一段文字都能帮助你。https://github.com/CrazyCodes/Blog
  5 | 
  6 | 大家好,我是CrazyCodes ,今天我们不聊工具、规范等等等等的辅助,就聊一下该如何写一段“好”的代码,本文以我的职业生涯碰到的代码为例,如有出入请在评论区提出异议,谢谢。
  7 | 
  8 | # 搜索功能
  9 | 搜索很常见,复杂的搜索大多出行在后台,举个栗子,大概需求是这样的
 10 | 
 11 | **这是一个后台用户列表的搜索功能**
 12 | | 搜索条件 | 可否并行 | 是否必填 |
 13 | | -- | -- | -- |
 14 | | 用户名 | 可以 | 否 |
 15 | | 手机号码 | 可以 | 否 |
 16 | | 是否已认证 | 可以 | 是 |
 17 | | 用户性别 | 可以 | 否 |
 18 | | 最近登录时间 | 可以 | 否 |
 19 | | 账户余额 | 可以 | 否|
 20 | 
 21 | # 初学者代码
 22 | 看到这些例子你是否不由的一颤,又要开始造轮子的是不是?
 23 | 以原生的例子为例,开始你可能会这样写(以下为伪代码)
 24 | ```
 25 | if (IS_POST) {
 26 |     $like = '';
 27 |     if (isset($_POST['username'])) {
 28 |         $username = $_POST['username'];
 29 |         $like .= "username like '%" . $username . "%' and ";
 30 |     }
 31 | 
 32 |     if (isset($_POST['phone'])) {
 33 |         $phone = $_POST['phone'];
 34 |         $like .= "phone like '%" . $phone . "%' and";
 35 | 
 36 |     }
 37 | 
 38 |     if ($_POST['is_auth']) {
 39 |         $isAuth = $_POST['is_auth'];
 40 |         $like .= "is_auth like '%" . $isAuth . "%' and";
 41 | 
 42 |     }
 43 | 
 44 |     if ($_POST['sex']) {
 45 |         $sex = $_POST['sex'];
 46 |         $like .= "sex like '%" . $sex . "%' and";
 47 |     }
 48 | 
 49 | 
 50 |     if ($_POST['time']) {
 51 |         $time = $_POST['time'];
 52 |         $like .= "time like '%" . $time . "%' and";
 53 |     }
 54 | 
 55 | 
 56 |     if ($_POST['wallet']) {
 57 |         $wallet = $_POST['wallet'];
 58 |         $like .= "wallet like '%" . $wallet . "%' and";
 59 | 
 60 |     }
 61 | 
 62 |     $like = rtrim($like, 'and');
 63 | 
 64 |     $sql = "SELECT * FROM `user` WHERE {$like}";
 65 | 
 66 | 
 67 | } else {
 68 |     return view('user');
 69 | }
 70 | ```
 71 | # 封装
 72 | 恩...,还不错,结构清晰,传统的初学者条型代码,接下来我们先封装一下几块代码。
 73 | ```
 74 | function post($param)
 75 | {
 76 |     return isset($_POST[$param]) ? $_POST[$param] : null;
 77 | }
 78 | 
 79 | if (IS_POST) {
 80 |     $like = '';
 81 |     if (post('username')) {
 82 |         $username = post('username');
 83 |         $like .= "username like '%" . $username . "%' and ";
 84 |     }
 85 | 
 86 |     if (post('phone')) {
 87 |         $phone = post('phone');
 88 |         $like .= "phone like '%" . $phone . "%' and";
 89 | 
 90 |     }
 91 | 
 92 |     if (post('is_auth')) {
 93 |         $isAuth = post('is_auth');
 94 |         $like .= "is_auth like '%" . $isAuth . "%' and";
 95 | 
 96 |     }
 97 | 
 98 |     if (post('sex')) {
 99 |         $sex = post('sex');
100 |         $like .= "sex like '%" . $sex . "%' and";
101 |     }
102 | 
103 | 
104 |     if (post('time')) {
105 |         $time = post('time');
106 |         $like .= "time like '%" . $time . "%' and";
107 |     }
108 | 
109 | 
110 |     if (post('wallet')) {
111 |         $wallet = post('wallet');
112 |         $like .= "wallet like '%" . $wallet . "%' and";
113 | 
114 |     }
115 | 
116 |     $like = rtrim($like, 'and');
117 | 
118 |     $sql = "SELECT * FROM `user` WHERE {$like}";
119 | 
120 | 
121 | } else {
122 |     return view('user');
123 | }
124 | ```
125 | # 适当使用迭代
126 | 恩,至少我们可以自由控制post方法了,但是这类过程化代码维护性太低,我们再改进下
127 | ```
128 | function post($param)
129 | {
130 |     return isset($_POST[$param]) ? $_POST[$param] : false;
131 | }
132 | 
133 | function postAll()
134 | {
135 |     return $_POST;
136 | }
137 | 
138 | if (IS_POST) {
139 |     $like = '';
140 | 
141 | 
142 |     foreach (postAll() as $key => $value) {
143 |         if (post($key)) {
144 |             $like .= "{$key} like '%{$value}%' and";
145 |         }
146 |     }
147 | 
148 |     $like = rtrim($like, 'and');
149 | 
150 |     $sql = "SELECT * FROM `user` WHERE {$like}";
151 | 
152 | 
153 | } else {
154 |     return view('user');
155 | }
156 | ```
157 | # 面向对象
158 | 加个迭代代码看起来还算是整洁了点,作为PHP程序员,写代码不面向对象不靠谱,把class加上
159 | ```
160 | function request($param = null)
161 | {
162 |     return new Request($param);
163 | }
164 | 
165 | class Request
166 | {
167 |     public function __construct(string $param = null)
168 |     {
169 |         return isset($_POST[$param]) ? $_POST[$param] : false;
170 |     }
171 | 
172 |     public function all()
173 |     {
174 |         return $_POST;
175 |     }
176 | }
177 | 
178 | 
179 | class User
180 | {
181 |     public function index()
182 |     {
183 |         if (IS_POST) {
184 |             $like = '';
185 | 
186 |             foreach (request()->all() as $key => $value) {
187 |                 if (request($key)) {
188 |                     $like .= "{$key} like '%{$value}%' and";
189 |                 }
190 |             }
191 | 
192 |             $like = rtrim($like, 'and');
193 | 
194 |             $sql = "SELECT * FROM `user` WHERE {$like}";
195 | 
196 |         } else {
197 |             return view('user');
198 |         }
199 |     }
200 | }
201 | 
202 | ```
203 | # 对User的改造
204 | 我们在对User的类进行改造,做一些判断及筛选
205 | ```
206 | function request($param = null)
207 | {
208 |     return new Request($param);
209 | }
210 | 
211 | class Request
212 | {
213 |     public function __construct(string $param = null)
214 |     {
215 |         return isset($_POST[$param]) ? $_POST[$param] : false;
216 |     }
217 | 
218 |     public function all()
219 |     {
220 |         return $_POST;
221 |     }
222 | }
223 | 
224 | 
225 | class User
226 | {
227 |     public $request = [
228 |         'username',
229 |         'phone',
230 |         'is_auth',
231 |         'sex',
232 |         'time',
233 |         'wallet'
234 |     ];
235 | 
236 |     public function index()
237 |     {
238 |         if (IS_POST) {
239 |             $like = '';
240 | 
241 |             foreach (request()->all() as $key => $value) {
242 |                 if (in_array($key, $this->request) && request($key)) {
243 |                     $like .= sprintf("%s like %s and", $key, $value);
244 |                 }
245 |             }
246 | 
247 |             $like = rtrim($like, 'and');
248 | 
249 |             $sql = "SELECT * FROM `user` WHERE {$like}";
250 | 
251 |         } else {
252 |             return view('user');
253 |         }
254 |     }
255 | }
256 | 
257 | 
258 | ```
259 | 这就差不多了,对比真是的代码可能还相差甚远,我写这篇文章的目的不是教会你如何写代码,是想说明编码不是一次性的,应经过多次修改,使代码具有可维护性,扩展性等等的,各种“性”
260 | # 致谢
261 | 感谢你看到这里,希望本篇文章可以帮到你,谢谢
262 | 


--------------------------------------------------------------------------------
/MySQL/MySQL常用系统表汇总.md:
--------------------------------------------------------------------------------
  1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/timg-1.jpeg)
  2 | 
  3 | # 概述
  4 | 本篇文章虽大部分内容为参考原文作者的相关内容,但对原文对于文章的逻辑与排版上进行了大范围修改,方便阅读与理解。原文链接在底部
  5 | 
  6 | # MySQL5.7 默认模式
  7 | 
  8 | |  库名 |  表数量 | 视图数量 |
  9 | | ------------ | ------------ | ----|
 10 | |  information_schema |  61 | 0|
 11 | |  mysql |  32 | 0|
 12 | | performance_schema | 87 | 0|
 13 | | sys | 1 | 100|
 14 | 
 15 | ## Information_schema
 16 | Information_schema数据库是MySQL自带的,它提供了访问数据库元数据的方式。
 17 | 
 18 | ### 什么是元数据呢?
 19 | 元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。有些时候用于表述该信息的其他术语包括“数据词典”和“系统目录”。
 20 | 
 21 | 在MySQL中,把 information_schema 看作是一个数据库,确切说是信息数据库。其中保存着关于MySQL服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。在INFORMATION_SCHEMA中,有数个只读表。它们实际上是视图,而不是基本表,因此,你将无法看到与之相关的任何文件
 22 | 
 23 | ### information_schema 数据库部分表说明
 24 | | 表名  | 注释  |
 25 | | ------------ | ------------ |
 26 | |  SCHEMATA |  提供了当前mysql实例中所有数据库的信息。是show databases的结果取之此表 |
 27 | | TABLES  | 提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema、表类型、表引擎、创建时间等信息。是show tables from schemaname的结果取之此表  |
 28 | | COLUMNS | 提供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是show columns from schemaname.tablename的结果取之此表 |
 29 | | STATISTICS |提供了关于表索引的信息。是show index from schemaname.tablename的结果取之此表 |
 30 | |  USER_PRIVILEGES | 用户权限表:给出了关于全程权限的信息。该信息源自mysql.user授权表。是非标准表|
 31 | | SCHEMA_PRIVILEGES | 方案权限表:给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。是非标准表 |
 32 | | TABLE_PRIVILEGES | 表权限表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。是非标准表 |
 33 | | COLUMN_PRIVILEGES |列权限表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。是非标准表 |
 34 | | CHARACTER_SETS | 字符集表:提供了mysql实例可用字符集的信息。是SHOW CHARACTER SET结果集取之此表|
 35 | | COLLATIONS | 提供了关于各字符集的对照信息|
 36 | | COLLATION_CHARACTER_SET_APPLICABILITY |指明了可用于校对的字符集。这些列等效于SHOW COLLATION的前两个显示字段。  |
 37 | | TABLE_CONSTRAINTS | 描述了存在约束的表。以及表的约束类型|
 38 | |  KEY_COLUMN_USAGE |描述了具有约束的键列 |
 39 | | ROUTINES | 提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数(UDF)。名为“mysql.proc name”的列指明了对应于INFORMATION_SCHEMA.ROUTINES表的mysql.proc表列|
 40 | | VIEWS |给出了关于数据库中的视图的信息。需要有show views权限,否则无法查看视图信息 |
 41 | | TRIGGERS | 提供了关于触发程序的信息。必须有super权限才能查看该表 |
 42 | 
 43 | ## performance_schema
 44 | PERFORMANCE_SCHEMA这个功能默认是关闭的。需要设置参数: performance_schema 才可以启动该功能,这个参数是静态参数,只能写在my.cnf 中 不能动态修改。 
 45 | ### performance_schema数据库部分表说明 
 46 | |  表名 |  注释 |
 47 | | ------------ | ------------ |
 48 | |  setup_table | 设置表,配置监控选项  |
 49 | |  current_events_table | 记录当前那些thread 正在发生什么事情  |
 50 | | history_table | 发生的各种事件的历史记录表 |
 51 | | summary_table | 对各种事件的统计表  |
 52 | | setup_consumers\setup_instruments|描述各种事件, 设置哪些事件能够被收集  |
 53 | | setup_instruments | 描述这个数据库下的表名以及是否开启监控 | 
 54 | | setup_timers | 描述监控选项已经采样频率的时间间隔|
 55 | | threads |监控服务器所有连接 |
 56 | | performance_timers | 设置一些监控信息, 指定mysql服务可用的监控周期,CYCLE表示按每秒检测2603393034次, 目前 performance-schema 只支持'wait'时间的监控,代码树上 wait/ 下的函数都可以监控到|
 57 | 
 58 | ## mysql
 59 | 在mysql数据库中,有mysql_install_db脚本初始化权限表,存储权限的表
 60 | ### mysql数据库部分表说明 
 61 | |  表名  | 注释  |
 62 | | ------------ | ------------ |
 63 | |  user |  用户列、权限列、安全列、资源控制列 |
 64 | | db  |  用户列、权限列  |
 65 | | host | |
 66 | | table_priv | |
 67 | | columns_priv | |
 68 | | proc_priv | |
 69 | 
 70 | 
 71 | 
 72 | ##  sys
 73 | sys_config : 这是在这个系统库上存在的唯一一个表
 74 | ### sys数据库表说明
 75 | ```
 76 | CREATE TABLE `sys_config` (
 77 |   `variable` varchar(128) NOT NULL,
 78 |   `value` varchar(128) DEFAULT NULL,
 79 |   `set_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 80 |   `set_by` varchar(128) DEFAULT NULL,
 81 |   PRIMARY KEY (`variable`)
 82 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8
 83 | ```
 84 | - variable : 配置选项名称 
 85 | - value    : 配置选项值 
 86 | - set_time : 该行配置修改的时间 
 87 | - set_by   : 该行配置信息修改者,如果从被安装没有修改过,那么这个数据应该为NULL 
 88 | 
 89 | ![](https://img-blog.csdn.net/20160701140113431)
 90 | 
 91 | 以上值的会话变量为```@sys.```+表中variable字段,如:
 92 | ```
 93 | @sys.statement_truncate_len 
 94 | ```
 95 | 
 96 | 可以
 97 | ```
 98 | set @sys.statement_truncate_len = 32 
 99 | ```
100 | 临时改变值,在会话中会一直使用这个值,如果想要恢复使用表的默认值,只需要将这个会话值设置为null
101 | ```
102 | set @sys.statement_truncate_len = null; 
103 | ```
104 | #### diagnostics.allow_i_s_tables
105 | 默认为OFF ,如果开启表示允许diagnostics() 存储过程执行扫描information_schema.tables 表,如果表很多,那么可能会很耗性能
106 | 
107 | #### diagnostics.include_raw 
108 | 默认为OFF,开启将会从metrics 视图输出未加工处理的数据
109 | #### statement_performance_analyzer.limit
110 | 视图在没有加limit限制时,返回的最大行数 
111 | #### statement_truncate_len 
112 | 通过format_statement()函数返回值的最大长度
113 | 
114 | #### debug
115 | 这个表非默认选项还有一个@sys.debug参数,可以手动加入
116 | ```
117 | INSERT INTO sys_config (variable, value) VALUES('debug', 'ON');
118 | UPDATE sys_config SET value = 'OFF' WHERE variable = 'debug';
119 | SET @sys.debug = NULL;
120 | ```
121 | 
122 | ### 关于此表有两个触发器 
123 | #### sys_config_insert_set_user触发器 
124 | 如果加入新行通过insert语句,那么这个触发器会把set_by列设置为当前操作者 
125 | #### sys_config_update_set_user触发器 
126 | 如果加入新行通过update语句,那么这个触发器会把set_by列设置为当前操作者
127 | 
128 | # MYSQL SHOW 命令
129 | |  命令  | 注释   |
130 | | ------------ | ------------ |
131 | |  desc [table_name] | 表信息  |
132 | |  show columns from [table_name]  | 表字段  |
133 | |  describe [table_name] | 表信息 |
134 | | show create table [table_name] | 表创建语句 |
135 | | show create database [database_name] | 显示数据库信息 |
136 | | show table status from [database_name] |数据库状态|
137 | |  show tables |显示当前数据库中所有表的名称  |
138 | | show tables from [database_name] | 显示当前数据库中所有表的名称(同上)  |
139 | | show databases | 显示mysql中所有数据库的名称 |
140 | | show processlist | 显示系统中正在运行的所有进程,也就是当前正在执行的查询。|
141 | | show table status | 显示当前使用或者指定的database中的每个表的信息。信息包括表类型和表的最新更新时间  |
142 | |  show columns from [table_name] from [database_name] | 显示表中列名称 |
143 | | show grants for user_name@localhost | 显示一个用户的权限,显示结果类似于grant 命令 |
144 | | show index from [table_name] |显示表的索引  |
145 | | show status | 显示一些系统特定资源的信息,例如,正在运行的线程数量 |
146 | | show variables |显示系统变量的名称和值 |
147 | | show privileges |  显示服务器所支持的不同权限 | 
148 | | show create database [database_name] |显示create database 语句是否能够创建指定的数据库 |
149 | | show create table [table_name] |显示create database 语句是否能够创建指定的数据库  |
150 | | show engies | 显示安装以后可用的存储引擎和默认引擎|
151 | | show innodb status | 显示innoDB存储引擎的状态 | 
152 | | show logs | 显示BDB存储引擎的日志  |
153 | | show warnings | 显示最后一个执行的语句所产生的错误、警告和通知 |
154 | | show errors | 只显示最后一个执行语句所产生的错误|
155 | 
156 | 整理转载自:
157 | https://blog.csdn.net/xlxxcc/article/details/51754524
158 | 
159 | # 致谢
160 | 感谢你看完这篇文章,作者原文的知识点概括的很全面,但是文章的排版与表达比较凌乱,不适宜新手来查阅,所以我就做了重构文章的一件事。希望可以帮到你。
161 | 


--------------------------------------------------------------------------------
/我与Jetbrains的这些年.md:
--------------------------------------------------------------------------------
  1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/3172380423-5bf6bb76b2c05_articlex.png)
  2 | 
  3 | # 前言
  4 | 本章主要说一下如何去使用Jetbrains的各类工具,并且在上周参加了Jetbrains开发者日的大会,把参会的感受和体验在这里分享给各位。话不多说,我们进入正题。
  5 | 
  6 | # 使用
  7 | 想必各位一定使用过Jetbrains的任意一款产品,就算没用过也应该听说过吧。Jetbrains从开始至今总共分为三大模块
  8 | 1. Developer Ide (集成开发工具)
  9 | 2. Language (Jetbrains的开发语言 Kotlin)
 10 | 3. DevOps (任务管理,持续集成,持续部署的一些东西)
 11 | 
 12 | 对于Developer Ide我推荐使用Jetbrains ToolBox,它是管理所有Jetbrains Ide的工具。
 13 | 
 14 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/2738462830-5bf763dbbaaa8_articlex.png)
 15 | 
 16 | Jetbrains平均一个月做一次小更新,安装了Jetbrains ToolBox就不必再去关心更新的事情了。并且Jetbrains ToolBox 还提供了项目列表的功能,如果这时候你需要打开一个项目,你无需去关心项目是哪种开发语言做的,只需要打开它并选择你需要开启的项目即可。
 17 | 
 18 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/3367914272-5bf764e45ef15_articlex.png)
 19 | 
 20 | 
 21 | 这里要注意,使用Alfred的同学,Jetbrains ToolBox将Ide安装的目录不在是/Application而是~/Application,要记得加入到可搜索列表中,否则是找不到启动文件的。
 22 | 
 23 | 
 24 | > 以PhpStrom为例,来讲解一下Jetbrains Ide的一些有趣的配置。
 25 | 
 26 | ## 快速编码
 27 | 这在Jetbrains开发者日上范老师讲过的一个技巧,你需要打开
 28 | 
 29 | > Preferences -> Editor -> {General->Postfix Completion || Live Templates}
 30 |     
 31 | General->Postfix Completion 与 Live Templates 都是为了去提供编码速度的设置。首先我们先看下 Live Templates
 32 |    
 33 | 
 34 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/1027937040-5bf76881278a9_articlex.png)
 35 | 
 36 | 当你在输入fore时按空格(当然也可以选择回车,Tab键,这是需要设置的)
 37 | 
 38 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/2201331946-5bf7691dc24c8_articlex.png)
 39 | 
 40 | 就会出现完整的foreach语句,你可以把他当做填空题去完善这个结构
 41 | ```
 42 | fore 
 43 | ----> 
 44 | foreach ($ITERABLE$ as $VAR_VALUE$) {
 45 |     $END$
 46 | }
 47 | ```
 48 | 当然还有一些其他的例如 eco -> echo , prof -> 创建一个protected的类方法。Ide默认已经为我们准备了一些常用的简写方法,涵盖了至少你知道的所有语言。当然如果没有你想要的你也可以自行添加新的模板。
 49 | 
 50 | 
 51 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/579261965-5bf769e67327e_articlex.png)
 52 | 
 53 | 真不愧叫 Live Templates,第二个来介绍下General->Postfix Completion,这个对于第一个来说是另外一种编辑,第一种是通过键入初始化命令来生成模板,例如eco -> echo ,第二种则是以对象形式去便捷的去生成模板。下面举个栗子
 54 | 
 55 | 
 56 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/1502191407-5bf76b0c98d65_articlex.png)
 57 | 
 58 | 当你键入 $a.notnull 则生成 if ($a !== null) {}
 59 | ```
 60 | $a.notnull
 61 | ---->
 62 | if ($a !== null) {
 63 | 
 64 | }
 65 | ```
 66 | 在编码的时候不是每次都会想好整个流程结构在开始编码,所以Live Templates 就有一定的局限性了。这时候 General->Postfix Completion 就起到了很大作用。最后我们介绍 File and Code Templates , 在快捷生成编码及后续生成代码是在编码开始时的一些骚操作,为了将编码更快,我们还需要将常用的文件模板加入其中
 67 | 
 68 | 
 69 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/1027937040-5bf76881278a9_articlex.png)
 70 | 
 71 | 例如你长期使用Laravel框架去开发应用,在使用laravel写一个功能的时候我们会经历下面几个步骤
 72 | 
 73 | > 声明一个路由 -> 创建一个Controller,Model,Action ... 文件 -> 调用Model操作数据 -> 返回给用户结果
 74 | 
 75 | 如果你使用PhpStrom你大致这样写
 76 | 1. 打开路由文件 router+空格 完成一个路由结构的自动生成 (当然你需要提前设置)
 77 | 2. 创建一个 Php Controller,Model,Action 文件,文件结构已经设置好
 78 | 3. 使用General->Postfix Completion设置好的写法分分钟解决所有操作
 79 | 
 80 | ## Database
 81 | PhpStrom内置了Database,面板十分简洁易用。他面板的右上角
 82 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/4104122340-5bf771de5184e_articlex.png)
 83 | 有兴趣你可以去试试
 84 | 
 85 | ## Api
 86 | 细心的朋友一定知道PhpStrom内置了接口测试工具,他在 Tools -> Http Client -> Test RestFul Web Service 下。
 87 | 
 88 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/2809847463-5bf7727ca72a0_articlex.png)
 89 | 
 90 | 临时的测试你不必打开万能的Postman,你完全可以通过ide内置的测试工具去完成api测试。当然还有另外一种更厉害的方式 -> 创建一个 test.http 文件
 91 | ```
 92 | GET www.baidu.com
 93 | ```
 94 | 
 95 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/558378001-5bf78e204e894_articlex.png)
 96 | 
 97 | 像上述这样,点击运行你将获得Response
 98 | ```
 99 | GET http://www.baidu.com
100 | 
101 | HTTP/1.1 200 OK
102 | Date: Fri, 23 Nov 2018 05:19:42 GMT
103 | Server: Apache
104 | Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
105 | ETag: "51-47cf7e6ee8400"
106 | Accept-Ranges: bytes
107 | Content-Length: 81
108 | Cache-Control: max-age=86400
109 | Expires: Sat, 24 Nov 2018 05:19:42 GMT
110 | Connection: Keep-Alive
111 | Content-Type: text/html
112 | 
113 | 
114 | 
115 | 
116 | 
117 | 
118 | Response code: 200 (OK); Time: 71ms; Content length: 81 bytes
119 | ```
120 | 当然对比Postman依旧不逊色,既然已经将软件改为编码形式,那在Postman内的功能,我们在.http文件中很方便的就可以实现。可以添加请求参数
121 | ```
122 | GET http://www.baidu.com
123 | Content-Type: application/json
124 | {
125 | "name":"zhangsan"
126 | }
127 | 
128 | ```
129 | 每个请求以下一个请求方式前结束。具体请移步官网查看。
130 | 
131 | ## 主题
132 | 工欲善其事,必先利其器。上面说的Jetbrains提供的功能只是冰山一角,想要具体的学习如何使用还请移步官方。帅气的UI也是调整开发效率的一部分。看到自己的ide非常漂亮,编码的心情自己好的不得了。我使用的是 Material Theme UI
133 | 
134 | 
135 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/2267589781-5bf7932089ee7_articlex.png)
136 | 
137 | 关于字体的调整可以在 Editor -> Font 下进行
138 | 
139 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/3237674940-5bf793994dca3_articlex.png)
140 | 
141 | 当然如果你不仅仅从事PHP相关的开发,好不容易配置出来一个自己满意的IDE,写Go的时候用Goland还要配置,大可不必这样。你可以通过 File -> Export Settings 来导出你的配置
142 | 
143 | 
144 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/11/2425191417-5bf7944a87c56_articlex.png)
145 | 
146 | 随后在通过 File -> Import Settings 来导入你的配置。这样就OK了。如果你购买了 Jetbrains 全家桶或者任意的Ide,也可以通过File -> Sync Settings To Jetbrains Account 来同步到你的Jetbrains账号,类似于云同步一样。
147 | 
148 | # 便捷
149 | 剩下的就是快捷键了,快捷键乃IDE编码之本,什么?不需要? 那你咋不用Word编码去?快捷键我就不总结了,网络上太多了。下面贴出laravel-china一位大神的总结。
150 | 
151 | https://laravel-china.org/topics/5420/your-keyboard-shortcuts-please
152 | # 大会
153 | 以上说了一部分大会的内容了。Jetbrains的开发者日也并不是全部商业宣传,至少它做的事情是值得让开发者认可的。PPT无法分享给各位,我也不知道怎么拿到。抱歉!大会主要将了以下几项内容,上午是主会场,下午分俩个会场 Kotlin & Java 和 Web,作为一个PHP程序员,我没得选。
154 | 
155 | 
156 | ----------
157 | 上午
158 | 
159 | 1. 大会开始,说了很多关于他们的语言 Kotlin 的使用方法。
160 | 2. 随后说了Jetbrains内部的工作方式及管理方法
161 | 3. 开始ide -> Kotlin 显示 Kotlin特性
162 | 
163 | 
164 | ----------
165 | 下午
166 | 
167 | 1. 简洁高效的PHP编程指南,推荐了一下测试、调试方法&类库及PhpStrom的使用方法
168 | 2. 在真实世界中进行 Go性能优化 讲的go pprof
169 | 3. 用TDD学习高效开发 开始这大佬写了一通的java测试用例,不咋能看懂。后面说了一些关于程序人生的事情
170 | 4. JetBrains的无痛DevOps解决方案 最后讲了JetBrainsDevOps的一些套件,例如TeamCity (与TravisCi类似) ,Upsource 等等。具体可参考 https://www.jetbrains.com/devops/?fromMenu
171 | 
172 | ----------
173 | 
174 | 总而言之,总体下来大多都是干货,至少我感觉没有任何商业宣传的性质。
175 | 
176 | # 致谢
177 | 到这里本章就结束了,感谢看到这里,不过我还要提醒一句给各位开发者
178 | 
179 | **编辑器不要汉化**
180 | **编辑器不要汉化**
181 | **编辑器不要汉化**
182 | 
183 | 相信你可以明白。谢谢!
184 |  


--------------------------------------------------------------------------------
/日常划水:短信验证码开发实例.md:
--------------------------------------------------------------------------------
  1 | ![clipboard.png](https://resources.blog.fastrun.cn/wp-content/uploads/2019/09/1406099182-5d1b310b33e30_articlex.png)
  2 | 
  3 | 
  4 | # 前言 
  5 | > 我一生的文章都会放在这里,我的博客,我希望每一行代码,每一段文字都能帮助你。https://github.com/CrazyCodes/Blog  
  6 | 
  7 | 大家好,我是CrazyCodes,在日常开发中有没有遇到过发送短信验证码的接口需要开发?你是如何处理短信验证码发送的呢?本篇我分享下短信验证码发送的设计。
  8 | 
  9 | # 初学者
 10 | 以聚合数据为例,初学者会酱紫做
 11 | 
 12 | 百度
 13 | 
 14 | ![clipboard.png](https://resources.blog.fastrun.cn/wp-content/uploads/2019/09/2058924079-5d1b29ffb12fa_articlex.png)
 15 | 
 16 | 找到一串既熟悉又陌生的代码 (咋整也记不住的代码)
 17 | 
 18 | ```
 19 | //初始化
 20 | $curl = curl_init();
 21 | //设置抓取的url
 22 | curl_setopt($curl, CURLOPT_URL, 'http://www.baidu.com');
 23 | //设置头文件的信息作为数据流输出
 24 | curl_setopt($curl, CURLOPT_HEADER, 1);
 25 | //设置获取的信息以文件流的形式返回,而不是直接输出。
 26 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
 27 | //设置post方式提交
 28 | curl_setopt($curl, CURLOPT_POST, 1);
 29 | //设置post数据
 30 | $post_data = array(
 31 |     "username" => "coder",
 32 |     "password" => "12345"
 33 |     );
 34 | curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
 35 | //执行命令
 36 | $data = curl_exec($curl);
 37 | //关闭URL请求
 38 | curl_close($curl);
 39 | //显示获得的数据
 40 | print_r($data);
 41 | ```
 42 | 官方也给出了一段维护性略差的代码
 43 | ```
 44 |  '*****************', //您申请的APPKEY
 55 |     'mobile'    => '1891351****', //接受短信的用户手机号码
 56 |     'tpl_id'    => '111', //您申请的短信模板ID,根据实际情况修改
 57 |     'tpl_value' =>'#code#=1234&#company#=聚合数据' //您设置的模板变量,根据实际情况修改
 58 | );
 59 |  
 60 | $content = juhecurl($sendUrl,$smsConf,1); //请求发送短信
 61 |  
 62 | if($content){
 63 |     $result = json_decode($content,true);
 64 |     $error_code = $result['error_code'];
 65 |     if($error_code == 0){
 66 |         //状态为0,说明短信发送成功
 67 |         echo "短信发送成功,短信ID:".$result['result']['sid'];
 68 |     }else{
 69 |         //状态非0,说明失败
 70 |         $msg = $result['reason'];
 71 |         echo "短信发送失败(".$error_code."):".$msg;
 72 |     }
 73 | }else{
 74 |     //返回内容异常,以下可根据业务逻辑自行修改
 75 |     echo "请求发送短信失败";
 76 | }
 77 |  
 78 | /**
 79 |  * 请求接口返回内容
 80 |  * @param  string $url [请求的URL地址]
 81 |  * @param  string $params [请求的参数]
 82 |  * @param  int $ipost [是否采用POST形式]
 83 |  * @return  string
 84 |  */
 85 | function juhecurl($url,$params=false,$ispost=0){
 86 |     $httpInfo = array();
 87 |     $ch = curl_init();
 88 |     curl_setopt( $ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 );
 89 |     curl_setopt( $ch, CURLOPT_USERAGENT , 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22' );
 90 |     curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT , 30 );
 91 |     curl_setopt( $ch, CURLOPT_TIMEOUT , 30);
 92 |     curl_setopt( $ch, CURLOPT_RETURNTRANSFER , true );
 93 |     if( $ispost )
 94 |     {
 95 |         curl_setopt( $ch , CURLOPT_POST , true );
 96 |         curl_setopt( $ch , CURLOPT_POSTFIELDS , $params );
 97 |         curl_setopt( $ch , CURLOPT_URL , $url );
 98 |     }
 99 |     else
100 |     {
101 |         if($params){
102 |             curl_setopt( $ch , CURLOPT_URL , $url.'?'.$params );
103 |         }else{
104 |             curl_setopt( $ch , CURLOPT_URL , $url);
105 |         }
106 |     }
107 |     $response = curl_exec( $ch );
108 |     if ($response === FALSE) {
109 |         //echo "cURL Error: " . curl_error($ch);
110 |         return false;
111 |     }
112 |     $httpCode = curl_getinfo( $ch , CURLINFO_HTTP_CODE );
113 |     $httpInfo = array_merge( $httpInfo , curl_getinfo( $ch ) );
114 |     curl_close( $ch );
115 |     return $response;
116 | }
117 | ```
118 | 这样看,“也不是不好,就是想再改改,至于改什么,不知道,就是想再改改”
119 | 
120 | # 接口
121 | 那么我就开始自己的表演了。首先看本篇你需要了解
122 | - 设计模式中的策略模式
123 | - 依赖注入
124 | - 接口 (interface)
125 | 否则,你看的会很懵 (大佬请略过)
126 | 
127 | 首先我们要写一个interface用于规范发短信这个动作
128 | ```
129 | namespace App\Api\Common;
130 | 
131 | interface MessageUseInterface
132 | {
133 |     public function insert($mobile, $code);
134 | }
135 | ```
136 | 要求所有发短信的动作都必须继承这个接口,并且实现insert方法。
137 | 
138 | # 短信类
139 | 接口设定好,我们需要设定一个父类,既发短信的类,具体实现如下
140 | ```
141 | namespace App\Api\Common;
142 | 
143 | use GuzzleHttp\Client;
144 | 
145 | class Message
146 | {
147 |     /**
148 |      * @param $mobile
149 |      * @param $tpl
150 |      * @param $code
151 |      * @param MessageUseInterface $use
152 |      * @return mixed
153 |      * @throws \GuzzleHttp\Exception\GuzzleException
154 |      */
155 |     public function send($mobile, $tpl, $code, MessageUseInterface $use)
156 |     {
157 |         $client = new Client();
158 |         $response = $client->request('POST', 'http://v.juhe.cn/sms/send', [
159 |             'form_params' => [
160 |                 'mobile' => $mobile,
161 |                 'tpl_id' => $tpl,
162 |                 'tpl_value' => sprintf('#code#=%s', $code),
163 |                 'key' => 'xxxxxxxxxxxx'
164 |             ]
165 |         ]);
166 | 
167 |         $use->insert($mobile, $code);
168 | 
169 |         return json_decode($response->getBody(), true);
170 |     }
171 | }
172 | ```
173 | 这个类创建了send方法,参数分别为
174 | - mobile 既接收短信验证码的手机号码
175 | - tpl_id 聚合数据提供的模板编码
176 | - code 发送的验证码
177 | - MessageUseInterface 上面创建的接口interface
178 | 
179 | 具体实现则是使用GuzzleHttp去实现POST请求,并按聚合数据规定发送验证码。
180 | ```
181 | $use->insert($mobile, $code);
182 | ```
183 | 则是调用通过MessageUseInterface传进来的实体类
184 | 
185 | # 实体类
186 | 之后我们创建实体类,这里以手机号+验证码登录为例
187 | ```
188 | namespace App\Api\Common\Message;
189 | 
190 | 
191 | use App\Api\Common\MessageUseInterface;
192 | use App\Api\Common\Redis;
193 | 
194 | class Login implements MessageUseInterface
195 | {
196 |     public function insert($mobile, $code)
197 |     {
198 |         $redis = Redis::init();
199 |         $key = sprintf('login_code:%s', $mobile);
200 |         $redis->setex($key, 600, $code);
201 |     }
202 | }
203 | ```
204 | Login继承接口MessageUseInterface 并实现insert方法。这里redis设定的规范为 
205 | ```
206 | module:手机号 -> value(需要发送的验证码)
207 | ```
208 | 至此,我们的短信验证码发送的例子就结束了。
209 | # 使用
210 | 我们可以下列方式调用,或者使用Laravel的服务提供者
211 | ```
212 | (new Message())->send($tel, 141345, mt_rand(100000, 999999), new Register());
213 | ```
214 | 这样既解决了乱七八糟的各种验证码,还提高了代码的可维护性,如果老板有新的需求,例如,支付验证码什么的,你只需要新建一个Pay的验证码类,即完成支付验证码的功能。
215 | 
216 | # 致谢
217 | 上述只是一个简单的例子,实体类并未做更多的延伸,请自行发挥创造力。
218 | 
219 | 感谢你看到这里,希望本篇文章可以帮到你。谢谢。
220 | 


--------------------------------------------------------------------------------
/PHP/使用GrumPHP来纠正代码“毛病”.md:
--------------------------------------------------------------------------------
  1 | ![clipboard.png](http://resources.blog.fastrun.cn/wp-content/uploads/2019/07/1636353612-5ceddce4547bb_articlex.png)
  2 | 
  3 | 
  4 | # 前言
  5 | > 我一生的文章都会放在这里,我的博客,我希望每一行代码,每一段文字都能帮助你。https://github.com/CrazyCodes/Blog
  6 | 
  7 | 嗨,我是CrazyCodes,小时候做错事,长辈有没有训斥过你呢?今天让我们看看PHP的监护者,愤怒的老头- - grumphp
  8 | 
  9 | # GrumPHP
 10 | GrumPHP 是通过挂在git hook上的一款PHP代码检测工具,他可以通过编码人员提交git时进行检查,检查通过则提交成功,检查失败则终止提交。
 11 | 
 12 | # 安装
 13 | 通过composer直接安装即可,不推荐其他安装方式
 14 | ```
 15 | composer require --dev phpro/grumphp
 16 | ```
 17 | 这类工具一定要放在dev内,在生产环境使用毫无意义,所以带参 --dev。
 18 | # 配置
 19 | 安装结束后会自动在项目根目录建立grumphp.yml,官方给出的demo如下
 20 | ```
 21 | # grumphp.yml
 22 | parameters:
 23 |     bin_dir: "./vendor/bin"
 24 |     git_dir: "."
 25 |     hooks_dir: ~
 26 |     hooks_preset: local
 27 |     git_hook_variables:
 28 |          VAGRANT_HOST_DIR: .
 29 |          VAGRANT_PROJECT_DIR: /var/www
 30 |          EXEC_GRUMPHP_COMMAND: exec
 31 |     stop_on_failure: false
 32 |     ignore_unstaged_changes: false
 33 |     hide_circumvention_tip: false
 34 |     process_async_limit: 10
 35 |     process_async_wait: 1000
 36 |     process_timeout: 60
 37 |     ascii:
 38 |         failed: grumphp-grumpy.txt
 39 |         succeeded: grumphp-happy.txt
 40 |     tasks:
 41 |         ant: ~
 42 |         atoum: ~
 43 |         behat: ~
 44 |         brunch: ~
 45 |         clover_coverage: ~
 46 |         codeception: ~
 47 |         composer: ~
 48 |         composer_normalize: ~
 49 |         composer_require_checker: ~
 50 |         composer_script: ~
 51 |         deptrac: ~
 52 |         doctrine_orm: ~
 53 |         file_size: ~
 54 |         gherkin: ~
 55 |         git_blacklist: ~
 56 |         git_branch_name: ~
 57 |         git_commit_message: ~
 58 |         grunt: ~
 59 |         gulp: ~
 60 |         infection: ~
 61 |         jsonlint: ~
 62 |         kahlan: ~
 63 |         make: ~
 64 |         npm_script: ~
 65 |         paratest: ~
 66 |         phan: ~        
 67 |         phing: ~
 68 |         php7cc: ~
 69 |         phpcpd: ~
 70 |         phpcs: ~
 71 |         phpcsfixer: ~
 72 |         phpcsfixer2: ~
 73 |         phplint: ~
 74 |         phpmd: ~
 75 |         phpmnd: ~
 76 |         phpparser: ~
 77 |         phpspec: ~
 78 |         phpstan: ~
 79 |         phpunit: ~
 80 |         phpunitbridge: ~
 81 |         phpversion: ~
 82 |         progpilot: ~
 83 |         psalm: ~
 84 |         robo: ~
 85 |         securitychecker: ~
 86 |         shell: ~
 87 |         twigcs: ~
 88 |         xmllint: ~
 89 |         yamllint: ~
 90 |     testsuites: []
 91 |     extensions: []
 92 | ```
 93 | ## 参数说明
 94 | | 参数名 | 默认值 | 注释 |
 95 | | -- | -- | -- |
 96 | | bin_dir | ./vendor/bin | 方便找到phpcs这类检测工具的外部命令,一般不需要修改 |
 97 | | git_dir | . | 默认git目录,正常都在根目录的吧 |
 98 | | hooks_dir | null | 设置钩子文件夹,默认会直接找 resources/hooks |
 99 | | tasks |  | 用于加载代码检测的库 |
100 | 
101 | # 实战
102 | 是不是太多了,忽略上面,咱一步一步看。下面是安装完成后自动生成的配置文件
103 | ```
104 | parameters:
105 |     git_dir: .
106 |     bin_dir: vendor/bin
107 |     tasks: {  }
108 | ```
109 | ## 准备
110 | 现在目录是酱紫的
111 | 
112 | ![clipboard.png](http://resources.blog.fastrun.cn/wp-content/uploads/2019/07/4032416285-5cede1e0a908e_articlex.png)
113 | 
114 | 1. 首先在github建立一个库,用于测试。https://github.com/CrazyCodes/grumphp_test
115 | 
116 | 2. 默认的配置不会检测任何代码。我们为 tasks 加入一个新成员 https://github.com/phpro/grumphp/blob/master/doc/tasks/phpcs.md
117 | 
118 | 3. 重新设置配置文件
119 | ## 骚操作
120 | ```
121 | parameters:
122 |     git_dir: .
123 |     bin_dir: vendor/bin
124 |     tasks: {
125 |        phpcs : ~
126 |     }
127 | ```
128 | 
129 | 新建一个文件easy.php,代码如下,多一个分号,老头是一定不会放过你的。
130 | ```
131 | echo '召唤愤怒的老头';;
132 | ```
133 | 老头是酱紫说的,额,其实他是一个红色的
134 | ```
135 | GrumPHP detected a pre-commit command.
136 | GrumPHP is sniffing your code!
137 | Running task 1/1: Phpcs... ✘
138 |              ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
139 |            ▄▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌
140 |          ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄
141 |         ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌
142 |        ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌
143 |   ▄███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌
144 |  █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌
145 |  ▐█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌
146 |    ▀█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌
147 |      ▀▀▓▓▓▓▓▓▓▓▓▓▓▓█▀▀▀▀▀▀▀▀▀▀▀▀▀▀████████████▄
148 |       ▄███████                       ██████████
149 |      ███████▀  ▀▀▀▀▀▄      ▄▀▀▀▀▀     █████ ▀
150 |       ▐████      ▐██        ▐██        ████▌
151 |       ████▌                            ███
152 |        ▌██▌           ▄▄ ▄▄           ▐███
153 |         ███       ▄▄▄▄▄▄▄▄▄▄▄▄       ▐███
154 |          ██▄ ▐███████████████████████████
155 |         █▀███████████▀     ▀▀███████████
156 |           ██████████▄███████▄███████████
157 |          ▐█████████████████████████████
158 |           █████████████████████████████
159 |            ██ █████████████████████▐██▀
160 |             ▀ ▐███████████████████▌ ▐▀
161 |                 ████▀████████▀▐███
162 |                  ▀█▌  ▐█████  ██▌
163 |                         ██▀   ▐▀
164 | 
165 |        ██████████████████████████████████
166 |        █░░░░░░▀█▀░░░░░░▀█░░░░░░▀█▀░░░░░▀█
167 |        █░░▐█▌░░█░░░██░░░█░░██░░░█░░░██░░█
168 |        █░░▐█▌░░█░░░██░░░█░░██░░░█░░░██░░█
169 |        █░░▐█▌░░█░░░██░░░█░░░░░░▄█░░▄▄▄▄▄█
170 |        █░░▐█▌░░█░░░██░░░█░░░░████░░░░░░░█
171 |        █░░░█░░░█▄░░░░░░▄█░░░░████▄░░░░░▄█
172 |        ██████████████████████████████████
173 | 
174 | FILE: /Users/crazy/http/app/yield_test/easy.php
175 | ----------------------------------------------------------------------
176 | FOUND 1 ERROR AFFECTING 1 LINE
177 | ----------------------------------------------------------------------
178 |  2 | ERROR | Missing file doc comment
179 | ----------------------------------------------------------------------
180 | 
181 | Time: 37ms; Memory: 4MB
182 | 
183 | ```
184 | 不仅仅有愤怒的老头,GrumPHP还提示了一些其他内容。
185 | - FILE 检测文件路径
186 | - FOUND 1 ERROR AFFECTING 1 LINE 有几个错误,在哪一行
187 | - 2 | ERROR | Missing file doc comment 错误的问题是什么
188 | 
189 | 这次检测是说我没写注释。是不是很有意思?GrumPHP不仅仅就这点能力哦,想深入请自行挖掘。
190 | 
191 | - 参数列表 https://github.com/phpro/grumphp/blob/master/doc/parameters.md
192 | - 质量检测包 https://github.com/phpro/grumphp/blob/master/doc/tasks.md
193 | 
194 | 
195 | # 其他
196 | 有人问,你为什么要这么折磨自己呢?其实像类型GrumPHP代码质量工具,不是仅仅自己拿来玩的,在开发人员略多的技术团队,可以通过使用它来达到代码规范一致,如果每个人代码都不一样,后果不堪设想。
197 | 
198 | ![clipboard.png](https://resources.blog.fastrun.cn/wp-content/uploads/2019/07/30655536-5cedef42825c0_articlex.png)
199 | 
200 | 
201 | # 致谢
202 | 感谢你看到这里,希望本篇文章可以帮到你。
203 | 
204 | 


--------------------------------------------------------------------------------
/Shop/电商系统设计之商品接口.md:
--------------------------------------------------------------------------------
  1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/877827781-5b62da5520aab_articlex.png)
  2 | 
  3 | # 前言
  4 | 我应该是少数在文章中直接展示接口文档的人。本篇我思考了很久到底要不要解析下商品接口开发的注意点。
  5 | 
  6 | 客户端开发与服务端开发即是天敌也是兄弟。希望本篇文章让你们减少争执,把“爱”给对方。
  7 | # 接口设计
  8 | 
  9 | ## 简述
 10 | 电商系统设计之中,比较复杂的接口就论商品详情的接口了,响应参数特别多,特别杂。在开发获取商品详情接口时要遵循以下几个原则
 11 | 
 12 | - 返回的JSON嵌套数量要少
 13 | - 方便去查询到指定的SKU
 14 | - 其他接口相关规范
 15 | 
 16 | 
 17 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/08/1556964048-5b62d7a7324ca_articlex.png)
 18 | 
 19 | ## 查询SKU
 20 | 关于查询SKU,我让我的小伙伴是这样做的,首先拿出规格和属性
 21 | ```
 22 | "选择颜色": [
 23 |    {
 24 |           "name": "银色",
 25 |           "id": 75
 26 |    }
 27 | ],
 28 | "选择版本": [
 29 |    {
 30 |           "name": "公开版",
 31 |           "id": 77
 32 |    },
 33 |    {
 34 |           "name": "【原厂延保版】",
 35 |           "id": 78
 36 |    }  
 37 | ],
 38 | "内存": [
 39 |    {
 40 |           "name": "64G",
 41 |           "id": 82
 42 |    },
 43 |    {
 44 |           "name": "256G",
 45 |           "id": 83
 46 |    }
 47 | ],
 48 |       
 49 | ```
 50 | 没错,你没有看错,实际就是将规格作为key,属性作为value。将value[id]取出,进行拼接即可查询到对应的SKU了。
 51 | ```
 52 | 响应参数[规格名称][属性编码] = 拼接SKU串的必需品
 53 | ```
 54 | 规格相当于一个分组,属性其实也是拼接SKU的重要组成部分,上述数据为例
 55 | ```
 56 | 75_77_82 = 银色,公开版,64G
 57 | ```
 58 | 
 59 | # 接口文档
 60 | ## 请求地址
 61 | ``/v1/product/{productId}``
 62 | ## 请求类型
 63 | `` GET ``
 64 | ## 请求参数
 65 | | 参数 | 类型 | 默认值 | 说明 |
 66 | | -- | -- | -- | -- |
 67 | | productId | int | 0 | 商品编码 |
 68 | 
 69 | ## 响应示例
 70 | ```
 71 | {
 72 |   "code": 200,
 73 |   "message": "获取成功",
 74 |   "data": {
 75 |     "id": 131,
 76 |     "name": "Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机",
 77 |     "price": "8388.00",
 78 |     "market_price": "8388.00",
 79 |     "sketch": "IPhone大法好,打九折,打九折,快剁手",
 80 |     "intro": "这是商品描述",
 81 |     "keywords":['苹果','iphone'],
 82 |     "attribute": {
 83 |       "选择颜色": [
 84 |         {
 85 |           "name": "银色",
 86 |           "id": 75
 87 |         },
 88 |         {
 89 |           "name": "深空灰色",
 90 |           "id": 76
 91 |         }
 92 |       ],
 93 |       "选择版本": [
 94 |         {
 95 |           "name": "公开版",
 96 |           "id": 77
 97 |         },
 98 |         {
 99 |           "name": "【原厂延保版】",
100 |           "id": 78
101 |         },
102 |         {
103 |           "name": "双网通版",
104 |           "id": 79
105 |         },
106 |         {
107 |           "name": "无线充套装",
108 |           "id": 80
109 |         },
110 |         {
111 |           "name": "Airpods套装",
112 |           "id": 81
113 |         }
114 |       ],
115 |       "内存": [
116 |         {
117 |           "name": "64G",
118 |           "id": 82
119 |         },
120 |         {
121 |           "name": "256G",
122 |           "id": 83
123 |         }
124 |       ],
125 |       "购买方式": [
126 |         {
127 |           "name": "官方标配",
128 |           "id": 84
129 |         },
130 |         {
131 |           "name": "移动优惠购",
132 |           "id": 85
133 |         },
134 |         {
135 |           "name": "电信优惠购",
136 |           "id": 86
137 |         },
138 |         {
139 |           "name": "联通优惠购",
140 |           "id": 87
141 |         }
142 |       ]
143 |     },
144 |     "album": [
145 |       {
146 |         "id": 2,
147 |         "name": "这是第一张图片",
148 |         "url": "http://xxx.com/59ec33eaN6ddb0c54.jpg"
149 |       },
150 |       {
151 |         "id": 3,
152 |         "name": "这是第二张图片",
153 |         "url": "http://xxx.com/59ec3400Nce4cc116.jpg"
154 |       }
155 |     ],
156 |     "radio": {
157 |       "id": 1,
158 |       "name": "这是一个视频",
159 |       "url": "http://xxx.com/1.mp4"
160 |     },
161 |     "sku": {
162 |       "75_77_82_84": {
163 |         "id": 1018,
164 |         "name": "选择颜色:银色;选择版本:公开版;内存:64G;购买方式:官方标配;",
165 |         "price": "8388.00",
166 |         "stock": 83888388
167 |       },
168 |       "75_77_82_85": {
169 |         "id": 1019,
170 |         "name": "选择颜色:银色;选择版本:公开版;内存:64G;购买方式:移动优惠购;",
171 |         "price": "8388.00",
172 |         "stock": 83888388
173 |       },
174 |       "75_77_82_86": {
175 |         "id": 1020,
176 |         "name": "选择颜色:银色;选择版本:公开版;内存:64G;购买方式:电信优惠购;",
177 |         "price": "8388.00",
178 |         "stock": 83888388
179 |       },
180 |       "75_77_82_87": {
181 |         "id": 1021,
182 |         "name": "选择颜色:银色;选择版本:公开版;内存:64G;购买方式:联通优惠购;",
183 |         "price": "8388.00",
184 |         "stock": 83888388
185 |       },
186 |       "75_77_83_84": {
187 |         "id": 1022,
188 |         "name": "选择颜色:银色;选择版本:公开版;内存:256G;购买方式:官方标配;",
189 |         "price": "8388.00",
190 |         "stock": 83888388
191 |       },
192 |       "75_77_83_85": {
193 |         "id": 1023,
194 |         "name": "选择颜色:银色;选择版本:公开版;内存:256G;购买方式:移动优惠购;",
195 |         "price": "8388.00",
196 |         "stock": 83888388
197 |       }
198 |     }
199 |   }
200 | }
201 | ```
202 | 
203 | ## 响应参数说明
204 | 
205 | ### 核心参数
206 | | 	参数	|	类型	|	默认值	|	说明	|
207 | |:----------|:--------------|:-----------|:------------------|
208 | |	id	|	int	|	0	|	商品编码	|
209 | |  name |  string | - | 商品标题 |
210 | | price | double | 00.00 | 商品价格|
211 | | keywords| string| - | 商品关键字 |
212 | | market_price | double | 00.00|  市场价格|
213 | | virtual | int | 0| 虚拟销量|
214 | | sketch | string |-| 商品简述|
215 | | intro | string | - | 商品详情 |
216 | 
217 | ### 商品图参数
218 | | 	参数	|	类型	|	默认值	|	说明	|
219 | |:----------|:--------------|:-----------|:------------------|
220 | | album[] | array |[] | 商品轮播图 |
221 | | id |  int | 0 | 资源编码 |
222 | | name | string | - | 图片名称|
223 | | url | string | - | 资源路径 |
224 | 
225 | ### 商品视频参数
226 | > 无视频则返回 []
227 | 
228 | | 	参数	|	类型	|	默认值	|	说明	|
229 | |:----------|:--------------|:-----------|:------------------|
230 | | radio[] | array | []| 商品视频 |
231 | | id |  int |0| 资源编码 |
232 | | name | string | -| 视频名称|
233 | | url | string |- | 资源路径 |
234 | 
235 | ### 商品规格/属性参数
236 | | 	参数	|	类型	|	默认值	|	说明	|
237 | |:----------|:--------------|:-----------|:------------------|
238 | | attribute | array[] | [] | 商品属性 |
239 | | [(attr_name)] [] | array[] | []| 属性名称 |
240 | | name | string |-| 属性项名称|
241 | | id | int | 0| 属性项编码 |
242 | 
243 | 
244 | ### 商品SKU参数
245 | | 	参数	|	类型	|	默认值	|	说明	|
246 | |:----------|:--------------|:-----------|:------------------|
247 | | sku[] | array[] | [] | 商品sku |
248 | | [(option_id)] [] | array[] | | 商品SKU查询办法为 attribute[(attr_name)]['id'] 拼接 |
249 | | id | int | 0 | sku编码|
250 | | name | string | - | sku 名称  |
251 | | price | double |00.00| 商品价格 |
252 | | stock | int |0 | 商品库存|
253 | 
254 | 
255 | # 致谢
256 | 字不在多,讲清楚就行,感谢你看到这里,希望本篇文章可以帮助到你,有疑问可以在评论区讨论,谢谢。


--------------------------------------------------------------------------------
/Shop/电商系统设计之用户系统.md:
--------------------------------------------------------------------------------
  1 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3459999803-5b232b2aa59fc_articlex.jpeg)
  2 | 
  3 | > 电商大伙每天都在用,类似某猫,某狗等。
  4 | > 电商系统设计看似复杂又很简单,看似简单又很复杂
  5 | > 本章适合初级工程师及中级工程师细看,大佬请随意
  6 | 
  7 | # 前言
  8 | 设计以以下为工具讲起
  9 | - PHP为开发语言
 10 | - 基于Laravel框架
 11 | - MySQL为数据存储
 12 | 
 13 | 
 14 | 电商的可变性与孩子的心情一样,变化极快,所以在设计之处就要想好大部分的功能接入及开发,尽量减少重构次数。对老板来说节约成本,对程序员来说“珍惜生命”
 15 | 
 16 | # 数据表
 17 | 前期业务简单时,我们可以将数据表设计为下列的样子
 18 | 
 19 | 
 20 | | TableName| Comments |
 21 | | :-- | :-- |
 22 | | member | 用户表 |
 23 | | member_address | 收货地址表 |
 24 | | member_card | 银行卡表 | 
 25 | | member_cart | 购物车表 | 
 26 | | member_cart_item | 购物车商品表 | 
 27 | | member_collect_product | 商品收藏表 |
 28 | | member_collect_supplier | 店铺收藏表 |
 29 | | member_data | 用户信息表 |
 30 | | member_query_history | 用户搜索历史表 |
 31 | | member_wallet | 用户账户表 |
 32 | | member_withdrawal | 用户提现表 |
 33 | 
 34 | ## 用户表
 35 | 考虑到多种登录方式,应在数据表中涉及到微信的openid,unionid,支付宝、QQ的用户token等,这些要在前期就涉及进去,因后期用户量大了之后加一个字段简直是噩梦,用户状态status也必不可少,比较人也是分好坏,其次就是创建时间,登录时间等,用户表与用户信息表绝逼是绑定关系,这就不多言了。
 36 | ```
 37 | CREATE TABLE `member` (
 38 |   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 39 |   `tel` bigint(20) DEFAULT NULL COMMENT '手机号码',
 40 |   `password` varchar(555) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '登录密码',
 41 |   `wx_token` varchar(125) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '微信TOKEN',
 42 |   `im_token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户融云token',
 43 |   `open_id` varchar(125) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
 44 |   `status` enum('1','-1') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '账号状态',
 45 |   `created_at` timestamp NULL DEFAULT NULL,
 46 |   `updated_at` timestamp NULL DEFAULT NULL,
 47 |   PRIMARY KEY (`id`),
 48 |   UNIQUE KEY `member_tel_unique` (`tel`),
 49 |   UNIQUE KEY `member_wx_token_unique` (`wx_token`)
 50 | ) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 51 | ```
 52 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/3334194934-5b228c8a611cd_articlex.png)
 53 | 
 54 | ## 收货地址表
 55 | 收货地址与用户是一一相对的,在设计上增加需要的字段即可,例如 收货人、收货人手机号、城市、详细地址等
 56 | ```
 57 | CREATE TABLE `member_address` (
 58 |   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 59 |   `member_id` int(11) NOT NULL COMMENT '用户编号',
 60 |   `nick_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收货人姓名',
 61 |   `tel` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号码',
 62 |   `prov` int(11) DEFAULT NULL COMMENT '省',
 63 |   `city` int(11) NOT NULL COMMENT '市',
 64 |   `area` int(11) DEFAULT NULL COMMENT '区',
 65 |   `address` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '街道地址',
 66 |   `number` int(11) NOT NULL COMMENT '邮政编码',
 67 |   `default` enum('0','1') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '默认收货地址 1=>默认',
 68 |   `deleted_at` timestamp NULL DEFAULT NULL,
 69 |   `created_at` timestamp NULL DEFAULT NULL,
 70 |   `updated_at` timestamp NULL DEFAULT NULL,
 71 |   PRIMARY KEY (`id`)
 72 | ) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 73 | ```
 74 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/815627702-5b228d261e2a3_articlex.png)
 75 | 
 76 | ## 银行卡表
 77 | 用于用户提现的业务等,大致将银行卡所需的信息记录即可,例如持卡人、卡号、归属银行等
 78 | ```
 79 | CREATE TABLE `member_card` (
 80 |   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 81 |   `member_id` int(11) NOT NULL COMMENT '用户编码',
 82 |   `card_name` varchar(25) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '持卡人姓名',
 83 |   `card_number` varchar(25) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '银行卡号',
 84 |   `created_at` timestamp NULL DEFAULT NULL,
 85 |   `updated_at` timestamp NULL DEFAULT NULL,
 86 |   PRIMARY KEY (`id`),
 87 |   UNIQUE KEY `member_card_card_number_unique` (`card_number`)
 88 | ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 89 | ```
 90 | ![](https://blog.fastrun.cn/wp-content/uploads/2018/07/2566994358-5b228d791c044_articlex.png)
 91 | 
 92 | ## 购物车表
 93 | 为何单独建这个表,也是又一定原因的,正常只需要member_cart_item表即可,根据实际下线的业务场景,正常购物到超市需要拿一个购物车,但这个购物车并非属于你,你使用之后,需要归还,他人可继续使用,将购物车公开化,并不是将购物车商品公开化。业务场景比较窄,例如京东到家和京东商城一样(我只是举例,并不清楚他们怎么做的),购物车不通用,那如何区分呢,是应该在购物车上区分还是在购物车商品上区分?我想你已经清楚了。
 94 | ```
 95 | CREATE TABLE `member_cart` (
 96 |   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 97 |   `member_id` int(11) NOT NULL COMMENT '用户编码',
 98 |   `created_at` timestamp NULL DEFAULT NULL,
 99 |   `updated_at` timestamp NULL DEFAULT NULL,
100 |   PRIMARY KEY (`id`),
101 |   UNIQUE KEY `member_cart_member_id_unique` (`member_id`),
102 |   KEY `member_cart_member_id_index` (`member_id`)
103 | ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
104 | ```
105 | ## 购物车商品表
106 | 这块需要提的一点是 [并不是所有表的设计都是互相绑定,互相依赖的],就例如购物车商品表,不仅仅将商品编码存储在内,还要将商品价格,商品的简介以及商品的规格(既SKU)存储,不能因卖家下架商品,而查询不到商品的存在,比较一切以用户为主,用户是上帝的原则,不能让商品悄悄的就消失了吧。所以在做购物车商品表查询时,切记不要使用join或者表关联查询
107 | ```
108 | CREATE TABLE `member_cart_item` (
109 |   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
110 |   `cart_id` int(11) NOT NULL COMMENT '购物车编码',
111 |   `product_desc` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品sku信息',
112 |   `product_img` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品快照',
113 |   `product_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品名称',
114 |   `price` decimal(8,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
115 |   `product_id` int(11) NOT NULL COMMENT '商品编码',
116 |   `supplier_id` int(11) NOT NULL COMMENT '店铺编码',
117 |   `sku_id` int(11) NOT NULL COMMENT '商品sku编码',
118 |   `number` int(11) NOT NULL DEFAULT '1' COMMENT '商品数量',
119 |   `created_at` timestamp NULL DEFAULT NULL,
120 |   `updated_at` timestamp NULL DEFAULT NULL,
121 |   PRIMARY KEY (`id`),
122 |   KEY `member_cart_item_cart_id_product_id_supplier_id_index` (`cart_id`,`product_id`,`supplier_id`)
123 | ) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
124 | ```
125 | ## 用户搜索历史表
126 | 用户搜索的记录是一定要有的,为了未来的数据分析,智能推荐做准备,毕竟现在是信息共享的时代嘛~
127 | ```
128 | CREATE TABLE `member_query_history` (
129 |   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
130 |   `member_id` int(11) NOT NULL COMMENT '用户编码',
131 |   `keyword` varchar(125) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关键字',
132 |   `created_at` timestamp NULL DEFAULT NULL,
133 |   `updated_at` timestamp NULL DEFAULT NULL,
134 |   PRIMARY KEY (`id`)
135 | ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
136 | ```
137 | # 数据记录
138 | 有很多场景,都要将标题呀,内容呀直接存储,类似与收藏的店铺和商品,无论卖家怎么做,用户购物车,订单不能动,这是基准。
139 | 
140 | # 致谢
141 | 感谢你们看到这里,下一篇我会讲一下关于电商系统的商品设计的部分。有什么问题可以评论区提问。谢谢


--------------------------------------------------------------------------------
/PHP/来!狂撸一款PHP现代化框架 (路由的设计).md:
--------------------------------------------------------------------------------
  1 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/12/4149909193-5c0898e0c4a99_articlex.png)
  2 | 
  3 | # 前言
  4 | 上一篇的标题改了一下,以一、二、三为章节对读者来说是种困扰,现在的标题是依照项目进度来编写的。上篇文章地址为 https://segmentfault.com/a/1190000017278828
  5 | 
  6 | 这一系列文章并不准备写太多章节,大概规划的只有4~5章左右,具体实现代码还请移步Github
  7 | https://github.com/CrazyCodes/z_framework
  8 | 
  9 | 本章详细讲解一下Route(路由的实现),Come on Up Image
 10 | 
 11 | 
 12 | ![](https://resources.blog.fastrun.cn/wp-content/uploads/2018/12/563696082-5c1318a81c42a_articlex.png)
 13 | 
 14 | 上图大概说明了实现路由要经过两个步骤
 15 | - 将所有路由信息存储到超全局变量中
 16 | - 用户请求时从全局变量中查找路由映射的服务脚本并实例化
 17 | 
 18 | OK,大概流程就是酱紫,下面开始“撸”
 19 | 
 20 | # 目录
 21 | 路由的代码暂分为以下几个文件(这并不是确定的,详细可查看Github)
 22 | 
 23 | | 文件名 | 注释 |
 24 | | -- | -- |
 25 | | Route | 转发文件:为实现 Route::get 效果 |
 26 | | RouteCollection | 路由信息处理存储 |
 27 | | RouteInterface | 无需解释 |
 28 | | RouteModel | 路由模型,将每个路由信息以结构体方式存储到$_SERVER |
 29 | | Router | 路由的核心类 |
 30 | 
 31 | 莫急,我们一个一个文件来看。先从RouteInterface开始
 32 | 
 33 | # RouteInterface
 34 | 参照RESTful规定设定接口方法分别为 GET、POST、PATCH、PUT、DELETE、OPTIONS,当然Laravel也是规范了以上标准请求。
 35 | 
 36 | GitHub : https://github.com/CrazyCodes/z_framework/blob/master/src/Zero/ZeroInterface.php
 37 | ```
 38 | interface RouteInterface
 39 | {
 40 |     /**
 41 |      * @param      $uri
 42 |      * @param null $action
 43 |      *
 44 |      * @return mixed
 45 |      */
 46 |     public function get($uri, $action = null);
 47 | 
 48 |     /**
 49 |      * @param      $uri
 50 |      * @param null $action
 51 |      *
 52 |      * @return mixed
 53 |      */
 54 |     public function post($uri, $action = null);
 55 | 
 56 |     /**
 57 |      * @param      $uri
 58 |      * @param null $action
 59 |      *
 60 |      * @return mixed
 61 |      */
 62 |     public function patch($uri, $action = null);
 63 | 
 64 |     /**
 65 |      * @param      $uri
 66 |      * @param null $action
 67 |      *
 68 |      * @return mixed
 69 |      */
 70 |     public function put($uri, $action = null);
 71 | 
 72 |     /**
 73 |      * @param      $uri
 74 |      * @param null $action
 75 |      *
 76 |      * @return mixed
 77 |      */
 78 |     public function delete($uri, $action = null);
 79 | 
 80 |     /**
 81 |      * @param      $uri
 82 |      * @param null $action
 83 |      *
 84 |      * @return mixed
 85 |      */
 86 |     public function options($uri, $action = null);
 87 | }
 88 | ```
 89 | # Router
 90 | 先写一个栗子
 91 | ```
 92 | public function get($uri, $action = null)
 93 | {
 94 |     return $this->addRoute("GET", $uri, $action);
 95 | }
 96 | ```
 97 | 用户调用下方代码会指向上述方法,方法既调用addRoute方法将路由信息存储到$_SERVER中
 98 | ```
 99 | Route::get('/','Controller')
100 | ```
101 | 以下为addRoute部分的代码
102 | ```
103 | public function addRoute($methods, $uri, $action)
104 | {
105 |     // 这里判断请求方式是否合规,既是否存在  GET、POST、PATCH、PUT、DELETE、OPTIONS其中之一
106 |     if ($this->verify($methods) == false) {
107 |         return false;
108 |     }
109 |     
110 |     // 之后我们去往RouteCollection路由信息的处理类中
111 |     return $this->routes->add($uri, $this->createRoute($methods, $action));
112 | }
113 | ```
114 | 
115 | # RouteCollection
116 | 最终达到 add 方法,将路由信息存储到$_SERVER中
117 | ```
118 | public function add($uri, RouteModel $model)
119 | {
120 |     if (empty($_SERVER["routes"][$uri])) {
121 |         $_SERVER["routes"][$uri] = $model;
122 |     }
123 | }
124 | ```
125 | 第二个参数RouteModel开始我们说过这是路由模型,将每个路由以结构体的方式存储到变量中,存储后的结果
126 | ```
127 | 'routes' =>
128 |   array(6) {
129 |     'test/get' =>
130 |     class Zero\Routing\RouteModel#13 (2) {
131 |       public $method =>
132 |       string(3) "GET"
133 |       public $action =>
134 |       string(19) "testController@test"
135 |     }
136 |     'test/post' =>
137 |     class Zero\Routing\RouteModel#14 (2) {
138 |       public $method =>
139 |       string(4) "POST"
140 |       public $action =>
141 |       string(19) "testController@test"
142 |     }
143 |     'test/put' =>
144 |     class Zero\Routing\RouteModel#15 (2) {
145 |       public $method =>
146 |       string(3) "PUT"
147 |       public $action =>
148 |       string(18) "testController@put"
149 |     }
150 |     'test/del' =>
151 |     class Zero\Routing\RouteModel#16 (2) {
152 |       public $method =>
153 |       string(6) "DELETE"
154 |       public $action =>
155 |       string(18) "testController@del"
156 |     }
157 |     'test/patch' =>
158 |     class Zero\Routing\RouteModel#17 (2) {
159 |       public $method =>
160 |       string(5) "PATCH"
161 |       public $action =>
162 |       string(20) "testController@patch"
163 |     }
164 |     'test/opt' =>
165 |     class Zero\Routing\RouteModel#18 (2) {
166 |       public $method =>
167 |       string(7) "OPTIONS"
168 |       public $action =>
169 |       string(18) "testController@opt"
170 |     }
171 |   }
172 | ```
173 | # Route
174 | 最后通过__callStatic将代码重定向到核心类中
175 | ```
176 | public static function __callStatic($name, $arguments)
177 | {
178 |     $router = new Router;
179 |     
180 |     return $router->{$name}($arguments[0], $arguments[1]);
181 | }
182 | ```
183 | 
184 | 上述套路部分是Laravel的设计思想,通过这款简单的框架可对Laravel核心设计有丁点的理解。
185 | # 测试
186 | 测试上次做的有点糙,从本章到系列结束,我们都以PHPunit来测试。
187 | ```
188 | /**
189 |  * @content tests all methods storage -> $_SERVER["routes"]
190 |  */
191 | public function testAllMethodsStorage()
192 | {
193 |     $this->routes->get($methodGet = "test/get", "testController@test");
194 |     $this->assertArrayHasKey($methodGet, $_SERVER[$this->methodsDataKey]);
195 |     
196 |     $this->routes->post($methodPost = "test/post", "testController@test");
197 |     $this->assertArrayHasKey($methodPost, $_SERVER[$this->methodsDataKey]);
198 |     
199 |     $this->routes->put($methodPut = "test/put", "testController@put");
200 |     $this->assertArrayHasKey($methodPut, $_SERVER[$this->methodsDataKey]);
201 |     
202 |     $this->routes->delete($methodDel = "test/del", "testController@del");
203 |     $this->assertArrayHasKey($methodDel, $_SERVER[$this->methodsDataKey]);
204 |     
205 |     $this->routes->patch($methodPatch = "test/patch", "testController@patch");
206 |     $this->assertArrayHasKey($methodPatch, $_SERVER[$this->methodsDataKey]);
207 |     
208 |     $this->routes->options($methodOpt = "test/opt", "testController@opt");
209 |     $this->assertArrayHasKey($methodOpt, $_SERVER[$this->methodsDataKey]);
210 | }
211 | ```
212 | 上述贴出部分代码,以过程化的方法去测试。查看存储是否符合预期。
213 | 
214 | ```
215 | /**
216 |  * @content RouteModel Success
217 |  */
218 | public function testCreateRoute()
219 | {
220 |     $response = $this->routes->createRoute("GET", "TestController@Get");
221 |     
222 |     $this->assertInstanceOf(RouteModel::class, $response);
223 | }
224 | ```
225 | 包括测试对路由创建后是否为RouteModel的实现。具体可查看Github
226 | https://github.com/CrazyCodes/z_framework/tree/master/tests/Routing
227 | # 致谢
228 | 上述已完成了路由的基本设计,下一章将讲解从启动到请求路由映射到服务脚本的过程。
229 | 
230 | 希望本章可以帮到你,谢谢。
231 | 


--------------------------------------------------------------------------------