-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 77.6 KB
/
Copy pathcontent.json
File metadata and controls
1 lines (1 loc) · 77.6 KB
1
{"pages":[{"title":"Vincent.Cheng","text":"个人简介06年进入互联网行业,至今已有十余年。曾开创模板建站项目、担任网络开发公司技术合伙人、游戏开发工程师等等,时任科技控股公司网络软件技术部支持(😂算是接触一下物联行业吧)。 热爱开发,精通 NodeJS 与 PHP 开发,架构设计及优化,喜欢研究新技术、擅长迅速学习并运用技术语言、能够快速处理代码编写过程中出现的各种问题。 工作认真负责,充满热情,爱好挑战,勇于创新,爱好阅读,注重横向知识面的拓展。 部分作品 潮宏基珠宝小店小程序 铂金港湾小程序 潮汕茶事小程序 移动竞速 H5 游戏 让候鸟飞公益宣传 - H5 开发 屏幕截图 chrome 浏览器插件","link":"/about/index.html"},{"title":"tags","text":"","link":"/tags/index.html"},{"title":"portfolio","text":"logo all design development home about Case 高端个性定制,智能响应覆盖全网终端,全网数据同步统一管理 小橙子科技官方网站 website 移动竞速 cocos creator / game 潮汕茶事小程序 mini app 紫藤官方网站 website 2 2 2 2","link":"/portfolio/index.html"}],"posts":[{"title":"屏幕截图 chrome 浏览器插件","text":"Platform:PCDate:2018.11.21Description: 这是一个 chrome 浏览器的插件 Link: github","link":"/portfolio/development/capture-chrome-plugin/"},{"title":"铂金港湾 - 小程序开发","text":"Platform:微信小程序Date:2019.4.25Description: 该项目是一个 B2B 的电子商务平台的微信小程序版本,使用了 NodeJS + Express + MySQL 技术搭建。具体包含了以下功能:商户部分的:商家入驻、发布服务、受理订单、在线客服等会员部分的:手机定位、距离筛选、服务分类查看、服务列表查看、服务详情查看、购物车、购物订单、在线支付等 预览 & 体验","link":"/portfolio/development/bjgw-wechat-mini-application/"},{"title":"Apache Bench (ab) 使用说明","text":"ApacheBench 是 Apache 软件基金会的一个开放源代码的网页服务器软件,可以在大多数电脑操作系统中运行。它是一个指令列程式,专门用来测试网站服务器的运行效能。 使用方法1ab [options] [http://]hostname[:port]/path Options 参数命令|参数|说明 ---|:--:|---: -n|requests|Number of requests to perform -c|concurrency|Number of multiple requests to make at a time -t|timelimit|Seconds to max. to spend on benchmarking, This implies -n 50000 -s|timeout|Seconds to max. wait for each responseDefault is 30 seconds -b|windowsize|Size of TCP send/receive buffer, in bytes -B|address|Address to bind to when making outgoing connections -p|postfile|File containing data to POST. Remember also to set -T -u|putfile|File containing data to PUT. Remember also to set -T -T|content-type|Content-type header to use for POST/PUT data, eg. 'application/x-www-form-urlencoded'Default is 'text/plain' -v|verbosity|How much troubleshooting info to print -w||Print out results in HTML tables -i||Use HEAD instead of GET -x|attributes|String to insert as table attributes -y|attributes|String to insert as tr attributes -z|attributes|String to insert as td or th attributes -C|attribute|Add cookie, eg. 'Apache=1234'. (repeatable) -H|attribute|Add Arbitrary header line, eg. 'Accept-Encoding: gzip' Inserted after all normal header lines. (repeatable) -A|attribute|Add Basic WWW Authentication, the attributes are a colon separated username and password. -P|attribute|Add Basic Proxy Authentication, the attributes are a colon separated username and password. -X|proxy:port|Proxyserver and port number to use -V||Print version number and exit -k||Use HTTP KeepAlive feature -d||Do not show percentiles served table. -S||Do not show confidence estimators and warnings. -q||Do not show progress when doing more than 150 requests -l||Accept variable document length (use this for dynamic pages) -g|filename|Output collected data to gnuplot format file. -e|filename|Output CSV file with percentages served -r||Don't exit on socket receive errors. -m|method|Method name -h||Display usage information (this message) 命令 参数 说明 -n requests 要执行的请求数 -c concurrency 同一时间发出的请求个数(并发数) -t timelimit 在基准测试上花费最多的秒数,这意味着-n 50000 -s timeout 等待每个响应的最长秒数默认:30秒 -b windowsize TCP发送/接收缓冲区的大小(字节) -B address 进行传出连接时要绑定到的地址 -p postfile 用 POST 发送文件,请设置-T -u putfile 用 PUT 发送文件,请设置-T -T content-type Content-type header to use for POST/PUT data默认:'text/plain' -v verbosity 要打印多少故障排除信息 -w 在 HTML 表格中打印出结果 -i 使用 head 替代 get -x attributes 要作为表属性插入的字符串 -y attributes 要作为tr属性插入的字符串 -z attributes 要作为td或th属性插入的字符串 -C attribute 添加cookie,例如“apache=1234”。(可重复) -H attribute 添加任意标题行,例如在所有普通标题行后插入“accept encoding:gzip”。(可重复) -A attribute 添加基本的WWW认证,属性是以冒号分隔的用户名和密码。 -P attribute 添加基本代理身份验证,属性是以冒号分隔的用户名和密码。 -X proxy:port 要使用的代理服务器和端口号 -V 打印版本号并退出 -k 使用 HTTP 保持连接功能 -d 不要显示服务台的百分比。 -S 不要显示置信估计值和警告。 -q 执行超过150个请求时不显示进度 -l 接受可变文档长度(用于动态页面) -g filename 将收集的数据输出到gnuplot格式文件。 -e filename 输出提供百分比的csv文件 -r 在套接字接收错误时不退出 -m method 方法名 -h 显示使用信息(this message) 例子解析123456789101112131415161718192021222324Benchmarking 10.0.0.146Completed 1000 requests...Finished 10000 requests(已经完成的请求)Server Software: # 服务器软件Server Hostname: # 服务器 IP 地址Server Port: # 服务器端口Document Path: # 文件路径Document Length: # 文件大小Concurrency Level: # 并发等级Time taken for tests: # 测试用时(秒)Complete requests: # 完成的请求数Failed requests: # 失败的请求数Total transferred: # 整个场景中的网络传输量HTML transferred: # 整个场景中的HTML内容传输量Requests per second: # 吞吐率,相当于 LR 中的每秒事务数Time per request: # 用户平均请求等待时间,相当于 LR 中的平均事务响应时间Time per request: # 每个连接请求实际运行时间的平均值Transfer rate: # 平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题 Win 系统Win 系统需要到 Apache 官网下载软件包。 Mac 系统Mac 系统自带 Apache Linux 系统123user@ubuntu:~$ ab程序 "ab" 尚未安装。您可以使用以下命令安装:user@ubuntu:~$ sudo apt-get install apache2-utils 关于登录的问题有时候进行压力测试需要用户登录,怎么办?请参考以下步骤: 先用账户和密码登录后,用开发者工具找到标识这个会话的Cookie值(Session ID)记下来如果只用到一个Cookie,那么只需键入命令:ab -n 100 -C key=value http://test.com/ 如果需要多个Cookie,就直接设Header:ab -n 100 -H “Cookie: Key1=Value1; Key2=Value2” http://test.com/","link":"/tutorial/apache-bench-manual/"},{"title":"潮汕茶事 - 小程序开发","text":"Platform:支付宝小程序Date:2019.3.31Description: 细节图片和网址稍后补上。","link":"/portfolio/development/chaoshan-tea-ali-mini-application/"},{"title":"Chrome 浏览器插件开发全攻略","text":"Chrome 插件2.1. 什么是Chrome插件严格来讲,我们正在说的东西应该叫Chrome扩展(Chrome Extension),真正意义上的Chrome插件是更底层的浏览器功能扩展,可能需要对浏览器源码有一定掌握才有能力去开发。鉴于Chrome插件的叫法已经习惯,本文也全部采用这种叫法,但读者需深知本文所描述的Chrome插件实际上指的是Chrome扩展。 Chrome插件是一个用Web技术开发、用来增强浏览器功能的软件,它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包。 个人猜测crx可能是 Chrome Extension 如下3个字母的简写: 另外,其实不只是前端技术,Chrome插件还可以配合C++编写的dll动态链接库实现一些更底层的功能(NPAPI),比如全屏幕截图。 由于安全原因,Chrome浏览器42以上版本已经陆续不再支持NPAPI插件,取而代之的是更安全的PPAPI。 2.2. 学习 Chrome 插件开发有什么意义2.3. 为什么是 Chrome 插件而不是 Firefox 插件 开发与调试Chrome插件没有严格的项目结构要求,只要保证本目录有一个manifest.json即可,也不需要专门的IDE,普通的web开发工具即可。 从右上角菜单->更多工具->扩展程序可以进入 插件管理页面,也可以直接在地址栏输入 chrome://extensions 访问。 勾选开发者模式即可以文件夹的形式直接加载插件,否则只能安装.crx 格式的文件。Chrome 要求插件必须从它的Chrome应用商店安装,其它任何网站下载的都无法直接安装,所以,其实我们可以把crx文件解压,然后通过开发者模式直接加载。 开发中,代码有任何改动都必须重新加载插件,只需要在插件管理页按下Ctrl+R 即可,以防万一最好还把页面刷新一下。 核心介绍4.1. manifest.json这是一个Chrome插件最重要也是必不可少的文件,用来配置所有和插件相关的配置,必须放在根目录。其中,manifest_version、name、version3个是必不可少的,description和icons是推荐的。 下面给出的是一些常见的配置项,均有中文注释,完整的配置文档请戳这里。 4.2. content-scripts所谓content-scripts,其实就是Chrome插件中向页面注入脚本的一种形式(虽然名为script,其实还可以包括css的),借助content-scripts我们可以实现通过配置的方式轻松向指定页面注入JS和CSS(如果需要动态注入,可以参考下文),最常见的比如:广告屏蔽、页面CSS定制,等等。 4.3. background4.4. event-pages4.5. popup4.6. injected-script4.7. homepage_url Chrome 插件的8种展示形式5.1. browserAction(浏览器右上角)5.2. pageAction(地址栏右侧)5.3. 右键菜单5.4. override(覆盖特定页面)5.5. devtools(开发者工具) 5种类型的 JS 对比Chrome插件的JS主要可以分为这5类:injected script、content-script、popup js、background js和devtools js,6.1. 权限对比6.2. 调试方式对比 消息通信通信主页:https://developer.chrome.com/extensions/messaging 前面我们介绍了Chrome插件中存在的5种JS,那么它们之间如何互相通信呢?下面先来系统概况一下,然后再分类细说。需要知道的是,popup和background其实几乎可以视为一种东西,因为它们可访问的API都一样、通信机制一样、都可以跨域。 其它补充8.1. 动态注入或执行JS8.2. 动态注入CSS8.3. 获取当前窗口ID8.4. 获取当前标签页ID8.5. 本地存储8.6. webRequest8.7. 国际化8.8. API总结 经验总结9.1. 查看已安装插件路径9.2. 特别注意background 的报错9.3. 如何让popup页面不关闭9.4. 不支持内联JavaScript 的执行9.5. 注入CSS的时候必须小心 打包与发布打包的话直接在插件管理页有一个打包按钮: 然后会生成一个.crx文件,要发布到Google应用商店的话需要先登录你的Google账号,然后花5个$注册为开发者,本人太穷,就懒得亲自验证了,有发布需求的自己去整吧。 参考 源文出自:小茗同学博客,小篇幅转载,方便查阅!","link":"/tutorial/plugin/chrome-plugin-develop-directions/"},{"title":"广东潮宏基实业 - 小程序开发","text":"Platform:微信小程序Date:2018.7.25Description: 该项目是一个可以用于促销活动的购物小程序,由产品分类、产品列表、产品详情、购物车、购物订单、在线支付等功能组成。 😉 该项目的设计是由好友 @牙龈出血 独立完成,他是设计经验超过10年的设计大神,现供职于上市公司设计部,担任主管职务。如果各位看官有设计需求也可以找他。 预览 & 体验","link":"/portfolio/development/chj-mini-shop-wechat-mini-application/"},{"title":"Websocket、Socket、TCP、HTTP 的区别","text":"概述WebSocket 是为了满足基于 Web 的日益增长的实时通信需求而产生的。在传统的 Web 中,要实现实时通信,通用的方式是采用 HTTP 协议不断发送请求。但这种方式即浪费带宽(HTTP HEAD 是比较大的),又消耗服务器 CPU 占用(没有信息也要接受请求)。(下图来自 WebSocket.org) 而是用 WebSocket 技术,则会大幅降低上面提到的消耗:(下图来自 WebSocket.org) 关于更详细的描述,尹立的这篇文章讲得非常好:WebSocket(2)–为什么引入WebSocket协议 。 那么,WebSocket 到底与 HTTP 协议到底是一个什么样的关系呢?它和 Socket 又有什么联系?这就要讲到 OSI 模型和 TCP/IP 协议族。 OSI 模型与 TCP/IP以下是 维基百科 中关于OSI 模型的说明: 开放式系统互联通信参考模型(英语:Open System Interconnection Reference Model,ISO/IEC 7498-1),简称为OSI模型(OSI model),一种概念模型,由国际标准化组织(ISO)提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。 而 TCP/IP 协议可以看做是对 OSI 模型的一种简化(以下内容来自 维基百科): 它将软件通信过程抽象化为四个抽象层,采取协议堆叠的方式,分别实作出不同通信协议。协议套组下的各种协议,依其功能不同,被分别归属到这四个阶层之中7,常被视为是简化的七层OSI模型。 这里有一张图详细介绍了 TCP/IP 协议族中的各个协议在 OSI模型 中的分布,一图胜千言(下图来自 科来): 在这里,我们只需要知道,HTTP、WebSocket 等协议都是处于 OSI 模型的最高层: 应用层 。而 IP 协议工作在网络层(第3层),TCP 协议工作在传输层(第4层)。 至于 OSI 模型的各个层次都有什么系统和它们对应,这里有篇很好的文章可以满足大家的求知欲:OSI七层模型详解 。 WebSocket、HTTP 与 TCP从上面的图中可以看出,HTTP、WebSocket 等应用层协议,都是基于 TCP 协议来传输数据的。我们可以把这些高级协议理解成对 TCP 的封装。 既然大家都使用 TCP 协议,那么大家的连接和断开,都要遵循 TCP 协议中的三次握手和四次握手 ,只是在连接之后发送的内容不同,或者是断开的时间不同。 更详细内容可阅读:wireshark抓包图解 TCP三次握手/四次挥手详解 对于 WebSocket 来说,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。 Socket 与 WebScoketSocket 其实并不是一个协议。它工作在 OSI 模型会话层(第5层),是为了方便大家直接使用更底层协议(一般是 TCP 或 UDP )而存在的一个抽象层。 最早的一套 Socket API 是 Berkeley sockets ,采用 C 语言实现。它是 Socket 的事实标准,POSIX sockets 是基于它构建的,多种编程语言都遵循这套 API,在 JAVA、Python 中都能看到这套 API 的影子。 下面摘录一段更容易理解的文字(来自 http和socket之长连接和短连接区别): Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 主机 A 的应用程序要能和主机 B 的应用程序通信,必须通过 Socket 建立连接,而建立 Socket 连接必须需要底层 TCP/IP 协议来建立 TCP 连接。建立 TCP 连接需要底层 IP 协议来寻址网络中的主机。我们知道网络层使用的 IP 协议可以帮助我们根据 IP 地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过 TCP 或 UPD 的地址也就是端口号来指定。这样就可以通过一个 Socket 实例唯一代表一个主机上的一个应用程序的通信链路了。 而 WebSocket 则不同,它是一个完整的 应用层协议,包含一套标准的 API 。所以,从使用上来说,WebSocket 更易用,而 Socket 更灵活。 HTML5 与 WebSocketWebSocket API 是 HTML5 标准的一部分, 但这并不代表 WebSocket 一定要用在 HTML 中,或者只能在基于浏览器的应用程序中使用。实际上,许多语言、框架和服务器都提供了 WebSocket 支持,例如: 基于 C 的 libwebsocket.org基于 Node.js 的 Socket.io基于 Python 的 ws4py基于 C++ 的 WebSocket++Apache 对 WebSocket 的支持: Apache Module mod_proxy_wstunnelNginx 对 WebSockets 的支持: NGINX as a WebSockets Proxy 、 NGINX Announces Support for WebSocket Protocol 、WebSocket proxyinglighttpd 对 WebSocket 的支持:mod_websocket","link":"/tutorial/different-between-websocket-socket-tcp-http/"},{"title":"CSS3 Flex 布局基本语法","text":"网页布局(layout)是 CSS 的一个重点应用。 布局的传统解决方案,基于盒状模型,依赖 display 属性 + position 属性 + float 属性。它对于那些特殊布局非常不方便,比如,垂直居中 就不容易实现。 2009年,W3C 提出了一种新的方案—-Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。 浏览器支持 IE Firefox Opera Chrome Safari 10+ 22+ 12.1+ 21+ 6.1+ Flex 布局将成为未来布局的首选方案。本文介绍它的语法,下一篇文章给出常见布局的 Flex 写法。 以下内容主要参考了下面两篇文章:A Complete Guide to Flexbox 和 A Visual Guide to CSS3 Flexbox Properties。 一、Flex 布局是什么?Flex 是 Flexible Box 的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为 Flex 布局。123.box{ display: flex;} 行内元素也可以使用 Flex 布局。123.box{ display: inline-flex;} Webkit 内核的浏览器,必须加上-webkit前缀。1234.box{ display: -webkit-flex; /* Safari */ display: flex;} 注意,设为 Flex 布局以后,子元素的 float、clear 和 vertical-align 属性将失效。 二、基本概念采用 Flex 布局的元素,称为 Flex 容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称”项目”。 容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做 main start,结束位置叫做 main end;交叉轴的开始位置叫做 cross start,结束位置叫做 cross end。项目默认沿主轴排列。单个项目占据的主轴空间叫做 main size,占据的交叉轴空间叫做 cross size。 三、容器的属性以下6个属性设置在容器上。123456flex-directionflex-wrapflex-flowjustify-contentalign-itemsalign-content flex-direction 属性flex-direction 属性决定主轴的方向(即项目的排列方向)。123.box { flex-direction: row | row-reverse | column | column-reverse;} 它可能有4个值。1234row(默认值):主轴为水平方向,起点在左端。row-reverse:主轴为水平方向,起点在右端。column:主轴为垂直方向,起点在上沿。column-reverse:主轴为垂直方向,起点在下沿。 flex-wrap 属性默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap 属性定义,如果一条轴线排不下,如何换行。 123.box{ flex-wrap: nowrap | wrap | wrap-reverse;} 它可能取三个值。 nowrap(默认):不换行。 wrap:换行,第一行在上方。 wrap-reverse:换行,第一行在下方。 flex-flowflex-flow 属性是 flex-direction 属性和 flex-wrap 属性的简写形式,默认值为 row nowrap。123.box { flex-flow: <flex-direction> || <flex-wrap>;} justify-content 属性justify-content 属性定义了项目在主轴上的对齐方式。123.box { justify-content: flex-start | flex-end | center | space-between | space-around;} 它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。12345flex-start(默认值):左对齐flex-end:右对齐center: 居中space-between:两端对齐,项目之间的间隔都相等。space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。 align-items属性align-items 属性定义项目在交叉轴上如何对齐。123.box { align-items: flex-start | flex-end | center | baseline | stretch;} 它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。12345flex-start:交叉轴的起点对齐。flex-end:交叉轴的终点对齐。center:交叉轴的中点对齐。baseline: 项目的第一行文字的基线对齐。stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。 align-content属性align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。123.box { align-content: flex-start | flex-end | center | space-between | space-around | stretch;} 该属性可能取6个值。123456flex-start:与交叉轴的起点对齐。flex-end:与交叉轴的终点对齐。center:与交叉轴的中点对齐。space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。stretch(默认值):轴线占满整个交叉轴。 四、项目的属性以下6个属性设置在项目上。123456orderflex-growflex-shrinkflex-basisflexalign-self order 属性order 属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。123.item { order: <integer>;} flex-grow 属性flex-grow 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。123.item { flex-grow: <number>; /* default 0 */} 如果所有项目的 flex-grow 属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的 flex-grow 属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。 flex-shrink 属性flex-shrink 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。123.item { flex-shrink: <number>; /* default 1 */} 如果所有项目的 flex-shrink 属性都为1,当空间不足时,都将等比例缩小。如果一个项目的 flex-shrink 属性为0,其他项目都为1,则空间不足时,前者不缩小。负值对该属性无效。 flex-basis 属性flex-basis 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为 auto,即项目的本来大小。123.item { flex-basis: <length> | auto; /* default auto */} 它可以设为跟 width 或 height 属性一样的值(比如350px),则项目将占据固定空间。 flex 属性flex 属性是 flex-grow, flex-shrink 和 flex-basis 的简写,默认值为 0 1 auto。后两个属性可选。123.item { flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]} 该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。 align-self 属性align-self 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。默认值为 auto,表示继承父元素的align-items 属性,如果没有父元素,则等同于 stretch。123.item { align-self: auto | flex-start | flex-end | center | baseline | stretch;} 该属性可能取6个值,除了auto,其他都与align-items属性完全一致。 源文出自:阮一峰-Flex 布局教程:语法篇,小篇幅转载,方便查阅!","link":"/tutorial/grammar/flex-grammar/"},{"title":"让候鸟飞公益宣传 - H5 开发","text":"Platform:微信 H5Date:2019.2.04Description: H5 动效 + 手机拍照与合成功能。 预览 & 体验","link":"/portfolio/development/dont-eat-birds-h5-application/"},{"title":"Nginx 均衡负载视频教程","text":"三种负载方式 round-robin:请求以轮询,轮转的方式分发到服务器 least-connected:下一个请求被分发到拥有最少活动连接数的服务器 ip-hash:使用一个哈希函数,基于客户端 ip 地址判断下一个请求应该被分发到哪台服务器 配置 Nginx 在 http 节点下,添加 upstream 节点1234upstream web_backend { server [ip:port]; server [ip:port];} 在 server 节点下的 location / 子节点中添加12proxy_set header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_pass http://web_backend; 现在负载均衡初步完成,nginx 按照默认方式进行负载处理,每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器宕机,则 nginx 自动剔除掉。优点:简便、成本低廉、部署简单缺点:可靠性低、负载不均衡适用性:图片服务器或纯静态页面服务器 upstream 其他策略 down 不参与服务器,手动表示服务器宕机1234upstream web_backend { server [ip:port] down; server [ip:port];} weight 权重策略,按比值分配1234upstream web_backend { server [ip:port] weight = 5; server [ip:port] weight = 10;} last-connected 最少连接数策略12345upstream web_backend { least-conn; server [ip:port]; server [ip:port];} ip_hash 来访 IP 策略12345upstream web_backend { ip_hash; server [ip:port]; server [ip:port];} fair 第三方,按服务器响应时间来分配12345upstream web_backend { server [ip:port]; server [ip:port]; fair;} url_hash 第三方,按 url 的 hash 来分配123456upstream web_backend { server [ip:port]; server [ip:port]; hash $request_uri; hash_method crc32;} backup 只有其他服务器宕机才访问该服务器1234upstream web_backend { server [ip:port] backup; server [ip:port];} max_fails 允许请求失败的次数,默认为 1。当超过最大次数时,返回 proxy_next_upstream 模块定义的错误 fail_timeout max_fails 规定的次数达到后,暂停的时间","link":"/tutorial/nginx/http-load-balancing-with-nginx/"},{"title":"前端存储方式总结","text":"本地存储 local storage存储方式:以键值对 (Key-Value) 的方式存储,永久存储,永不失效,除非手动删除。存储大小:每个域名 5M 支持情况: IE Firefox Opera Chrome Safari iPhone Android 8.0+ 3.0+ 10.5+ 4.0+ 4.0+ 2.0+ 2.0+ 注意:IE9 localStorage 不支持本地文件,需要将项目署到服务器,才可以支持! 检测方法:12345if(window.localStorage){ alert('This browser supports localStorage');}else{ alert('This browser does NOT support localStorage');} 常用的API: 方法 作用 getItem 取记录 setIten 设置记录 removeItem 移除记录 key 取key所对应的值 clear 清除记录 存储的内容:数组,图片,json,样式,脚本。。。(只要是能序列化成字符串的内容都可以存储) 本地存储 session storageHTML5 的本地存储 API 中的 localStorage 与 sessionStorage 在使用方法上是相同的,区别在于 sessionStorage 在关闭页面后即被清空,而 localStorage 则会一直保存。 离线缓存 application cache本地缓存应用所需的文件 使用方法: 配置manifest文件 页面上: 1234<!DOCTYPE HTML><html manifest="demo.appcache">...</html> Manifest 文件: manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)。 manifest 文件可分为三个部分: + CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存 + NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存 + FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面) 完整demo: 12345678910CACHE MANIFEST# 2016-07-24 v1.0.0/theme.css/main.jsNETWORK:login.jspFALLBACK:/html/ /offline.html 服务器上:manifest 文件需要配置正确的 MIME-type,即 “text/cache-manifest”。 如 Tomcat: 1234<mime-mapping><extension>manifest</extension><mime-type>text/cache-manifest</mime-type></mime-mapping> 常用API: 核心是 applicationCache 对象,有个 status 属性,表示应用缓存的当前状态:123456(UNCACHED) : 无缓存, 即没有与页面相关的应用缓存(IDLE) : 闲置,即应用缓存未得到更新(CHECKING) : 检查中,即正在下载描述文件并检查更新(DOWNLOADING) : 下载中,即应用缓存正在下载描述文件中指定的资源(UPDATEREADY) : 更新完成,所有资源都已下载完毕(IDLE) : 废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存 相关的事件: 表示应用缓存状态的改变:1234567checking : 在浏览器为应用缓存查找更新时触发error : 在检查更新或下载资源期间发送错误时触发noupdate : 在检查描述文件发现文件无变化时触发downloading : 在开始下载应用缓存资源时触发progress:在文件下载应用缓存的过程中持续不断地下载地触发updateready : 在页面新的应用缓存下载完毕触发cached : 在应用缓存完整可用时触发 Application Cache的三个优势:123离线浏览提升页面载入速度降低服务器压力 注意事项: 浏览器对缓存数据的容量限制可能不太一样(某些浏览器设置的限制是每个站点 5MB) 如果manifest文件,或者内部列举的某一个文件不能正常下载,整个更新过程将视为失败,浏览器继续全部使用老的缓存 引用manifest的html必须与manifest文件同源,在同一个域下 浏览器会自动缓存引用manifest文件的HTML文件,这就导致如果改了HTML内容,也需要更新版本才能做到更新。 manifest文件中CACHE则与NETWORK,FALLBACK的位置顺序没有关系,如果是隐式声明需要在最前面 FALLBACK中的资源必须和manifest文件同源 更新完版本后,必须刷新一次才会启动新版本(会出现重刷一次页面的情况),需要添加监听版本事件。 站点中的其他页面即使没有设置manifest属性,请求的资源如果在缓存中也从缓存中访问 当manifest文件发生改变时,资源请求本身也会触发更新 点我参考更多资料! 离线缓存与传统浏览器缓存区别: 离线缓存是针对整个应用,浏览器缓存是单个文件 离线缓存断网了还是可以打开页面,浏览器缓存不行 离线缓存可以主动通知浏览器更新资源 Web SQL关系数据库,通过SQL语句访问Web SQL 数据库 API 并不是 HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用 SQL 操作客户端数据库的 APIs。 支持情况: Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作。 核心方法: ① openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。② transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。③ executeSql:这个方法用于执行实际的 SQL 查询。 打开数据库:12var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024,fn);//openDatabase() 方法对应的五个参数分别为:数据库名称、版本号、描述文本、数据库大小、创建回调 执行查询操作:1234var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)');}); 插入数据: 123456var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)'); tx.executeSql('INSERT INTO WIN (id, name) VALUES (1, "winty")'); tx.executeSql('INSERT INTO WIN (id, name) VALUES (2, "LuckyWinty")');}); 读取数据:123456789101112db.transaction(function (tx) { tx.executeSql('SELECT * FROM WIN', [], function (tx, results) { var len = results.rows.length, i; msg = "<p>查询记录条数: " + len + "</p>"; document.querySelector('#status').innerHTML += msg; for (i = 0; i < len; i++){ alert(results.rows.item(i).name ); } }, null);}); 由这些操作可以看出,基本上都是用 SQL 语句进行数据库的相关操作,如果你会 MySQL 的话,这个应该比较容易用。更多教程 IndexedDB索引数据库 (IndexedDB) API(作为 HTML5 的一部分)对创建具有丰富本地存储数据的数据密集型的离线 HTML5 Web 应用程序很有用。同时它还有助于本地缓存数据,使传统在线 Web 应用程序(比如移动 Web 应用程序)能够更快地运行和响应。 异步 API:在 IndexedDB 大部分操作并不是我们常用的调用方法,返回结果的模式,而是请求——响应的模式,比如打开数据库的操作 这样,我们打开数据库的时候,实质上返回了一个 DB 对象,而这个对象就在 result 中。由上图可以看出,除了result之外。还有几个重要的属性就是 onerror、onsuccess、onupgradeneeded(我们请求打开的数据库的版本号和已经存在的数据库版本号不一致的时候调用)。这就类似于我们的 ajax 请求那样。我们发起了这个请求之后并不能确定它什么时候才请求成功,所以需要在回调中处理一些逻辑。 关闭与删除:123456function closeDB(db){ db.close();}function deleteDB(name){ indexedDB.deleteDatabase(name);} 数据存储: indexedDB 中没有表的概念,而是 objectStore,一个数据库中可以包含多个 objectStore,objectStore 是一个灵活的数据结构,可以存放多种类型数据。也就是说一个objectStore相当于一张表,里面存储的每条数据和一个键相关联。 我们可以使用每条记录中的某个指定字段作为键值(keyPath),也可以使用自动生成的递增数字作为键值(keyGenerator),也可以不指定。选择键的类型不同,objectStore 可以存储的数据结构也有差异。 这个就有点复杂了。看这里的教程: http://www.cnblogs.com/dolphinX/p/3415761.html http://www.cnblogs.com/dolphinX/p/3416889.html 详细API地址:http://www.ibm.com/developerworks/cn/web/wa-indexeddb/#ibm-pcon","link":"/tutorial/frontend-storage/"},{"title":"JavaScript array","text":"参考http://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array Array.from()Array.isArray()Array.of()Array.prototype.concat()Array.prototype.copyWithin()Array.prototype.entries()Array.prototype.every()Array.prototype.fill()Array.prototype.push()Array.prototype.pop()Array.prototype.shift()Array.prototype.unshift()Array.prototype.splice()Array.prototype.split()Array.prototype.sort()Array.prototype.reverse()Array.prototype.slice()Array.prototype.forEach()Array.prototype.map()语法var newArray = arr.map(callback(element[, index[, array]])[, thisArg]) 示例1234var filtered = [1, 4, 9, 16].map(element => { return element * 2})→ [2, 8, 18, 32] Array.prototype.filter()语法var newArray = arr.filter(callback(element[, index[, array]])[, thisArg]) 示例1234var filtered = [12, 5, 8, 130, 44].filter(element => { return element >= 10})→ [12, 130, 44] Array.prototype.come()Array.prototype.reduce()Array.prototype.reduceRight()Array.prototype.indexOf()Array.prototype.lastIndexOf()Array.prototype.find()Array.prototype.findIndex()Array.prototype.includes()Array.prototype.keys()Array.prototype.values()","link":"/tutorial/grammar/javascript-array/"},{"title":"广东韩江司法鉴定所 - 网站设计","text":"Platform:PCDate:2018.11.8Description:","link":"/portfolio/design/judicial-appraisal-institute/"},{"title":"Markdown 基本语法","text":"Markdown 是一种纯文本格式的标记语言。通过简单的标记语法,它可以使普通文本内容具有一定的格式。 相比 WYSIWYG 编辑器 优点:1、因为是纯文本,所以只要支持Markdown的地方都能获得一样的编辑效果,可以让作者摆脱排版的困扰,专心写作。2、操作简单。比如:WYSIWYG编辑时标记个标题,先选中内容,再点击导航栏的标题按钮,选择几级标题。要三个步骤。而Markdown只需要在标题内容前加#即可 缺点:1、需要记一些语法(当然,是很简单。五分钟学会)。2、有些平台不支持Markdown编辑模式。还好,简书是支持Markdown编辑模式的。 一、标题在想要设置为标题的文字前面加#来表示一个#是一级标题,二个#是二级标题,以此类推。支持六级标题。注:标准语法一般在#后跟个空格再写文字。 示例: 123456# 这是一级标题## 这是二级标题### 这是三级标题#### 这是四级标题##### 这是五级标题###### 这是六级标题 效果如下: 这是一级标题这是二级标题这是三级标题这是四级标题这是五级标题这是六级标题 二、字体加粗要加粗的文字左右分别用两个*号包起来 斜体要倾斜的文字左右分别用一个*号包起来 斜体加粗要倾斜和加粗的文字左右分别用三个*号包起来 删除线要加删除线的文字左右分别用两个~~号包起来 示例: 1234**这是加粗的文字***这是倾斜的文字****这是斜体加粗的文字***~~这是加删除线的文字~~ 效果如下: 这是加粗的文字这是倾斜的文字这是斜体加粗的文字这是加删除线的文字 三、引用在引用的文字前加>即可。引用也可以嵌套,如加两个>>三个>>>n个…貌似可以一直加下去,但没神马卵用 示例: 123>这是引用的内容>>这是引用的内容>>>>>>>>>>这是引用的内容 效果如下: 这是引用的内容 这是引用的内容 这是引用的内容 四、分割线三个或者三个以上的 - 或者 * 都可以。 示例: 1234-------******** 效果如下: 五、图片语法: 123图片alt就是显示在图片下面的文字,相当于对图片内容的解释。图片title是图片的标题,当鼠标移到图片上时显示的内容。title可加可不加 示例: 1 效果如下: 六、超链接语法: 12[超链接名](超链接地址 "超链接title")title可加可不加 示例: 12[简书](http://jianshu.com)[百度](http://baidu.com) 效果如下: 简书百度 七、列表无序列表语法: 1234567无序列表用 - + * 任何一种都可以- 列表内容+ 列表内容* 列表内容注意:- + * 跟内容之间都要有一个空格 效果如下: 列表内容 列表内容 列表内容 有序列表语法: 1234567数字加点1. 列表内容2. 列表内容3. 列表内容注意:序号跟内容之间要有空格 效果如下: 列表内容 列表内容 列表内容 列表嵌套语法: 123456789101112131415161718192021+ 一级无序列表内容 + 二级无序列表内容 + 二级无序列表内容 + 二级无序列表内容+ 一级无序列表内容 1. 二级有序列表内容 2. 二级有序列表内容 3. 二级有序列表内容1. 一级有序列表内容 + 二级无序列表内容 + 二级无序列表内容 + 二级无序列表内容2. 一级有序列表内容 1. 二级有序列表内容 2. 二级有序列表内容 3. 二级有序列表内容注意:下一级要比上一级缩进三个空格 效果如下: 一级无序列表内容 二级无序列表内容 二级无序列表内容 二级无序列表内容 一级无序列表内容 二级有序列表内容 二级有序列表内容 二级有序列表内容 一级有序列表内容 二级无序列表内容 二级无序列表内容 二级无序列表内容 一级有序列表内容 二级有序列表内容 二级有序列表内容 二级有序列表内容 八、表格语法: 1234567891011表头|表头|表头---|:--:|---:内容|内容|内容内容|内容|内容第二行分割表头和内容。- 有一个就行,为了对齐,多加了几个文字默认居左-两边加:表示文字居中-右边加:表示文字居右注:原生的语法两边都要用 | 包起来。此处省略 示例: 12345姓名|技能|排行---|:--:|---:刘备|哭|大哥关羽|打|二哥张飞|骂|三弟 效果如下: 姓名 技能 排行 刘备 哭 大哥 关羽 打 二哥 张飞 骂 三弟 九、代码语法: 1234567单行代码:代码之间分别用一个反引号包起来`代码内容`代码块:代码之间分别用三个反引号包起来,且两边的反引号单独占一行123代码...代码...代码...注:为了防止转译,前后三个反引号处加了小括号,实际是没有的。这里只是用来演示,实际中去掉两边小括号即可。 示例: 123456单行代码`create database hero;`代码块1234function fun(){ echo "这是一句非常牛逼的代码";}fun(); 十、流程图语法: 1212345678st=>start: 开始op=>operation: My Operationcond=>condition: Yes or No?e=>endst->op->condcond(yes)->econd(no)->op&","link":"/tutorial/grammar/markdown-grammar/"},{"title":"平板点餐 - APP 开发","text":"Platform:iPadDate:2018.12.21Description: 该项目采用前后端分离的开发方式,平板端采用 vue + vuex + cordova 等技术栈进行开发、封包。开发过程中使用了 CSS3 transition 动效,使人机交互更加生动。前后端完全分离的做法也使开发上更加专注于相对领域。 Link: github","link":"/portfolio/development/myshare-pad-order-application/"},{"title":"深圳蒂脉时代科技控股 - 标志设计","text":"Platform:VI DesignDate:2019.2.27","link":"/portfolio/design/myshare-logo-design/"},{"title":"纳诺神蜂(福建)个人护理用品有限公司 - 网站建设","text":"Platform:PC + MobileDate:2013.8.13Description: 纳诺神蜂(福建)个人护理用品有限公司于2013年成立于著名的海西经济区——福建省漳州市。公司专注于口腔健康护理用品的研发与推广,借鉴先进的口腔护理技术和现代口腔保健理念,致力于为各种口腔问题提供专业的解决方案,全方位保护口腔健康状态,让全世界都能绽放甜美的笑容。Url: http://www.nanofj.com 案例还在整理中,稍后上传!!","link":"/portfolio/development/nanofj-website/"},{"title":"平板点餐 - UI 设计","text":"Platform:UI DesignDate:2018.12.5案例还在整理中,稍后上传!!","link":"/portfolio/design/myshare-pad-order-design/"},{"title":"NodeJS 分布式和多进程的测试与记录","text":"目的:测试与记录高并发环境下,分布式 与 多进程 的情况测试:对来访数据进行记录,类似日志的处理目录: 测试环境 — 软、硬件 单服务器单进程 单服务器多进程 多服务器单进程 多服务器多进程 备注:该测试不含数据库操作 测试环境 - CPU RAM(GB) OS OS type NodeJS Server 00 Intel(R) Core(TM) i5-8250U 1.60GHz x 4 1.80GHz 8.00 Win10 x64 v8.11.4 Server 01 Intel(R) Core(TM) i5-3470S 2.90GHz x 4 2.90GHz 8.00 Win10 x64 v10.16.0 Server 02 Intel(R) Core(TM) i5-3470S 3.20GHz x 4 9.60 Ubuntu 18.04.2 LTS x64 v10.16.0 ApacheBench v2.3 nginx 1.14.2 单服务器,单进程(利用 Server 02)Server 搭建利用 http 模块搭建服务端。 123456789101112131415161718192021222324252627282930const http = require('http')const fs = require('fs')let num = 0http.createServer((req, res) => { ++num const file = './log/file.txt' const data = JSON.stringify(Object.assign({ num, create_time: new Date() }, req.headers)) + "\\n" fs.open(file, 'a', (err, fd) => { if (err) throw err fs.appendFile(file, data , 'utf8', (err) => { if (err) throw err fs.close(fd, (err) => { if (err) throw err res.writeHead(200, {'Content-Type': 'text/plain'}) res.end('success') }) }) })}).listen(8080, () => { console.log('<<< [ node server ready ] <<<')}) ApacheBench 测试执行 2000 并发,10000 请求的 Http Keep-Alive 压测 1ab -c2000 -n10000 -k http://[ip:port]/ 返回结果 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253This is ApacheBench, Version 2.3 <$Revision: 1826310 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking [ip] (be patient)Completed 1000 requestsCompleted 2000 requestsCompleted 3000 requestsCompleted 4000 requestsCompleted 5000 requestsCompleted 6000 requestsCompleted 7000 requestsCompleted 8000 requestsCompleted 9000 requestsCompleted 10000 requestsFinished 10000 requestsServer Software:Server Hostname: [ip]Server Port: [port]Document Path: /Document Length: 7 bytesConcurrency Level: 2000Time taken for tests: 25.023 seconds # 总共花费 25 秒左右Complete requests: 10000Failed requests: 0Total transferred: 1080000 bytesHTML transferred: 70000 bytesRequests per second: 399.63 [#/sec] (mean) # 平均每秒接受请求个数 400,相当于LR中的每秒事务数Time per request: 5004.594 [ms] (mean) # 平均每次请求花费 5005 毫秒,相当于LR中的平均事务响应时间Time per request: 2.502 [ms] (mean, across all concurrent requests) # 每个连接请求实际运行时间的平均值Transfer rate: 42.15 [Kbytes/sec] received # 平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题Connection Times (ms) min mean[+/-sd] median maxConnect: 1 2 0.5 2 13Processing: 112 4510 1145.8 4973 5065Waiting: 22 2509 1425.1 2515 5040Total: 114 4513 1145.8 4976 5067Percentage of the requests served within a certain time (ms) 50% 4976 66% 5013 75% 5023 80% 5034 90% 5044 95% 5050 98% 5054 99% 5061 100% 5067 (longest request) 单服务器,多进程(利用 Server 02)Server 搭建利用 cluster 模块进行多进程处理。 1234567891011121314151617181920212223242526272829303132333435363738const cluster = require('cluster')const http = require('http')const numCPUs = require('os').cpus().lengthconst fs = require('fs')if (cluster.isMaster) { for (let i = 0; i < numCPUs; i++) { cluster.fork() }} else { let num = 0 http.createServer((req, res) => { ++num const file = './log/file.txt' const data = JSON.stringify(Object.assign({ num, create_time: new Date() }, req.headers)) + "\\n" fs.open(file, 'a', (err, fd) => { if (err) throw err fs.appendFile(file, data , 'utf8', (err) => { if (err) throw err fs.close(fd, (err) => { if (err) throw err res.writeHead(200, {'Content-Type': 'text/plain'}) res.end(`${process.pid} success`) // process.kill(process.pid) }) }) }) }).listen(8080, () => { console.log(`<<< [ node server ready by pid:${process.pid} ] <<<`) })} ApacheBench 测试执行 2000 并发,10000 请求的 Http Keep-Alive 压测 1ab -c2000 -n10000 -k http://[ip:port]/ 返回结果 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354This is ApacheBench, Version 2.3 <$Revision: 1826310 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking [ip] (be patient)Completed 1000 requestsCompleted 2000 requestsCompleted 3000 requestsCompleted 4000 requestsCompleted 5000 requestsCompleted 6000 requestsCompleted 7000 requestsCompleted 8000 requestsCompleted 9000 requestsCompleted 10000 requestsFinished 10000 requestsServer Software:Server Hostname: [ip]Server Port: [port]Document Path: /Document Length: 12 bytesConcurrency Level: 2000Time taken for tests: 26.649 seconds # 总共花费 27 秒左右Complete requests: 10000Failed requests: 0Keep-Alive requests: 0Total transferred: 1130000 bytesHTML transferred: 120000 bytesRequests per second: 375.26 [#/sec] (mean) # 平均每秒接受请求个数 375Time per request: 5329.704 [ms] (mean) # 平均每次请求花费 5330 毫秒Time per request: 2.665 [ms] (mean, across all concurrent requests)Transfer rate: 41.41 [Kbytes/sec] receivedConnection Times (ms) min mean[+/-sd] median maxConnect: 1 2 0.8 2 33Processing: 111 4833 1359.5 5026 6156Waiting: 23 2615 1544.7 2565 5868Total: 113 4835 1359.6 5028 6159Percentage of the requests served within a certain time (ms) 50% 5028 66% 5520 75% 5747 80% 5887 90% 5990 95% 6050 98% 6140 99% 6149 100% 6159 (longest request) 多服务器,单进程Server 搭建利用 nginx 进行分发。 ApacheBench 测试执行 2000 并发,10000 请求的 Http Keep-Alive 压测 1ab -c2000 -n10000 -k http://[ip:port]/ 返回结果 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354This is ApacheBench, Version 2.3 <$Revision: 1826310 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking [ip] (be patient)Completed 1000 requestsCompleted 2000 requestsCompleted 3000 requestsCompleted 4000 requestsCompleted 5000 requestsCompleted 6000 requestsCompleted 7000 requestsCompleted 8000 requestsCompleted 9000 requestsCompleted 10000 requestsFinished 10000 requestsServer Software: nginx/1.14.2Server Hostname: [ip]Server Port: [port]Document Path: /Document Length: 7 bytesConcurrency Level: 2000Time taken for tests: 8.483 seconds # 总共花费 8 秒左右Complete requests: 10000Failed requests: 0Keep-Alive requests: 0Total transferred: 1300000 bytesHTML transferred: 70000 bytesRequests per second: 1178.82 [#/sec] (mean) # 平均每秒接受请求个数 1178Time per request: 1696.606 [ms] (mean) # 平均每次请求花费 1696 毫秒Time per request: 0.848 [ms] (mean, across all concurrent requests)Transfer rate: 149.66 [Kbytes/sec] receivedConnection Times (ms) min mean[+/-sd] median maxConnect: 0 1 16.6 0 502Processing: 112 1509 456.6 1581 2099Waiting: 48 1029 515.5 840 1994Total: 112 1510 456.8 1581 2099Percentage of the requests served within a certain time (ms) 50% 1581 66% 1592 75% 1594 80% 1608 90% 2050 95% 2085 98% 2093 99% 2094 100% 2099 (longest request) 多服务器,多进程Server 搭建利用 nginx 进行分发。 ApacheBench 测试执行 2000 并发,10000 请求的 Http Keep-Alive 压测 1ab -c2000 -n10000 -k http://[ip:port]/ 返回结果 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455This is ApacheBench, Version 2.3 <$Revision: 1826310 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking [ip] (be patient)Completed 1000 requestsCompleted 2000 requestsCompleted 3000 requestsCompleted 4000 requestsCompleted 5000 requestsCompleted 6000 requestsCompleted 7000 requestsCompleted 8000 requestsCompleted 9000 requestsCompleted 10000 requestsFinished 10000 requestsServer Software: nginx/1.14.2Server Hostname: [ip]Server Port: [port]Document Path: /Document Length: 13 bytesConcurrency Level: 2000Time taken for tests: 7.978 seconds # 总共花费 8 秒左右Complete requests: 10000Failed requests: 7450 (Connect: 0, Receive: 0, Length: 7450, Exceptions: 0)Keep-Alive requests: 0Total transferred: 1352550 bytesHTML transferred: 122550 bytesRequests per second: 1253.45 [#/sec] (mean) # 平均每秒接受请求个数 1253Time per request: 1595.600 [ms] (mean) # 平均每次请求花费 1595 毫秒Time per request: 0.798 [ms] (mean, across all concurrent requests)Transfer rate: 165.56 [Kbytes/sec] receivedConnection Times (ms) min mean[+/-sd] median maxConnect: 0 1 15.8 0 502Processing: 113 1413 468.6 1587 2100Waiting: 46 952 517.6 860 1983Total: 113 1414 468.6 1587 2100Percentage of the requests served within a certain time (ms) 50% 1587 66% 1592 75% 1595 80% 1596 90% 2021 95% 2050 98% 2099 99% 2099 100% 2100 (longest request) 结论单服务器,多进程并没有比单进程更有效的提升多服务器(2台),提升效率明显(接近 50% ) 相关文章 Nginx 均衡负载视频教程 Apache Bench (ab) 使用说明","link":"/tutorial/nodejs-distributed-and-multiprocess-test/"},{"title":"紫藤知识产权运营(深圳)有限公司 - 网站开发","text":"Platform:PC + MobileDate:2019.7.9Description: 该网站使用 jQuery + Bootstrap 技术搭建,是一个中规中矩的展示型网站。开发过程中使用了 CSS3 transition 动效、jQuery 动效等动画脚本函数,使人机交互更加生动。监听 Bootstrap Events 事件等方法,使脚本更加简明和易于阅读理解。。Url: www.purplevineip.com 细节图片和网址稍后补上。","link":"/portfolio/development/purplevineip-website/"},{"title":"在线 PDF 签名 - APP 开发","text":"Platform:iPadDate:2018.12.28Description: 该项目是一个在线阅读 PDF 合同文档并可以实时签名存档、生成副本、回调查阅的应用程序。程序采用著名互联网公司 Mozilla 的开源代码配合 vue + vuex + canvas + cordova 等技术栈进行开发、封包。开发过程中使用了 CSS3 transition 动效,使人机交互更加生动。前后端完全分离的做法也使开发上更加专注于相对领域。","link":"/portfolio/development/pdf-signature-online-application/"},{"title":"基于 Node.js 的微服务框架","text":"SenecaA Node.js toolkit for Microservice architectures这是一个基于 NodeJS 的微服务工具套件。 官网地址: http://senecajs.org/ Methods 方法seneca.add添加一个新的 action pattern 到 seneca 实例 pattern 匹配的属性模式 action 模式匹配消息时要执行的函数,包含两个参数 msg 匹配的入站消息(作为普通对象提供) respond 用于提供消息响应的回调函数。响应函数是具有标准 error, result 签名的回调。 1234567891011121314151617// 这是相同的两个写法seneca.add('role:math, cmd:sum', (msg, reply) => { reply(null, { answer: (msg.left + msg.right) })})seneca.add({ role: 'math', cmd: 'sum'}, function (msg, respond) { var sum = msg.left + msg.right respond(null, { answer: sum })}) seneca.act提交要执行的消息 msg 消息对象 response_callback 接收消息响应(如果有)的函数 err result 123456789seneca.act({ role: 'math', cmd: 'sum', left: 1, right: 2}, function (err, result) { if (err) return console.error(err) console.log(result)}) seneca.prior当前动作模式调用先前的动作定义1234567891011121314151617181920212223242526272829303132333435363738394041var seneca = require('seneca')()seneca .add( 'role:math,cmd:sum', function( msg, respond ) { var sum = msg.left + msg.right respond( null, { answer: sum } ) }) // override role:math,cmd:sum with additional functionality .add( 'role:math,cmd:sum', function( msg, respond ) { // bail out early if there's a problem if( !Number.isFinite( msg.left ) || !Number.isFinite( msg.right ) ) { return respond( new Error("Expected left and right to be numbers.") ) } // call previous action function for role:math,cmd:sum this.prior({ role: 'math', cmd: 'sum', left: msg.left, right: msg.right, }, function( err, result ) { if( err ) return respond( err ) result.info = msg.left+'+'+msg.right respond( null, result ) }) }) // enhanced role:math,cmd:sum .act( 'role:math,cmd:sum,left:1.5,right:2.5', console.log // prints { answer: 4, info: '1.5+2.5' } ) seneca.wrap pin 引脚是模式匹配模式 action 动作扩展功能 匹配一组模式,并使用相同的动作扩展功能覆盖所有模式 123456789101112131415161718192021222324module.exports = function math(options) { this.add('role:math,cmd:sum', function sum(msg, respond) { respond(null, { answer: msg.left + msg.right }) }) this.add('role:math,cmd:product', function product(msg, respond) { respond(null, { answer: msg.left * msg.right }) }) this.wrap('role:math', function (msg, respond) { msg.left = Number(msg.left).valueOf() msg.right = Number(msg.right).valueOf() this.prior(msg, respond) })}// these are equivalentrequire('seneca')() .use(require('./math.js')) .act('role:math,cmd:sum,left:1,right:2', console.log)require('seneca')() .use('math') // finds ./math.js in local folder .act('role:math,cmd:sum,left:1,right:2', console.log) seneca.use使用插件,Seneca 插件是具有单个参数的函数 options。 plugin 插件定义函数或插件名称 options 插件的options对象 1234567var seneca = require('seneca')()function minimal_plugin(options) { console.log(options)}seneca.use(minimal_plugin, { foo: 'bar' }) 插件内部不推荐用 act 方法 使用 this.add({ init: 'math' }, function(msg, respond){}) 可以初始化插件 seneca.listen服务器的设置(默认使用 HTTP 通过端口 10101 进行监听) port 可选整数;端口号 host 可选字符串;主机IP地址 spec 可选对象;完整规格对象 seneca.client port 可选整数;端口号 host 可选字符串;主机IP地址 spec 可选对象;完整规格对象 –seneca.log.all–seneca.log=plugin:math–seneca.print.tree WEB 服务器集成1234567891011121314151617181920var SenecaWeb = require('seneca-web')var Express = require('express')var Router = Express.Routervar context = new Router()var senecaWebConfig = { context: context, adapter: require('seneca-web-adapter-express'), options: { parseBody: false } // so we can use body-parser}var app = Express() .use( require('body-parser').json() ) .use( context ) .listen(3000)var seneca = require('seneca')() .use(SenecaWeb, senecaWebConfig ) .use('api') .client( { type:'tcp', pin:'role:math' } ) 1234567891011121314151617181920212223module.exports = function api(options) { var valid_ops = { sum:'sum', product:'product' } this.add('role:api,path:calculate', function (msg, respond) { var operation = msg.args.params.operation var left = msg.args.query.left var right = msg.args.query.right this.act('role:math', { cmd: valid_ops[operation], left: left, right: right, }, respond) }) this.add('init:api', function (msg, respond) { this.act('role:web',{routes:{ prefix: '/api', pin: 'role:api,path:*', map: { calculate: { GET:true, suffix:'/:operation' } } }}, respond) })} 附微服务这一概念出现于2012年,是因软件作者 Martin Fowler 而流行,他承认这并没有精确地定义出这一架构形式,虽然围绕业务能力、自动化部署、终端智能以及语言和数据的分散控制有一些常见的特性。 为了让大家更好的理解,我这里转了一篇大白话的文章,感兴趣的可以看看如何给老婆解释什么是微服务?","link":"/uncategorized/nodejs-microservices/"},{"title":"移动竞速 - H5 游戏开发","text":"Platform:Cocos CreatorDate:2018.7.25Description: 该项目是由两个好友和我一起发起的一个怀旧项目,以《公路赛车》这款年少时的游戏为基准进行开发。😓😓 因为近期太忙了,一直没完成…. 细节图片和预览稍后补上。","link":"/portfolio/development/race-game/"},{"title":"保险箱 - 海报设计","text":"Platform:Graphic DesignDate:2018.11.23","link":"/portfolio/design/safe-deposit-box-poster/"},{"title":"webpack 教程","text":"现今的很多网页其实可以看做是功能丰富的应用,它们包含着大量,复杂的脚本逻辑、样式、图片与动画等元素。特别是 JS 的发展与迭代,TS 的出现,CSS 预处理器等等,方便开发的同时,浏览器并不能很好的逐一识别。所以 webpack 解决了这些兼容性的同时,对文件进行了更加细腻的模块化打包,使应用能更好的运行在各个浏览器之间。 webpack Demo","link":"/tutorial/webpack-tutorial/"},{"title":"状态管理器(状态机)","text":"我最先接触到状态机是在利用 cocos 编写游戏的时候,游戏的变量特别多,各个组件之间也有通讯的需求,状态机这种概念刚好解决了变量之间传递的繁琐。而且状态机不同于全局变量,一般来说状态机都是可以带事务触发的,所以它更适合做一些大型的,或团队协作的项目。下面我们就来说说 Vue 和 React 这两种当下最热门的前端框架所对应使用的状态管理器(Vuex 和 Redux)。 vue 状态管理器 vuex在 vue 中使用状态机,需要先安装 1npm install vuex --save /store/index.js1234567import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ ...})export default store /main.js123456import store from './store'new Vue({ store, render: h => h(App),}).$mount('#app') 至此,我们就可以开始编写 vuex 的业务代码了。 Statevuex 中保存的“数据状态”,可以在页面通过 this.$store.state 来获取已经定义好的状态。 /store/index.js123456789import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: { count: 1 }})export default store /src/App.vue12345<template> <div> {{ this.$store.state.count }} </div></template> Getters相当于 vue 中的 computed 计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算,这里我们可以通过定义 vuex 的 Getter 来获取,Getters 可以用于监听、state 中的值的变化,返回计算后的结果 /store/index.js12345678910111213import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state:{ count:1, }, getters:{ getStateCount: state => state.count + 1 }})export default store /src/App.vue123456<template> <div> {{ this.$store.state.count }} {{ this.$store.getters.getStateCount }} </div></template> Mutations数据我们在页面是获取到了,但是如果我们需要修改 count 值怎么办?如果需要修改 store 中的值唯一的方法就是提交mutation 来修改,我们现在 App.vue 文件中添加两个按钮,一个加1,一个减1;这里我们点击按钮调用 addFun(执行加的方法)和 reductionFun(执行减法的方法),然后在里面直接提交 mutations 中的方法修改值 /store/index.js123456789101112131415161718192021import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: { count:1, }, getters: { getStateCount: state => state.count + 1 }, mutations: { add (state) { state.count = state.count + 1 }, reduction (state) { state.count = state.count - 1 } }})export default store /src/App.vue1234567891011121314151617181920212223<template> <div> <p>{{ this.$store.state.count }}</p> <p>{{ this.$store.getters.getStateCount }}</p> <p>count 的值:{{ this.$store.state.count }}</p> <button @click="addFun">+</button> <button @click="reductionFun">-</button> </div></template><script>export default { name : 'app', methods: { addFun(){ this.$store.commit('add') }, reductionFun(){ this.$store.commit('reduction') } }}</script> Actions我们看到,当点击三次后值从 2 变成了-1;页面上的值是改变了;我们达到了修改 store 中状态值的目的,但是,官方并不提倡我们这样直接去修改 store 里面的值,而是让我们去提交一个 actions,在 actions 中提交 mutation 再去修改状态值,先定义 actions 提交 mutation 的函数: /store/index.js123456789101112131415161718192021222324252627import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: { count:1, }, getters: { getStateCount: state => state.count + 1 }, mutations: { add: state => ++state.count, reduction (state) { state.count = state.count - 1 } }, actions: { addFun (context) { context.commit('add') }, reductionFun (context) { context.commit('reduction') } }})export default store /src/App.vue12345678910111213141516171819202122232425<template> <div> <p>{{ this.$store.state.count }}</p> <p>{{ this.$store.getters.getStateCount }}</p> <p>count 的值:{{ this.$store.state.count }}</p> <button @click="addFun">+</button> <button @click="reductionFun">-</button> </div></template><script>export default { name : 'app', methods: { addFun(){ // this.$store.commit('add') this.$store.dispatch('addFun') }, reductionFun(){ // this.$store.commit('reduction') this.$store.dispatch('reductionFun') } }}</script> 这里我们把commit提交mutations修改为使用dispatch来提交actions;我们点击页面,效果是一样的。 好了,我们这里已经实现了一个基本的vuex修改状态值的完整流程,如果我们需要指定加减的数值,那么我们直接传入dispatch中的第二个参数,然后在actions中的对应函数中接受参数在传递给mutations中的函数进行计算: mapState,mapGetters,mapActions如果我们不喜欢这种在页面上使用 this.$stroe.state.count 和 this.$store.dispatch('funName') 这种很长的写法,那么我们可以使用 mapState、mapGetters、mapActions 就不会这么麻烦了 123456789import { mapState、mapGetters、mapActions } from 'vuex'; . . . computed:{ ...mapState({ count : state => state.count }) } react 状态管理器 redux在 react 中使用状态机,需要先安装 1npm install redux --save 1234567891011121314151617181920212223242526272829303132333435363738394041import { createStore } from 'redux';/** * 这是一个 reducer,形式为 (state, action) => state 的纯函数。 * 描述了 action 如何把 state 转变成下一个 state。 * * state 的形式取决于你,可以是基本类型、数组、对象、 * 甚至是 Immutable.js 生成的数据结构。惟一的要点是 * 当 state 变化时需要返回全新的对象,而不是修改传入的参数。 * * 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper) * 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。 */function counter(state = 0, action) { switch(action.type) { case 'INCREMENT': return state + 1; case 'DECRMENT': return stat - 1; default: return state; }}// 创建 Redux store 来存放应用的状态。// API 是 { subscribe, dispatch, getState }。let store = createStore(counter);// 可以手动订阅更新,也可以事件绑定到视图层。store.subscribe(() => { console.log(store.getState())})// 改变内部 state 惟一方法是 dispatch 一个 action。// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行store.dispatch({ type: 'INCREMENT' });// 1store.dispatch({ type: 'INCREMENT' });// 2store.dispatch({ type: 'DECREMENT' });// 1","link":"/tutorial/states-manager/"},{"title":"pug 语法","text":"pug 是一个 JavaScript 的高性能模板引擎,被使用在 Node.js 和 浏览器 项目中。是由 Jade 重命名而来 参考https://pugjs.org 符号与汉含义 符号 含义 . 表示之后的内容翻译为纯文本 : 表示嵌套关系,不需要换行 | 表示之后的内容翻译为纯文本,不识别 html 标签 // 注释,在编译出来的 html 中显示 //- 注释,在编译出来的 html 中不显示 - 后接 js 代码 #{变量} 转义插值 !{变量} 非转义插值 模板关键字 关键字 描述 block 占位符,通过其实现子模版的追加和替换 entends 继承,子模版继承父模板中的代码 appends 在子模版中向后追加内容 prepend 在子模版中向前追加内容 include 引入文件,并将其中的代码放到模板中 按照 html 的缩进格式 标签12345doctype htmlhtml head title body 编译结果1234567<!DOCTYPE html><html> <head> <title></title> </head> <body></body></html> 文本1234p 这是文本| 这是文本p. 这是文本 编译结果123<p>这是文本</p>这是文本<p>这是文本</p> 属性设置 class 名跟 id 名(默认是 div)12345p.foop#foop#foo.foo.foo#foo 编译结果12345<p class="foo"></p><p id="foo"></p><p id="foo" class="foo"></p><div class="foo"></div><div id="foo"></div> 其他属性:12345678910111213a(href="google.com") google- var foo = truep(class=foo ? "authed" : "anon")input( type="checkbox" name="agreement" checked)div(data-bar="foo")&attributes({"data-foo": "bar"})- var attr = {}- attr.class = "baz"div(data-bar="foo")&attributes(attr) 编译结果123456<a href="google.com">google</a><p class="authed"></p><input type="checkbox" name="agreement" checked><div data-bar="foo" data-foo="bar"></div><div class="baz" data-bar="foo"></div> 注释12345// 行注释//- 编译后不会显示出来// 块注释<!--可以直接使用注释代码--> case语句12345678- var num = 3case num when 0 p 这是0 when 1 - break when 3 p 这是#{num} 编译结果1<p>这是3</p> 循环1234567891011ul - var n = 0 while n < 4 li= n++ ul - for (var x = 0; x < 3; x++) li itemol - var list = [1,2,3,4,5,6] each item in list li= item 编译结果12345678910111213141516171819<ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li></ul><ul> <li>item</li> <li>item</li> <li>item</li></ul><ol> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li></ol> 条件语句12345678910111213141516- var user = {foo: "this is foo"}- var bar = baz#user if user.foo h2.green foo p.foo= user.foo else if bar h2.blue foo p.foo. User has no foo else h2.red foo p.foo user has no foounless user.isFoo p 等同于:if !user.Foo 编译结果12345<div id="user"> <h2 class="green">foo</h2> <p class="foo">this is foo</p> <p>等同于:if !user.Foo</p></div> mixin 创建重用的代码1234567mixin list ul li foo li bar li baz+list+list 编译结果12345678910<ul> <li>foo</li> <li>bar </li> <li>baz</li></ul><ul> <li>foo</li> <li>bar </li> <li>baz</li></ul> mixin支持传参123456789101112mixin article(title) .article .article-wrapper h3= title if block block else p NO content provided+article("Hello worle")+article("hello pug") p This is my p Amazing article 编译结果12345678910111213<div class="article"> <div class="article-wrapper"> <h3>Hello worle</h3> <p>NO content provided</p> </div></div><div class="article"> <div class="article-wrapper"> <h3>hello pug</h3> <p>This is my</p> <p>Amazing article</p> </div></div> 还有:123mixin link(href, name) a(class!=attributes.class href=href)= name+link("/foo", "foo")(class="btn") 编译结果1<a class="btn" href="/foo">foo</a> 未知参数:12345mixin list(id, ...items) ul(id=id) each item in items li= item+list("my-list",1,2,3,4) 编译结果123456<ul id="my-list"> <li>1</li> <li>2</li> <li>3</li> <li>4</li></ul> Extends 扩展允许模板来扩展一个布局或父模板,它可以覆盖某些预定义内容块。12345678//- index.pugextends layout.pugblock title title Article Titleblock content h1 My Article 12345678//- layout.pugdoctype htmlhtml head block title title Default title body block content 编译结果:123456789101112<!DOCTYPE html><html><head> <title>Article Title</title></head><body> <h1>My Article</h1></body></html> Includes允许将一个pug文件的内容插入到另一个文件。在要插入的位置:include 文件地址12345678//- index.pugdoctype htmlhtml include includes/head.pug body h1 My Site p Welcome to my super lame site. include includes/foot.pug 12345//- includes/head.pughead title My Site script(src='/javascripts/jquery.js') script(src='/javascripts/app.js') 123//- includes/foot.pugfooter#footer p Copyright (c) foobar 编译结果:123456789101112131415161718<!DOCTYPE html><html><head> <title>My Site</title> <script src="/javascripts/jquery.js"></script> <script src="/javascripts/app.js"></script></head><body> <h1>My Site</h1> <p>Welcome to my super lame site.</p> <footer id="footer"> <p>Copyright (c) foobar</p> </footer></body></html>","link":"/tutorial/grammar/pug-grammar/"}],"tags":[{"name":"browser plug-in","slug":"browser-plug-in","link":"/tags/browser-plug-in/"},{"name":"mini app","slug":"mini-app","link":"/tags/mini-app/"},{"name":"apache","slug":"apache","link":"/tags/apache/"},{"name":"ab","slug":"ab","link":"/tags/ab/"},{"name":"http, tcp, socket, websocket","slug":"http-tcp-socket-websocket","link":"/tags/http-tcp-socket-websocket/"},{"name":"css3","slug":"css3","link":"/tags/css3/"},{"name":"h5","slug":"h5","link":"/tags/h5/"},{"name":"nginx","slug":"nginx","link":"/tags/nginx/"},{"name":"storage","slug":"storage","link":"/tags/storage/"},{"name":"javascript","slug":"javascript","link":"/tags/javascript/"},{"name":"website design","slug":"website-design","link":"/tags/website-design/"},{"name":"markdown","slug":"markdown","link":"/tags/markdown/"},{"name":"application","slug":"application","link":"/tags/application/"},{"name":"logo design","slug":"logo-design","link":"/tags/logo-design/"},{"name":"vi design","slug":"vi-design","link":"/tags/vi-design/"},{"name":"website","slug":"website","link":"/tags/website/"},{"name":"ui design","slug":"ui-design","link":"/tags/ui-design/"},{"name":"distributed","slug":"distributed","link":"/tags/distributed/"},{"name":"nodejs, seneca, express, javascript","slug":"nodejs-seneca-express-javascript","link":"/tags/nodejs-seneca-express-javascript/"},{"name":"game","slug":"game","link":"/tags/game/"},{"name":"cocos creator","slug":"cocos-creator","link":"/tags/cocos-creator/"},{"name":"graphic design","slug":"graphic-design","link":"/tags/graphic-design/"},{"name":"webpack","slug":"webpack","link":"/tags/webpack/"},{"name":"vuex, redux","slug":"vuex-redux","link":"/tags/vuex-redux/"},{"name":"pug","slug":"pug","link":"/tags/pug/"}],"categories":[{"name":"portfolio","slug":"portfolio","link":"/categories/portfolio/"},{"name":"tutorial","slug":"tutorial","link":"/categories/tutorial/"},{"name":"development","slug":"portfolio/development","link":"/categories/portfolio/development/"},{"name":"grammar","slug":"tutorial/grammar","link":"/categories/tutorial/grammar/"},{"name":"nginx","slug":"tutorial/nginx","link":"/categories/tutorial/nginx/"},{"name":"design","slug":"portfolio/design","link":"/categories/portfolio/design/"},{"name":"plugin","slug":"tutorial/plugin","link":"/categories/tutorial/plugin/"}]}