├── Eslint静态代码检查的规则说明.md ├── Express VS Koa.md ├── Flux的理解.md ├── Mac下初尝Docker.md ├── README.md ├── mongoDao ├── README.md ├── SUMMARY.md └── publish │ ├── add.md │ ├── array.md │ ├── batchAddData.md │ ├── comparaQuery.md │ ├── config.md │ ├── count.md │ ├── distinct.md │ ├── findAndModify.md │ ├── findAndRemove.md │ ├── group.md │ ├── inc.md │ ├── intr.md │ ├── mapReduce.md │ ├── orand.md │ ├── pagingQuery.md │ ├── queryData.md │ ├── regxQuery.md │ ├── removeData.md │ ├── twosubmit.md │ └── updateData.md ├── nodeapi ├── README.md ├── SUMMARY.md └── publish │ └── about.md ├── 关于微信中回退的问题.md ├── 团队内部践行的技术栈.md ├── 用Docker构建开发环境.md └── 高姿态的使用CDN.md /Eslint静态代码检查的规则说明.md: -------------------------------------------------------------------------------- 1 | 现在JS代码的静态质量检查在项目质量保障方面的重要性与必要性已毋庸置疑。 2 | ESLint是一种开源方案,广受大家喜欢。我们团队中的各个项目也引用了它作为代码检查的插件。 3 | 在此强烈推荐[airbnb的js代码规范](https://github.com/airbnb/javascript),并选用eslint-config-airbnb-base作为基准配置,不论node后端,还是前端团队中都在使用这套规范。 4 | ### ESLint的规则参数说明: 5 | 6 | > - 0:表示不验证 7 | > - 1:验证不对的话,以警告的方式提示 8 | > - 2 : 验证不对的话,以异常的方式抛出 9 | 10 | ### 规则说明: 11 | 12 | > "no-alert": 0 //禁止使用alert confirm prompt 13 | "no-array-constructor": 2 //禁止使用数组构造器 14 | "no-bitwise": 0 //禁止使用按位运算符 15 | "no-caller": 1 //禁止使用arguments.caller或arguments.callee 16 | "no-catch-shadow": 2 //禁止catch子句参数与外部作用域变量同名 17 | "no-class-assign": 2 //禁止给类赋值 18 | "no-cond-assign": 2 //禁止在条件表达式中使用赋值语句 19 | "no-console": 2 //禁止使用console 20 | "no-const-assign": 2 //禁止修改const声明的变量 21 | "no-constant-condition": 2 //禁止在条件中使用常量表达式 if(true) if(1) 22 | "no-continue": 0 //禁止使用continue 23 | "no-control-regex": 2 //禁止在正则表达式中使用控制字符 24 | "no-debugger": 2 //禁止使用debugger 25 | "no-delete-var": 2 //不能对var声明的变量使用delete操作符 26 | "no-div-regex": 1 //不能使用看起来像除法的正则表达式/=foo/ 27 | "no-dupe-keys": 2 //在创建对象字面量时不允许键重复 {a:1 a:1} 28 | "no-dupe-args": 2 //函数参数不能重复 29 | "no-duplicate-case": 2 //switch中的case标签不能重复 30 | "no-else-return": 2 //如果if语句里面有return 后面不能跟else语句 31 | "no-empty": 2 //块语句中的内容不能为空 32 | "no-empty-character-class": 2 //正则表达式中的[]内容不能为空 33 | "no-empty-label": 2 //禁止使用空label 34 | "no-eq-null": 2 //禁止对null使用==或!=运算符 35 | "no-eval": 1 //禁止使用eval 36 | "no-ex-assign": 2 //禁止给catch语句中的异常参数赋值 37 | "no-extend-native": 2 //禁止扩展native对象 38 | "no-extra-bind": 2 //禁止不必要的函数绑定 39 | "no-extra-boolean-cast": 2 //禁止不必要的bool转换 40 | "no-extra-parens": 2 //禁止非必要的括号 41 | "no-extra-semi": 2 //禁止多余的冒号 42 | "no-fallthrough": 1 //禁止switch穿透 43 | "no-floating-decimal": 2 //禁止省略浮点数中的0 .5 3. 44 | "no-func-assign": 2 //禁止重复的函数声明 45 | "no-implicit-coercion": 1 //禁止隐式转换 46 | "no-implied-eval": 2 //禁止使用隐式eval 47 | "no-inline-comments": 0 //禁止行内备注 48 | "no-inner-declarations": [2 "functions"] //禁止在块语句中使用声明(变量或函数) 49 | "no-invalid-regexp": 2 //禁止无效的正则表达式 50 | "no-invalid-this": 2 //禁止无效的this,只能用在构造器,类,对象字面量 51 | "no-irregular-whitespace": 2 //不能有不规则的空格 52 | "no-iterator": 2 //禁止使用__iterator__ 属性 53 | "no-label-var": 2 //label名不能与var声明的变量名相同 54 | "no-labels": 2 //禁止标签声明 55 | "no-lone-blocks": 2 //禁止不必要的嵌套块 56 | "no-lonely-if": 2 //禁止else语句内只有if语句 57 | "no-loop-func": 1 //禁止在循环中使用函数(如果没有引用外部变量不形成闭包就可以) 58 | "no-mixed-requires": [0 false] //声明时不能混用声明类型 59 | "no-mixed-spaces-and-tabs": [2 false] //禁止混用tab和空格 60 | "linebreak-style": [0 "windows"] //换行风格 61 | "no-multi-spaces": 1 //不能用多余的空格 62 | "no-multi-str": 2 //字符串不能用\换行 63 | "no-multiple-empty-lines": [1 {"max": 2}] //空行最多不能超过2行 64 | "no-native-reassign": 2 //不能重写native对象 65 | "no-negated-in-lhs": 2 //in 操作符的左边不能有! 66 | "no-nested-ternary": 0 //禁止使用嵌套的三目运算 67 | "no-new": 1 //禁止在使用new构造一个实例后不赋值 68 | "no-new-func": 1 //禁止使用new Function 69 | "no-new-object": 2 //禁止使用new Object() 70 | "no-new-require": 2 //禁止使用new require 71 | "no-new-wrappers": 2 //禁止使用new创建包装实例,new String new Boolean new Number 72 | "no-obj-calls": 2 //不能调用内置的全局对象,比如Math() JSON() 73 | "no-octal": 2 //禁止使用八进制数字 74 | "no-octal-escape": 2 //禁止使用八进制转义序列 75 | "no-param-reassign": 2 //禁止给参数重新赋值 76 | "no-path-concat": 0 //node中不能使用__dirname或__filename做路径拼接 77 | "no-plusplus": 0 //禁止使用++,-- 78 | "no-process-env": 0 //禁止使用process.env 79 | "no-process-exit": 0 //禁止使用process.exit() 80 | "no-proto": 2 //禁止使用__proto__属性 81 | "no-redeclare": 2 //禁止重复声明变量 82 | "no-regex-spaces": 2 //禁止在正则表达式字面量中使用多个空格 /foo bar/ 83 | "no-restricted-modules": 0 //如果禁用了指定模块,使用就会报错 84 | "no-return-assign": 1 //return 语句中不能有赋值表达式 85 | "no-script-url": 0 //禁止使用javascript:void(0) 86 | "no-self-compare": 2 //不能比较自身 87 | "no-sequences": 0 //禁止使用逗号运算符 88 | "no-shadow": 2 //外部作用域中的变量不能与它所包含的作用域中的变量或参数同名 89 | "no-shadow-restricted-names": 2 //严格模式中规定的限制标识符不能作为声明时的变量名使用 90 | "no-spaced-func": 2 //函数调用时 函数名与()之间不能有空格 91 | "no-sparse-arrays": 2 //禁止稀疏数组, [1 2] 92 | "no-sync": 0 //nodejs 禁止同步方法 93 | "no-ternary": 0 //禁止使用三目运算符 94 | "no-trailing-spaces": 1 //一行结束后面不要有空格 95 | "no-this-before-super": 0 //在调用super()之前不能使用this或super 96 | "no-throw-literal": 2 //禁止抛出字面量错误 throw "error" 97 | "no-undef": 1 //不能有未定义的变量 98 | "no-undef-init": 2 //变量初始化时不能直接给它赋值为undefined 99 | "no-undefined": 2 //不能使用undefined 100 | "no-unexpected-multiline": 2 //避免多行表达式 101 | "no-underscore-dangle": 1 //标识符不能以_开头或结尾 102 | "no-unneeded-ternary": 2 //禁止不必要的嵌套 var isYes = answer === 1 ? true : false 103 | "no-unreachable": 2 //不能有无法执行的代码 104 | "no-unused-expressions": 2 //禁止无用的表达式 105 | "no-unused-vars": [2 {"vars": "all" "args": "after-used"}] //不能有声明后未被使用的变量或参数 106 | "no-use-before-define": 2 //未定义前不能使用 107 | "no-useless-call": 2 //禁止不必要的call和apply 108 | "no-void": 2 //禁用void操作符 109 | "no-var": 0 //禁用var,用let和const代替 110 | "no-warning-comments": [1 { "terms": ["todo" "fixme" "xxx"] "location": "start" }] //不能有警告备注 111 | "no-with": 2 //禁用with 112 | "array-bracket-spacing": [2 "never"] //是否允许非空数组里面有多余的空格 113 | "arrow-parens": 0 //箭头函数用小括号括起来 114 | "arrow-spacing": 0 //=>的前/后括号 115 | "accessor-pairs": 0 //在对象中使用getter/setter 116 | "block-scoped-var": 0 //块语句中使用var 117 | "brace-style": [1 "1tbs"] //大括号风格 118 | "callback-return": 1 //避免多次调用回调什么的 119 | "camelcase": 2 //强制驼峰法命名 120 | "comma-dangle": [2 "never"] //对象字面量项尾不能有逗号 121 | "comma-spacing": 0 //逗号前后的空格 122 | "comma-style": [2 "last"] //逗号风格,换行时在行首还是行尾 123 | "complexity": [0 11] //循环复杂度 124 | "computed-property-spacing": [0 "never"] //是否允许计算后的键名什么的 125 | "consistent-return": 0 //return 后面是否允许省略 126 | "consistent-this": [2 "that"] //this别名 127 | "constructor-super": 0 //非派生类不能调用super,派生类必须调用super 128 | "curly": [2 "all"] //必须使用 if(){} 中的{} 129 | "default-case": 2 //switch语句最后必须有default 130 | "dot-location": 0 //对象访问符的位置,换行的时候在行首还是行尾 131 | "dot-notation": [0 { "allowKeywords": true }] //避免不必要的方括号 132 | "eol-last": 0 //文件以单一的换行符结束 133 | "eqeqeq": 2 //必须使用全等 134 | "func-names": 0 //函数表达式必须有名字 135 | "func-style": [0 "declaration"] //函数风格,规定只能使用函数声明/函数表达式 136 | "generator-star-spacing": 0 //生成器函数*的前后空格 137 | "guard-for-in": 0 //for in循环要用if语句过滤 138 | "handle-callback-err": 0 //nodejs 处理错误 139 | "id-length": 0 //变量名长度 140 | "indent": [2 4] //缩进风格 141 | "init-declarations": 0 //声明时必须赋初值 142 | "key-spacing": [0 { "beforeColon": false "afterColon": true }] //对象字面量中冒号的前后空格 143 | "lines-around-comment": 0 //行前/行后备注 144 | "max-depth": [0 4] //嵌套块深度 145 | "max-len": [0 80 4] //字符串最大长度 146 | "max-nested-callbacks": [0 2] //回调嵌套深度 147 | "max-params": [0 3] //函数最多只能有3个参数 148 | "max-statements": [0 10] //函数内最多有几个声明 149 | "new-cap": 2 //函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用 150 | "new-parens": 2 //new时必须加小括号 151 | "newline-after-var": 2 //变量声明后是否需要空一行 152 | "object-curly-spacing": [0 "never"] //大括号内是否允许不必要的空格 153 | "object-shorthand": 0 //强制对象字面量缩写语法 154 | "one-var": 1 //连续声明 155 | "operator-assignment": [0 "always"] //赋值运算符 += -=什么的 156 | "operator-linebreak": [2 "after"] //换行时运算符在行尾还是行首 157 | "padded-blocks": 0 //块语句内行首行尾是否要空行 158 | "prefer-const": 0 //首选const 159 | "prefer-spread": 0 //首选展开运算 160 | "prefer-reflect": 0 //首选Reflect的方法 161 | "quotes": [1 "single"] //引号类型 `` "" '' 162 | "quote-props":[2 "always"] //对象字面量中的属性名是否强制双引号 163 | "radix": 2 //parseInt必须指定第二个参数 164 | "id-match": 0 //命名检测 165 | "require-yield": 0 //生成器函数必须有yield 166 | "semi": [2 "always"] //语句强制分号结尾 167 | "semi-spacing": [0 {"before": false "after": true}] //分号前后空格 168 | "sort-vars": 0 //变量声明时排序 169 | "space-after-keywords": [0 "always"] //关键字后面是否要空一格 170 | "space-before-blocks": [0 "always"] //不以新行开始的块{前面要不要有空格 171 | "space-before-function-paren": [0 "always"] //函数定义时括号前面要不要有空格 172 | "space-in-parens": [0 "never"] //小括号里面要不要有空格 173 | "space-infix-ops": 0 //中缀操作符周围要不要有空格 174 | "space-return-throw-case": 2 //return throw case后面要不要加空格 175 | "space-unary-ops": [0 { "words": true "nonwords": false }] //一元运算符的前/后要不要加空格 176 | "spaced-comment": 0 //注释风格要不要有空格什么的 177 | "strict": 2 //使用严格模式 178 | "use-isnan": 2 //禁止比较时使用NaN,只能用isNaN() 179 | "valid-jsdoc": 0 //jsdoc规则 180 | "valid-typeof": 2 //必须使用合法的typeof的值 181 | "vars-on-top": 2 //var必须放在作用域顶部 182 | "wrap-iife": [2 "inside"] //立即执行函数表达式的小括号风格 183 | "wrap-regex": 0 //正则表达式字面量用小括号包起来 184 | "yoda": [2 "never"]//禁止尤达条件 185 | -------------------------------------------------------------------------------- /Express VS Koa.md: -------------------------------------------------------------------------------- 1 | ##Express VS Koa 2 | 3 | 这几天使用了一下Koa.js, 之前很多次听人说起它的精简,优雅。 4 | 于是我怀着这样的心情来使用它,但是用下来后,我发现它的api设计有些不合常理。 5 | 我们来对Express 和 Koa 做一个对比: 6 | | 功能 | Express | Koa | 7 | | :-------- |:--------:| :--: | 8 | |核心模块 | 有 | 有 | 9 | | 路由 | 有 | 无 | 10 | | 模版 | 有 | 无 | 11 | | 富响应 | 有 | 无 | 12 | | 中间件 | 支持 | 支持 | 13 | | 流程控制 | 无 | 支持Generator | 14 | 15 | 从上面的对比中,我们可以看出来,Koa精简是有原因的,因为它只保留了核心模版,其他的路由,模版,富响应(下载文件,JSONP等)都需要以插件的方式来进行按需安装。这...不精简有鬼了。 16 | 我们来谈一下流程控制部分,在Koa中借助Generator函数,使用Co来对它进行流程控制。 17 | 这种方式的引入,是Koa被人接受的最大理由,个人也非常认同这种方式的处理。这种方式带来了什么? 18 | 19 | > 使异步变成同步的书写方式,可读性增强 20 | > 异常处理变的可控 21 | > 避免callback hell问题,以及Promise不够自然的链式问题,冗余代码问题 22 | > 中间件以装配器模式引入,跟Express的链式模式不同 23 | 24 | **koa中的req,res** 25 | 关于这点,是我个人非常不喜欢koa的一个最重要的原因,有些过度封装。在Koa中,没有明确的response,request。 26 | 如果你想返回参数,用this.body=data 的方式。这样等于弱化了web开发的很多概念,非常不好。 27 | 28 | > 如果我们能在Express中引入基于Generator的流程控制,那么Express就能变的更加的强大。因此个人开发了[power-express插件](https://www.npmjs.com/package/power-express),通过它,可以使得在Express中像Koa中一样,使用Generator来进行流程控制。并且对于中间件来说,并没有严格要求使用Generator函数,这使的链式跟装配器模式共存,它能为开发上带来更多的灵活性。 29 | 30 | 下篇我们来讲讲power-express是如何实现的... 31 | 32 | -------------------------------------------------------------------------------- /Flux的理解.md: -------------------------------------------------------------------------------- 1 | # Flux的理解 2 | ### flux是什么? 3 | flux不是一个具体的框架,而是Facebook提出的一种代码架构。 4 | React只是一个视图库,mvc中react只是v,flux是在React基础上对于前端整体的组织方案。Flux中包括了存储(store),动作(action),分发器(dispatcher),视图(view),我们可以理解成react只是flux中的一部分,flux中可以不采用react,使用一些其它类似的来代替,都没有关系。它只是一种设计思想,它并不局限于某种框架。为什么facebook会推出它,传统的mvc只适用中小型系统,对于大型系统来说,mvc复杂度过高,因为mvc一般来说,一个m或者一个v都会对应一个c,他们会有一个对应关系,如果mvc的其中一部分变得非常复杂,那么就会导致整个mvc结构非常庞大,并且逻辑关系,依赖关系非常复杂。 5 | **flux的目的就是保证逻辑清晰,数据流向清晰,依赖关系清晰。**当你用mvc来开发一个大型项目的时候,你会发现很多是耦合的,你不知道一个操作会产生什么后果,你修改了一个m,会导致很多c很多v 无法正常工作,因为他们之间的关系非常隐蔽非常复杂。但是fulx中,把这些关系都清晰罗列出来,并且每一部分,都专注于自己的事情,而且数据流是单一的,这和react中的思想是一致的。flux贯彻了这一点,在整个项目中都保证了单向的数据流动,这就大大减少了整体的依赖关系,复杂关系。 6 | ![enter image description here](http://iamhades.oss-cn-shanghai.aliyuncs.com/flux-diagram-white-background.png) 7 | 8 | 上图为flux的数据流动图,从中我们可以看出其分四个大块,Dispatcher,Store,React Views,Action Creators。 9 | 10 | 11 | **我们从一个简单的小列子来读懂这张图**:我们构建了一个表单,当用户输入一个值后,其会触发User Interactions,这个时候就会把这个触发操作,传递给action creators,这个action creators 会根据你的要求来产生一个action,比如说输入一个值,那么产生一个对应的action,这个action会传递给dispatcher,dispatcher可以理解成一个大脑,它接受到这个aciton之后,它会处理这些数据,这个本质上就是一个数据在不断流动,dispatcher中会调用不同的store,sotre就是mvc中的m,你可以这么理解,当然他们中有些差别,总的来说store就是用来存储数据。每建立一个新的sotre,就会向dispatcher中去注册,等于我告诉dispatcher,我这里有一个新的store,如果你需要就来找我。这样的话如果有一个action到达dispatcher,然后就会去dispatcher中找,谁能处理这个action,比如说这个表单,对应一个store,dispatcher找到之后就会说,我找到了你的store,然后就会把这个action传递给store,因为store在注册的时候会告诉dispatcher有个回调函数,也是就是callback,所以说dispatcher就可以通过回调函数把一些操作发送给store,这个时候数据就流动到了store,sotre拿到数据后,就是非常简单了,进行一些增删改查,完毕之后,它会发出一个event事件,广播一个事件,比如说,它添加了一条记录,它就会发出一个事件说,我添加了一纪录,这个事件就会广播到整个view,如果view中有人关心这个事件,我们知道view中可能有很多东西,比如评论,表单,用户,文章等组件,但是每个组件关注的数据不一样,比如说表单组件收到这个更新表单的事件,它就会知道这是我需要的,它就会向sotre获取新数据,store就会把更新后的数据给veiw,view拿到数据后setState来更新自己的状态,状态更新之后了就会触发对应的reader,界面就会发生变化。这个完整的流程就结束了。 12 | 13 | >数据流向的简单总结:首先是view界面,用户进行操作之后,view会触发一个action,action负责具体的操作,action知道了你的改动之后就会操作传递给你的dispatcher,dispatcher拿到你的操作之后就会去找对应的store,把操作应用进去。store修改玩数据之后,会广播一个事件,事件广播出来之后,哪个view关心哪个veiw就去要数据,更新整个组件视图。 14 | 15 | 一般来说我们不止一个view,一个dispatcher,他们之间不是一对一的关系,也就是说不必非要一个store对应一个view,一个store可以对应多个veiw,一个view也可以对应多个action。这样做了分层抽象思想,store中处理数据的时候不用担心谁去用它,它的作用只是单纯的处理数据,view拿到数据后,不同的view会根据不同的情况进行展示,这个跟store没有关系,这样就解耦开了,同样action,dispatcher原理也一样,这样的抽象,分层,就可以比较好的提高聚合度,减少耦合性。 16 | -------------------------------------------------------------------------------- /Mac下初尝Docker.md: -------------------------------------------------------------------------------- 1 | #Mac下初尝Docker 2 | 3 | ###mac下安装Homebrew 4 | 5 | 在终端中输入: 6 | 7 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 8 | 9 | 如果不是管理用户,需要用sudo命令来执行。 10 | 安装完成以后,如果在运行 brew install 命令的过程中,出现错误提示: 11 | /usr/local/bin/brew: line 26: /usr/local/Library/brew.rb: Undefined error: 0。 12 | 13 | 输入以下命令进行修复即可: 14 | 15 | cd /usr/local/Library 16 | 17 | git pull origin master 18 | 19 | ###mac下安装Docker的管理工具Panamax 20 | brew install http://download.panamax.io/installer/brew/panamax.rb && panamax init 21 | 22 | ###mac下安装Docker 23 | 24 | mac下通过boot2docker 进行下载安装,其地址为: 25 | https://github.com/boot2docker/osx-installer/releases 26 | 27 | Docker Hub 为:https://hub.docker.com/, 请注册一个账号,用以管理你的docker image,国内下载 Docker HUB 官方的相关镜像可能比较慢,可以使用 docker.cn 镜像 28 | 29 | 安装好的下载的boot2docker后,点击启动,当看见To connect the Docker client to the Docker daemon的字样,表示启动完毕。 30 | 建议将下面关于docker的变量属性设置为环境变量,设置方式为: 31 | 打开 `~/.bash_profile` 文件,然后在其后面追加docker的变量即可,最后输入`source ~/.bash_profile` 重置系统的环境变量,是否重置成功,可以输入 echo $XXX的方式 进行查看。 32 | 33 | 启动好的docker ,可以通过以下命令来开发端口的访问权限: 34 | 比如:docker run --rm -i -t -p 80:80 nginx 35 | 再用:boot2docker ip 来获取当前docker的ip 36 | 然后就可以通过该ip+端口,就可以访问到docker里面运行的nginx了。 37 | 38 | 注意在boot2docker里面,默认的用户和密码是:`docker/tcuser` 39 | 40 | 在docker中运行image,需要先到docker hub网站选择你需要的image来运行。 41 | 42 | ####docker常用命令 43 | docker ps 显示正在运行的image 44 | 45 | docker ps -a 显示所有运行过的image,通过里面的STATUS字段,可以查看image状态 46 | 47 | docker ps -l 显示刚刚运行的image 信息 48 | 49 | docker run xxx 运行image,如果该image不存在,则其会先自动到docker hub上下载, 加上参数-d表示以后台进程运行,-p表示指定端口映射,如果想容器外,能访问到docker运行的image服务,需要设定-p参数,来对外开放端口,比如 docker run -d -p 6379:6379 redi 启动后,可以通过boot2docker ip的在外部ip+6379进行访问。 50 | 51 | dokcer stop xxx 根据image ID 来停止某个image。其会有10秒的强杀时间。 52 | 53 | docker run `-i -t` centos:centos6 `/bin/bash` 可以通过这个命令运行os的shell,输入exit 则就可以退出该进程, -i标志保证容器中STDIN是开启的以提供持久的标准输入是交互式shell,-t标志则告诉Docker为要创建的容器分配一个伪tty终端。这样,新创建的容器才能提供一个交互式shell。 54 | 55 | 56 | ###docker需要下载的image 57 | docker pull redis 58 | docker pull mongo 59 | docker pull mysql 60 | docker pull nginx 61 | docker pull node 62 | 63 | ###docker 启动node项目 64 | 我们可以把源代码放在host上,然后利用docker run -v选项把源代码的文件路径映射到docker container中,这样container就可以直接使用host上的Node代码开启服务。 65 | 首先你要进入到node的目录中,然后输入以下命令就可以启动。 66 | 67 | docker run -it --rm --name YYY -v "$PWD":XXX -w XXX node ZZZ 68 | 69 | 其中YYY为自定义这次启动的image进程的名字,通过不同的命名,可以启动多个image, 70 | XXX为映射的路径,ZZZ为执行的node命令。 71 | 72 | 比如:docker run -it --rm --name `nodeTest` -v "$PWD":`/Users/tangnian/WebstormProjects/huodongla` -w `/Users/tangnian/WebstormProjects/huodongla` node `npm start` 73 | 74 | 75 | ###制作node服务的image 76 | 77 | 方式有2种: 78 | 79 | 1:一种是run container 的时候通过 -v 参数将 host 文件或目录加载/共享到 container 里。 80 | 命令为:`docker run -v XXX:YYY -i -t ZZZ AAA` 81 | XXX为host上文件地址,YYY为image中地址,ZZZ为image名称,AAA为在image中需要运行的命令 82 | 如: docker run -v /Users/tangnian/nodeDeveloper/src:/root -i -t easefit/node node /root/huodongla/index.js 83 | 2:制作包含host中node代码的image,然后直接运行该image。 84 | 对于第二种方式,可以采取以下命令来实现: 85 | 1. 创建一个新的文件夹,如:mkdir test,进入该目录,并将app放入该目录 86 | 2. 用命令:touch Dockerfile 创建一个Dockerfile,并在Dockerfile中输入以下配置 属性,如: 87 | 88 | ``` 89 | from node 90 | copy . /usr/src 91 | WORKDIR /usr/src/app 92 | 93 | ``` 94 | 最后再执行`docker build -t XXX` . 的命令。其中XXX为新创建的image的名称,注意要用easefit/node 这样的方式来取名。对于创建好的image,可以通过运行 `docker rmi XX`来删除。注意add . /user/src 是将当前目录下面的文件,都添加到/usr/src下面去,所以最后image中的文件路径为/usr/src/app。 95 | 96 | 通过运行 docker run -p 8111:8111 XXX npm start 来运行自己创建的node ima###制作node服务的image 97 | 98 | ###DockerFile中添加的参数说明 99 | FROM 是作为镜像的基础 100 | RUN 可以理解为在FROM下来的镜像做一些环境的部署。 101 | CMD 是创建容器后,会运行的命令 102 | EXPOSE 是暴露的端口 103 | MAINTAINER 通知的邮件 104 | ADD/COPY 里面2个参数,第一个为需要放进image的data路径,第二个image的路径。注意路径需要为相对路径 105 | VOLUME 是本地的路径的映射 106 | WORKDIR 是执行的路径,也就是cmd entrypoint执行的路径。 107 | 比如: 108 | 109 | ``` 110 | FROM centos:centos6 111 | # Enable EPEL for Node.js 112 | RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm 113 | # Install Node.js and npm 114 | RUN yum -y update 115 | RUN yum install -y npm 116 | # Bundle app source 117 | COPY . /src 118 | # Install app dependencies 119 | RUN cd /src; npm install 120 | EXPOSE 8080 121 | CMD ["node", "/src/index.js"] 122 | 123 | ``` 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ####个人文章集 2 | 你可以登录[http://www.iamhades.com](http://www.iamhades.com)的个人博客进行查阅 3 | -------------------------------------------------------------------------------- /mongoDao/README.md: -------------------------------------------------------------------------------- 1 | ##关于mongodbDao 2 | 在基于[Node的快速web开发框架](https://github.com/iAmHades/node-web-boilerplate)中,有一个文件叫mongodbDao.js 。 3 | 它是对官方提供的[MongoDB针对node.js](https://www.npmjs.com/package/mongodb)的驱动包的一个封装,以更好的适应业务开发。本文是对该封装的一个详细解说,同时会附带一些mongodb的一些特性说明。 4 | 5 | 发布在gitbook上的地址[点击浏览](https://hades.gitbooks.io/mongodbdao/content/publish/intr.html) 6 | 7 | ####官方文档 8 | | 分类 |地址 | 9 | |---------------|------------------------------------------------| 10 | | documentation | http://mongodb.github.io/node-mongodb-native/ | 11 | | api-doc | http://mongodb.github.io/node-mongodb-native/2.1/api/ | 12 | | source | https://github.com/mongodb/node-mongodb-native | 13 | | mongodb | http://www.mongodb.org/ | 14 | 15 | -------------------------------------------------------------------------------- /mongoDao/SUMMARY.md: -------------------------------------------------------------------------------- 1 | * [关于MongoDB](publish/intr.md) 2 | * [参数配置](publish/config.md) 3 | * [增加数据](publish/add.md) 4 | * [删除数据](publish/removeData.md) 5 | * [修改数据](publish/updateData.md) 6 | * [查询数据](publish/queryData.md) 7 | - [分页查询](publish/pagingQuery.md) 8 | - [正则表达式查询](publish/regxQuery.md) 9 | - [比较查询](publish/comparaQuery.md) 10 | - [和/或查询](publish/orand.md) 11 | - [group](publish/group.md) 12 | - [distinct](publish/distinct.md) 13 | - [findAndModify](publish/findAndModify.md) 14 | - [findAndRemove](publish/findAndRemove.md) 15 | - [count](publish/count.md) 16 | * [数组操作](publish/array.md) 17 | * [$inc自增长](publish/inc.md) 18 | * [MapReduce](publish/mapReduce.md) 19 | * [二段提交](publish/twosubmit.md) 20 | 21 | 22 | -------------------------------------------------------------------------------- /mongoDao/publish/add.md: -------------------------------------------------------------------------------- 1 | ##添加数据方法的实例 2 | > **save ( collection, data)** ------ 插入新增数据 3 | > - conllection (String) -- 集合名 4 | > - data (Object) -- 插入的数据,同一个集合,其data可以不同。 5 | > - return (Object) -- 返回数据插入后的数据对象,里面会有_id属性。 6 | 7 | 8 | #####用法如下: 9 | var userData={userName:"卫龙飞",age:"30"}; 10 | mongodbDao.save('User', userData).then(function (data) { 11 | // TODO 12 | }) 13 | 14 | 15 | 16 | **在使用save进行插入的时候,你不必像关系型数据库那样先创建表。如果User存在,则直接插入User的集合中,如果不存在,则先自动创建,再插入。** -------------------------------------------------------------------------------- /mongoDao/publish/array.md: -------------------------------------------------------------------------------- 1 | ##数据内部属性是数组的操作实例 2 | **向数据库插入一条数据 数据有个属性就是数组** 3 | ``` 4 | var egData={ 5 | userName: "hades", 6 | age: 30, 7 | address: "xxxxxx", 8 | friends: ["x","y","z"] 9 | } 10 | ``` 11 | 12 | **判断egData里面的friends数组里面有没有"x"这个数据** 13 | ``` 14 | var selector={ 15 | "friends": { 16 | "$all": ["x"] 17 | } 18 | }; 19 | mongodbDao.query('User', selector).then(function(data){ 20 | //TODO 21 | }) 22 | ``` 23 | 24 | **向egData里面的friends数组里面增加一个"lulu" 并且原先数组里面没有这个数据在添加** 25 | ``` 26 | var selector={ 27 | "$addToSet": { 28 | "friends": "lulu" 29 | } 30 | } 31 | mongodbDao.update('User', {userName: "hades"}, selector, false, false).then(fucntion(data){ 32 | // TODO 33 | }) 34 | ``` 35 | 36 | **删除egData里面的friends数组里面的"x"数据** 37 | ``` 38 | var selector={ 39 | "$pull": { 40 | "friends": "x" 41 | } 42 | } 43 | mongodbDao.update('User', {userName: "hades"}, selector).then(fucntion(data){ 44 | // TODO 45 | }) 46 | ``` 47 | **删除多个数据 使用pullAll** 48 | 49 | -------------------------------------------------------------------------------- /mongoDao/publish/batchAddData.md: -------------------------------------------------------------------------------- 1 | ##**批量数据插入** 2 | >####saveBatch(collection, data) 3 | >+ conllection   [type:string]   需要创建并且保存数据的表名 4 | >+ data    [type:数组对象]    需要插入到数据库的大批量数据 5 | >+ return    [type:int]    批量插到数据库的数量 6 | 7 | #####我们这里的批量插入采用先将数据放入数组里面,然后调用saveBatch方法执行,他的效率高于使用for循环将数据一条条save到数据库。 8 | var batchData=[ 9 | {userName:"大飞01",passWord:"露露01"}, 10 | {userName:"大飞02",passWord:"露露02"}, 11 | {userName:"大飞03",passWord:"露露03"}, 12 | {userName:"大飞04",passWord:"露露04"} 13 | ]; 14 | 15 | mongodbDao.saveBatch('User',batchData).then(function(data){ 16 | console.log("批量插入数据",data); 17 | }) -------------------------------------------------------------------------------- /mongoDao/publish/comparaQuery.md: -------------------------------------------------------------------------------- 1 | ##比较查询 2 | **query( collection, selector )方法中的selector参数进行比较查询的时候的设置如下:** 3 | 4 | 例如需要查询用户表中年龄大于25的所有人的数据信息 5 | ``` 6 | var selector={age: {$gt: 25 }}; 7 | ``` 8 | 例如需要进行一个范围查询,年龄在大于20,小于30的所有人的数据信息 9 | ``` 10 | var selector={age: {$gt: 20, $lt:30}}; 11 | ``` 12 | 在时间上的查询,也是可以做的,如下: 13 | ``` 14 | var selector={activityStartTime: {$gt: new Date(), $lt:new Date(2015-01-01)}}; 15 | ``` 16 | #####其中的$gt的意思就是“大于”,其它比较符如下: 17 | | 符号 | 含义 | 18 | | :--------:| :--------: | 19 | | $lt | < | 20 | | $lte | <= | 21 | | $gt | > | 22 | | $gte | >= | 23 | | $ne | != | 24 | 25 | 26 | -------------------------------------------------------------------------------- /mongoDao/publish/config.md: -------------------------------------------------------------------------------- 1 | ##配置参数设置 2 | mongodb的配置参数设置在名为config.js的文件中,如下: 3 | 4 | ``` 5 | //mongodb开发环境 6 | dbhost: '开发环境的数据库ip', 7 | dbport: '开发环境的数据库端口', 8 | //mongodb正式环境 9 | dbhost1: '副本集主节点ip', 10 | dbhost2: '副本集副节点ip', 11 | dbport1: '副本集主节点端口', 12 | dbport2: '副本集副节点端口', 13 | replSetName:'副本集名称', 14 | dbname: "数据库名", 15 | dbusername:'登录用户名', 16 | dbpwd: '登录密码', 17 | ``` 18 | 19 | 我们采用了官方推荐的Replica Sets的方式启动mongodb,以保证服务的稳定性。 20 | 服务在启动的时候,如果是直接node index.js的方式启动,则它是以开发环境启动的,它连接的是开发环境的数据库。想以生产模式启动,需要以NODE_ENV=PRODUCTION node index.js的方式启动。 21 | 22 | -------------------------------------------------------------------------------- /mongoDao/publish/count.md: -------------------------------------------------------------------------------- 1 | ##统计数据量 2 | >**getCount(collection, selector) ---- 统计指定条件的数据量** 3 | >- conllection (String) -- 查询数据的表名 4 | >- selector (Object) -- 需要查询数据的匹配条件 5 | >- return (Num) -- 统计的数据 6 | 7 | var selector = {userName: 'hades'}; 8 | mongodbDao.getCount('User', selector).then(function (data) { 9 | // TODO 10 | }) 11 | 12 | 13 | -------------------------------------------------------------------------------- /mongoDao/publish/distinct.md: -------------------------------------------------------------------------------- 1 | ##去重数据查询 2 | >**distinct(collection, filed) ---- 过滤重复的指定的字段查询** 3 | >- conllection (String) -- 查询数据的表名 4 | >- fied (Object) -- 去重字段 5 | >- return (Object) -- 所有去重后数据 6 | 7 | var fied =['userName','address']; 8 | mongodbDao.distinct('User', fied).then(function (data) { 9 | // TODO 10 | }) 11 | 12 | 13 | -------------------------------------------------------------------------------- /mongoDao/publish/findAndModify.md: -------------------------------------------------------------------------------- 1 | ##findAndModify 2 | >**findAndModify(collection, selector, newData, sort) ---- 查询并修改的操作,注意该方法属于一个原子操作** 3 | >- conllection (String) -- 查询数据的表名 4 | >- selector (Object) -- 需要修改数据的匹配条件 5 | >- newData (Object) -- 更新数据 6 | >- sort (Object) -- 排序规则 7 | >- return (Object) -- 修改后的数据 8 | 9 | 更新最常见的操作是根据某些条件查询出文档,然后对查询出的文档进行某些更改,如果我们分步进行操作(先查询、然后更改),这样会带来线程安全隐患。mongoDB为了避免这种问题,为我们提供了原子性的命令findAndModify,下面对findAddModify方法进行说明 10 | 11 | var selector =['userName','卫龙飞']; 12 | mongodbDao.findAndModify('User', selector,{address:"北京"},{}).then(function (data) { 13 | // TODO 14 | }) 15 | findAndModify命令一个速度较慢的原子性操作,它的优点在于是原子性操作,而缺点是运行速度较慢。他会对query条件查询出来的第一条数据第进行修改操作(这里要注意sort排序规则,因为是先对query出来的数据排序,在更新第一条数据。如果排序规则为空,默认为按数据插入时间降序排序。),如何query条件没有匹配到数据,那么数据库会插入一条新的数据,数据就是newData。findAndModify最重要的特性就是其原子性,保证线程安全。 16 | -------------------------------------------------------------------------------- /mongoDao/publish/findAndRemove.md: -------------------------------------------------------------------------------- 1 | ##findAndRemove 2 | >**findAndRemove(collection, selector, newData, sort) ---- 查询并删除的操作,注意该方法属于一个原子操作** 3 | >- conllection (String) -- 查询数据的表名 4 | >- selector (Object) -- 需要修改数据的匹配条件 5 | >- sort (Object) -- 排序规则 6 | >- return (Object) -- 修改后的数据 7 | 8 | 用法如下: 9 | 10 | var selector =['userName','hades']; 11 | mongodbDao.findAndRemove('User', selector,{}).then(function (data) { 12 | // TODO 13 | }) 14 | findAndRemove方法会先按查询条件匹配数据,然后排序,最后删除第一条数据,整个过程没有并发问题。 15 | 16 | -------------------------------------------------------------------------------- /mongoDao/publish/group.md: -------------------------------------------------------------------------------- 1 | ##分组查询 2 | >group(collection, keys, condition, initial, reduce) ---- 根据条件进行分组查询 3 | >- conllection (String) -- 查询数据的表名 4 | >- keys (Object) -- 分组键 5 | >- condition (Object) -- 匹配条件 6 | >- initial (Object) -- 表示$reduce函数参数prev的初始值。每个组都有一份该初始值 7 | >- reduce (Function) -- reduce函数接受两个参数,doc表示正在迭代的当前文档,prev表示累加器文档 8 | >- return ([Object]) -- 分组后查询到的数据 9 | 10 | var keys={age:true}; 11 | var condition={address:"上海"}; 12 | var initial={count: 0}; 13 | var reduce={ 14 | reduce: function(doc,prev){ 15 | prev.count++; 16 | } 17 | } 18 | mongoDao.group('User',keys, condition, initial, reduce}).then(function(data){ 19 | // TODO 20 | }); 21 | 22 | 23 | -------------------------------------------------------------------------------- /mongoDao/publish/inc.md: -------------------------------------------------------------------------------- 1 | ##$inc自增长 2 | 业务中总存在着字段需要自增长的情况,我们如果通过先查询出来,再计算后更新的方式来实现,会存在并发问题。所以mongodb提供了$inc这样的属性来做这样操作,整个过程保证是原子性。 3 | 4 | **字段age值增加2** 5 | 6 | ``` 7 | mongodbDao.updateAdv('User',{userName:"卫龙飞"},{"$inc":{"age":2}}).then(function(data){ 8 | // TODO 9 | }) 10 | ``` 11 | 12 | 注意在使用$inc的时候,不能使用update方法,要使用updateAdv方法。 13 | 14 | -------------------------------------------------------------------------------- /mongoDao/publish/intr.md: -------------------------------------------------------------------------------- 1 | ##MongoDB 2 | 3 | [MongoDB](http://docs.mongoing.com/manual-zh/index.html) 是 NoSQL 的代表之一,其采用 **JSON/BSON**当做数据储存格式,亦使用 JavaScript 做为 Server-side 的执行语言(相当于PLSQL)。它的这些特性,使得其成为NodeJs的推荐搭配数据库。 4 | 在结合业务开发的需要,对MongoDB的driver做了封装,以适合快速的业务开发需要和更加的面向对象。 5 | 6 | ####封装思路及要求如下: 7 | > - 在官方driver的基础上,封装一个Dao层,该Dao层提供统一的接口规范,以屏蔽不同driver的接口变动对业务层的影响。 8 | > - 利用封装后的Dao层,构建BaseService,后续所有的业务Service 继承该类,以提供基础CRUD需求,增加代码的复用性。 9 | > - BaseService层中,注入的Dao实列,在满足接口规范的情况下,可随意进行其他数据库Dao层的切换(如:mySql)。 10 | > - 需要考虑服务的高可用性,目前业务数据量暂不考虑分片,故需要引入Replica Set的方式。 11 | > - 发挥NoSql数据库的精髓,给予表结构充分的自由,不用像mongoose那样的Schema Model,当然这使的你在开发的时候需要当心一些。 12 | > - 需要引入Promise,来增加代码的复用性,同时避免回调地狱。我们选用的Promise第三方库是 [Q](https://www.npmjs.com/package/q) 13 | 14 | ####我们现在使用的MongoDB3.0的特性: 15 | > - 数据存储的格式是BSON。 16 | > - 文档级别并发控制,3.0以前是库级别。 17 | > - 缺少事物支持,需要二段提交的方式来解决。 18 | > - HA支持3种工作方式:Master-Slave, Replica Set, Sharding。 19 | > - 支持动态查询。支持完全索引,包含内部对象。 20 | 21 | ####使用MongoDB的注意点: 22 | > - 在国内服务器存在一个时区的问题,可能需要我们手动的加上8个小时的时差。在我们的框架中默认对save操作会增加一个createTime字段,对update操作会增加一个updateTime字段,它们都有做加8小时的处理。 23 | > - 由于却少数据模型的约束,所以会给开发者极大的自由,在整个的开发过程中,会非常的爽,但是这可能会导致很多隐性的bug存在,所以还是请使用者注意下规范,测试案列的编写必不可少。 24 | 25 | -------------------------------------------------------------------------------- /mongoDao/publish/mapReduce.md: -------------------------------------------------------------------------------- 1 | 2 | #mapReduce 3 | 4 | ##mapReduce是用于数据分析、统计的函数 5 | ``` 6 | db.collection.mapReduce( 7 | (map), 8 | (reduce), 9 | (options):{ 10 | out: , 11 | query: , 12 | sort: , 13 | limit: , 14 | finalize: , 15 | scope: , 16 | jsMode: , 17 | verbose: , 18 | bypassDocumentValidation: 19 | } 20 | ) 21 | 22 | ``` 23 | ###参数: 24 | - **map** :(function) 用emit函数发出(key, value)键值对,value是一个集合 25 | - **reduce** :(function) 把所有map函数中发出的键值对按照key的值聚合为(key,values),values是key相同的value的数组 26 | - **option** :(document) 控制mapReduce过程的参数 27 | 28 | ###option参数: 29 | - **query** : (document) 集合的查询参数,map函数使用. 30 | - **sort** : (document) 集合按该字段排序,必须存在.设置该字段和emit发出的key字段相同可以提高map函数执行效率 31 | - **limit** : (number) 设置最大数量的集合数 32 | - **finalize** : (function) 用户对mapreduce最终结果的修改 33 | - **scope** : (document) mapreduce过程中可以访问到的对象集合 34 | - **out** : (string) 统计结果存放的表名 35 | - **jsMode** : (Boolean) 是否减少执行过程中BSON和JS的转换,默认为true 36 | - **verbose** :verbose (Boolean) 是否显示详细的时间统计信息,默认为true 37 | 38 | ## 用法如下: 39 | 40 | 一个存放店家订单的文档,要统计一段时间内每个店完成的订单数量和总价格 41 | ###订单表格式: 42 | ``` 43 | { 44 | "_id" : ObjectId("56944bc9b78fe0f714d7ff32"), 45 | "price" : 20, 46 | "shopid" : "29000007", 47 | "orderId" : "D290000071600020100", 48 | "createTime" : ISODate("2016-01-12T08:41:45.872Z") 49 | } 50 | ``` 51 | 52 | ### 代码块 53 | ``` 54 | var map = function () { 55 | emit(this.shopid, { 56 | counts: 0, 57 | price: 0 58 | }); 59 | emit(this.shopid, { 60 | counts: 1, 61 | price: this.price 62 | }); 63 | }; 64 | ``` 65 | 66 | > **注意:**emit函数执行两次是因为如果只有一条数据的情况下不执行reduce函数,这个问题不清楚是不是mapReduce故意为之, 真* * *别扭 67 | 68 | ``` 69 | var reduce = function (key, values) { 70 | 71 | var result = { 72 | counts: 0, 73 | price: 0 74 | }; 75 | 76 | //数据聚合 77 | values.forEach(function (value) { 78 | result.counts += value.counts; 79 | result.price += value.price; 80 | }); 81 | 82 | return result; 83 | }; 84 | 85 | collection.mapReduce(map, reduce, { 86 | //输出结果 87 | out: {replace: 'shopStatistics'}, 88 | scope: {}, 89 | //查询start到end这段时间内的订单 90 | query: {createTime: {$gt: start, $lt: end}} 91 | }, 92 | function (err, collection) { 93 | if (err) { 94 | deferred.reject(new Error(err)); 95 | } else { 96 | collection.find().toArray(function (err, results) { 97 | if (err) { 98 | deferred.reject(new Error(err)); 99 | } else { 100 | deferred.resolve(results); 101 | } 102 | }); 103 | 104 | } 105 | }); 106 | ``` 107 | 108 | 109 | 110 | ##吐槽一下mapReduce的坑 111 | 112 | ###问题提出 113 | 在reduce函数中values并不是一个连续的数组,而是一个链表结构的,每个节点存储100个键值对,每个节点有一个索引指向下一个节点。 114 | 115 | > **结构:**values --> [100个键值对] --> [100个键值对] --> [53个键值对] 116 | 117 | 在reduce函数中聚合操作是这样写的: 118 | ``` 119 | values.forEach(function (value) { 120 | result.counts += value.counts; 121 | result.price += value.price; 122 | }); 123 | 124 | ``` 125 | 这种情况下不会出现问题。问题稍作修改假设只统计订单金额大于50的订单,代码改成这样 126 | 127 | 128 | ``` 129 | values.forEach(function (value) { 130 | if(result.price > 50){ 131 | result.counts += value.counts; 132 | result.price += value.price; 133 | } 134 | }); 135 | ``` 136 | 137 | 138 | 理论上不会对mapReduce的结果产生影响,实践证明在reduce函数中丢掉部分数据,通过代码的跟踪调试发现这样的规律,假设数据如下,mapReduce过程按照shopId排序: 139 | - **shop001** 70个订单 140 | - **shop002** 20个订单 141 | - **shop003** 50个订单 142 | 143 | 此时计算shop001、shop002的结果是正确的,但是shop003只计算了后面40个订单的值 10 == 100 -(70 + 20) 50 - 10 = 40 144 | 145 | ###结论:每逢reduce数据横跨100的倍数就会产生问题 146 | 147 | > **猜想:** 在mapReduce的执行过程中对shop003的聚合函数执行了两次,且后一次覆盖的前一次的结果,这个问题搜索了很多论坛,有多人提出过这个问题,但是目前没看到解决的办法。 148 | 149 | 当然在这个简单的问题处理上通过修改query参数能轻松解决,但是涉及到mapReduce的表结果稍微复杂,或者表设计时候就存在一些判断逻辑,这个问题无法绕过去。哪位大牛解决过这个问题,务必不吝赐教。 150 | 151 | 当然程序员不能在一棵树上吊死,替代办法: 152 | ###聚合管道aggregate 153 | 154 | -------------------------------------------------------------------------------- /mongoDao/publish/orand.md: -------------------------------------------------------------------------------- 1 | ##和/或查询 2 | 查询条件为 ‘or’ 的时候,其设置为: 3 | 4 | ``` 5 | var selector={ $or: [{ name:'hades', age:'31'}] } 6 | ``` 7 | 查询条件为'and' 的时候,其设置为: 8 | 9 | ``` 10 | var selector={ $and: [{ name:'hades', age:'31'}] } 11 | ``` 12 | 更为复杂一点的查询如下: 13 | 14 | ``` 15 | var selector={ $and: [{ name: 'hades', age: {$lt: 30}}] } 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /mongoDao/publish/pagingQuery.md: -------------------------------------------------------------------------------- 1 | ##常用的分页查询 2 | >####findPagingData(collection,selector,pageRequest) ----分页查询 3 | >- conllection (String) -- 查询数据的表名 4 | >- selector (Object) -- 查询过滤条件 5 | >- pageRequest (Object) -- 分页对象,里面包含start开始的数据下标,limit一次查询的数据量,sort排序规则 6 | >- return (Object) -- 返回值是一个比较复杂的javaScript Object, 如果获取分页数据,可以直接获取返回对象的records属性值 7 | 8 | 9 | var selector={age:30}; 10 | var pageRequest = {}; 11 | pageRequest.start = 1; 12 | pageRequest.limit = 10; 13 | pageRequest.sort = sort ? sort : {createTime: -1}; 14 | mongodbDao.findPagingData('User', selector,pageRequest).then(function(userData){ 15 | // TODO 16 | }) 17 | 18 | 19 | -------------------------------------------------------------------------------- /mongoDao/publish/queryData.md: -------------------------------------------------------------------------------- 1 | ##**调用查询数据的方法实例** 2 | >**query( collection, selector )**------ 根据条件查询数据 3 | >- conllection(String) -- 查询数据的表名 4 | >- selector (Object) -- 指定条件的数据对象。其中值可以为字符串,但是该字符串只能是数据库内部_id的 值,当是它的时候,其会操作掉该条数据 5 | >- return ([Object]) -- 查询出所有匹配条件的数据 6 | 7 | #####用法如下 8 | var selector={ userName: "卫龙飞" }; 9 | mongodbDao.query('User', selector).then(function (data) { 10 | // TODO 11 | }) 12 | 13 | 14 | >**findOne( collection, data )** ------ 根据条件查询一条数据 15 | >- conllection(String) -- 查询数据的表名 16 | >- selector (Object) -- 指定条件的数据对象。其中值可以为字符串,但是该字符串只能是数据库内部_id的 值,当是它的时候,其会操作掉该条数据 17 | >- return (Object) -- 从所有匹配条件的数据中返回第一条 18 | 19 | #####用法如下 20 | var selector={ userName: "卫龙飞" }; 21 | mongodbDao.findOne('User',selector).then(function(data){ 22 | // TODO 23 | }) 24 | 25 | > **queryAdv( collection, selector, fields )** ------ 根据条件查询的用法,其是优化的查询,因为它可以指定返回的字段 26 | >- conllection(String) -- 查询数据的表名 27 | >- selector (Object) -- 指定条件的数据对象。其中值可以为字符串,但是该字符串只能是数据库内部_id的 值,当是它的时候,其会操作掉该条数据 28 | > - fields (Object) -- 指定需要的字段,如{ username : 1, age : 0 } 29 | >- return (Object) -- 从所有匹配条件的数据中返回第一条 30 | 31 | #####用法如下 32 | var selector={userName:"卫龙飞"}; 33 | mongodbDao.queryAdv('User',selector,{usrname:1}).then(function(data){ 34 | // TODO 35 | }) 36 | -------------------------------------------------------------------------------- /mongoDao/publish/regxQuery.md: -------------------------------------------------------------------------------- 1 | ##正则表达式的查询 2 | **query( collection, selector )方法中的selector参数可以设置为正则表达式,如下:** 3 | ``` 4 | var selector={ userName:{ $regex:"卫龙飞" }}; 5 | ``` 6 | 另外一种写法: 7 | ``` 8 | var selector={ userName:/卫龙飞/ }; 9 | ``` 10 | 查询条件如果是由英文字母组成,必然有字母大小写的是区别,如果不需要区分大小写, 需要在查询条件后面加上$options:"$i" 11 | 12 | ``` 13 | var selector={ userName:{ $regex: "weilongfei", $options:"$i" }} 14 | mongodbDao.query('User',selector).then(function(data){ 15 | // TODO 16 | }) 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /mongoDao/publish/removeData.md: -------------------------------------------------------------------------------- 1 | ##删除数据方法的实例 2 | > **remove ( collection, selector )** ------ 按条件删除数据。 3 | > - conllection (String) -- 集合名 4 | > - selector (Object) -- 指定条件的数据对象。其中值可以为字符串,但是该字符串只能是数据库内部_id的 值,当是它的时候,其会删除掉该条数据 5 | > - return (Object) -- 。 6 | 7 | 8 | #####用法如下: 9 | //普通用法 10 | mongodbDao.remove('User', {userName:"卫龙飞"}).then(function (data) { 11 | // TODO 12 | }) 13 | //简洁用法 14 | mongodbDao.remove('User', '56970b5a8d28674a506d2346').then(function(data){ 15 | // TODO 16 | }) 17 | 18 | 19 | **该方法会将符合selector条件的数据都删除,所以这可能是一个批量删除。** 20 | -------------------------------------------------------------------------------- /mongoDao/publish/twosubmit.md: -------------------------------------------------------------------------------- 1 | ##二段提交 2 | 更新中... -------------------------------------------------------------------------------- /mongoDao/publish/updateData.md: -------------------------------------------------------------------------------- 1 | ##调用修改数据的方法实例 2 | 对于mongodb的更新操作来说,其考虑的情况非常的多,所以设置相对也多,但是在实际业务的使用过程中,我们发现大部分情况下,需要的设置是一致的,所以对更新操作做了如下两种封装。 3 | >**update( collection, selector, newData )**------ 常规的更新数据,其强制对newData采用$set操作,并options设定为w=1,upsert=true,multi=true。具体设置[点击查看](https://mongodb.github.io/node-mongodb-native/api-generated/collection.html#update) 4 | >- selector (Object) -- 指定条件的数据对象。其中值可以为字符串,但是该字符串只能是数据库内部_id的 值,当是它的时候,其会操作掉该条数据 5 | >- newData (Object) -- 更新的数据 6 | >- return (int) -- 修改成功的数据数量 7 | 8 | var conditionData={userName:"卫龙飞"}; 9 | var newData={age:"30"}; 10 | mongodbDao.update('User', conditionData, newData).then(function(data){ 11 | // TODO 12 | }) 13 | **这个update方法通常用来更新数据的某些字段的值,当字段不存在的时候将创建这些字段,并不会影响其他字段。** 14 | 15 | >**updateAdv(collection, selector, updateObj, options)**------ 更新数据的高级方法,其可以自定义更新属性 16 | >- collection (String) -- 集合名 17 | >- selector (Object) -- 指定条件的数据对象。其中值可以为字符串,但是该字符串只能是数据库内部_id的 值,当是它的时候,其会操作掉该条数据 18 | >- updateObj (Object) -- 更新设置对象,里面需要使用$inc,$addToSet,$pull,$set等方式。具体设置[点击查看](https://mongodb.github.io/node-mongodb-native/api-generated/collection.html#update) 19 | > - options (Object) -- 更新操作的参数设置,包括w,upsert,multi等设置。具体设置[点击查看](https://mongodb.github.io/node-mongodb-native/api-generated/collection.html#update) 20 | >- return (int) -- 修改成功的数据数量 21 | 22 | var conditionData={userName:"卫龙飞"}; 23 | var updateObj={$set:{age:"30"}}; 24 | var options={w:1,upsert:true}; 25 | mongodbDao.updateAdv('User', conditionData, updateObj, options).then(function(data){ 26 | // TODO 27 | }) 28 | **updateAdv的使用,务必在熟悉[mongodb的特性和api](https://mongodb.github.io/node-mongodb-native/api-generated/collection.html)情况下使用。** 29 | 30 | -------------------------------------------------------------------------------- /nodeapi/README.md: -------------------------------------------------------------------------------- 1 | ##Node.js v6.3.0 Documentation 2 | 3 | 针对[nodejs官方文档](https://nodejs.org/dist/latest-v6.x/docs/api/assert.html)的翻译。 4 | 翻译该文档的原因,是因为最近工作需要,但是发现目前网上中文的版本都没能跟上nodejs的发展脚步,api有些不对。 5 | 出于学习的态度,开始翻译,以后会保持它同官方api的同步更新。 -------------------------------------------------------------------------------- /nodeapi/SUMMARY.md: -------------------------------------------------------------------------------- 1 | * [关于文档](publish/about.md) 2 | * [用法&案例](publish/#) 3 | * [Assertion Testing](publish/#) 4 | * [Buffer](publish/#) 5 | * [C/C++ Addons](publish/#) 6 | * [Child Processes](publish/#) 7 | * [Cluster](publish/#) 8 | * [Command Line Options](publish/#) 9 | * [Console](publish/#) 10 | * [Crypto](publish/#) 11 | * [Debugger](publish/#) 12 | * [DNS](publish/#) 13 | * [Domain](publish/#) 14 | * [Errors](publish/#) 15 | * [Events](publish/#) 16 | * [File System](publish/#) 17 | * [Globals](publish/#) 18 | * [HTTP](publish/#) 19 | * [HTTPS](publish/#) 20 | * [Modules](publish/#) 21 | * [Net](publish/#) 22 | * [OS](publish/#) 23 | * [Path](publish/#) 24 | * [Process](publish/#) 25 | * [Punycode](publish/#) 26 | * [Query Strings](publish/#) 27 | * [REPL](publish/#) 28 | * [Stream](publish/#) 29 | * [String Decoder](publish/#) 30 | * [Timers](publish/#) 31 | * [TLS/SSL](publish/#) 32 | * [UDP/Datagram](publish/#) 33 | * [URL](publish/#) 34 | * [Utilities](publish/#) 35 | * [V8](publish/#) 36 | * [VM](publish/#) 37 | * [ZLIB](publish/#) 38 | 39 | -------------------------------------------------------------------------------- /nodeapi/publish/about.md: -------------------------------------------------------------------------------- 1 | ##关于该文档 2 | 本文档以提供基本的参考信息或者从掌握概念的角度来全面解释Node.js的API。我们按照一个内置模块或一个高层次的概念为章节进行介绍。 -------------------------------------------------------------------------------- /关于微信中回退的问题.md: -------------------------------------------------------------------------------- 1 | ##关于微信中回退的问题 2 | 更新中... 3 | 4 | 回退其实分两种: 5 | > - 层级的回退 6 | > - 快照的回退 7 | 8 | 项目中页面的层级关系,不能随意用history.go的方式,进行跳转,必要的history.back 是必须的。 9 | 这能带来更好的用户体验。比如popstate状态中,state的恢复 -------------------------------------------------------------------------------- /团队内部践行的技术栈.md: -------------------------------------------------------------------------------- 1 | # 团队内部践行的技术栈 2 | 3 | ## 系统环境 4 | Mac系统,整套技术栈也围绕着mac系统来打造。至于为什么选择mac?[理由太多了...](http://mp.weixin.qq.com/s?__biz=MjM5ODIyMTE0MA==&mid=211925064&idx=1&sn=341e2b97415619edfbf656d9ab0b9c51&scene=4#wechat_redirect) 5 | 6 | ## 系统架构 7 | 系统整体采用微服务架构,其中前端部分的开发采用前后端分离的开发模式。这样前端只负责数据的渲染和提供页面路由;后端只负责提供restful风格的数据接口,后端所有服务以rpc的方式进行相互调用。 8 | 9 | ## 相关技术 10 | ### 项目管理 11 | - `git` : 版本控制的最佳方案.... 12 | - `git-flow` : git虽好,但是具体的branch管理方案并没有提供,而git-flow则提供了一套非常完整的[流程管理模型](https://ihower.tw/blog/archives/5140),为自动化打好基石。 13 | - `gitlab` : 开源的git服务端,提供各种webhooks 的回调,为自动化部署,发布,集成做好铺垫。 14 | - `slack`: 用于工作沟通,文档管理,异常通知。对于新加入频道的人,其也能看见频道之前的聊天记录,这个特点对于需求沟通或者事情告之,非常方便。其跟邮件系统,gitlab ,Jenkins 都可以进行很好的集成,集成后,slack由于其全平台覆盖的特性,其能变成一个事件通知软件,任何时候都能获取状态通知,非常方便。 15 | - `有道云笔记` : 团队内部的wiki分享,这个挺好的,如果能私有,那么更好的了,哈哈。 16 | - `Jenkins` :它可以自动远程部署,执行远程脚本命令的工具,自动化的关键。 17 | - `swagger`: 前后端分离开发的重点在于接口api的准确性和规范性上,swagger则提供了自动生成并提供交互性测试接口文档的能力 ,比如:[兼客api](http://api.mkjianzhi.com/docs1/index.html)。 18 | - `gitbook` : 开源的文档编写软件,采用markdown的方式书写网页版文档,比如:[vue-router文档](http://router.vuejs.org/zh-cn/index.html) 19 | - `EditorConfig` : 让文本编辑器能够对换行,空格等style上的作出约束。 20 | - `ESLint`: 让团队编写js的代码风格保持绝对的一致。我们选用[airbnb](https://github.com/airbnb/javascript)的[eslint-config-airbnb-base](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb-base)的代码风格作为团队中使用的规范,不论node后端还是前端,均采用这套规则。 21 | 22 | ### 后台开发 23 | - `工具` :WebStrom, Idea, Sublime. 24 | - `nginx` :用于设置跨域,请求转发,gzip压缩等。对于微服务架构,前后端分离的开发模式,非常有用,视为核心组件。 25 | - `node` :当web服务和中间件使用。 26 | - `java` :使用solr cloud 的搜索,webmagic的爬虫。 27 | - `redis`:缓存,简单系统需要的队列以及geohash,性能非常强劲。 28 | - `mongodb` : 为配合node和集群的需要 而使用,他跟node的配合非常适合敏捷开发的需要。 29 | - `ngrok` : 本地项目,外网访问的神器,需要自己搭建ngrok服务,自己编译client端。用于微信开发,接口对接等。 30 | 31 | 32 | ### 前端开发 33 | - `工具` :WebStrom(vue中对es6的支持不太好), Sublime. 34 | - `ES6`: 完全按照es6的js特性进行编码,使得项目更容易管理。 35 | - `angularjs` : 缺少组件化的开发方式,是硬伤,优势也就只有网上模版资源多了。目前已经被我抛弃掉。 36 | - `vuejs` : 数据监听,依赖收集,组件系统,动画钩子,轻量级...这些特点完全让我抛弃了angularjs,投向vuejs。目前它的唯一问题就是 太新,生态资源还在成长中。不过经历过几个项目的迭代,组件轮子都造的差不多了,非常看好它(目前淘宝的勾股大叔分享的[无线电商动态化方案的思考](https://github.com/amfe/article/issues/13)中系列文章中介绍的淘宝的全新开发框架Weex, 其就大量借鉴了vuejs的语法和特性,其中数据监听和依赖收集的核心代码,直接复用vuejs的,这说明了vuejs的优秀)。 37 | - `reactjs` : reactjs的组件生态中,有一个叫[Antd](http://ant.design)的第三方组件,其出自阿里。这套组件所提供的多样性,全面性,灵活性实在是太适合做后台管理系统的开发,所以毫不犹豫的让团队选择了reactjs来做后台管理系统界面的开发工作。 38 | - `webpack` : es6 , sass的实时预编译; 项目发布的打包,压缩,混编; 开发阶段的热部署,浏览器同步....非常赞,给前端开发带来完全不一样的开发体验。当然部分功能,glup中也可以实现,但是在团队中编写团队需要的扩展插件完全不如webpack方便。 39 | - `glup`: 目前被webpack所取代。 40 | - `fastclick` : 彻底解决移动端点击问题。 41 | - `fetch` : 移动h5端中,取代ajax的api。 42 | - `psd2html` : 自己编写的将设计稿自动转换为html文件的node插件,目前还在持续进化中,后续会配合上vuejs的组件,会非常强悍,让人期待。 43 | - `ESLint` : 使得团队拥有统一的代码规范,可读性,管理性增强。 44 | 45 | 46 | ### App开发 47 | - `swift` :因为个人关系,没有用oc,而是直接使用的swift,但2.0的升级,有点受伤。 48 | - `react native`: 考虑到团队的特点,或许以后采用react native 的方式来开发app,会有更好的开发效率和掌控力,以及让app具备动态化的能力。但是我个人还是觉得在移动端,性能非常重要,无论是angularjs的dirty check,还是react的vitural dom ,都不及vuejs的数据监听 来的更有效率。 49 | - `electron` : JavaScript的一个运行时,其跟vuejs 或者react 的结合能让前端人员轻松开发垮平台的桌面应用。 50 | 51 | 52 | 53 | ### 测试 54 | - `browser-sync` :node的一个工具包,用于浏览器同步,其流弊之处在于你用不同的浏览器访问其启动的服务,你的所有操作,能在不同的浏览器间同步执行,大大提高测试效率。配合一些脚本,可以实现前端测试的自动化工作。 55 | - `postman`:一款chrome 的支持rest http请求的插件,后台人员能够通过该插件,完成自己开发接口的请求测试工作。 56 | - `mocha` :其可以运行在Node.js和浏览器环境,使异步测试变得简单,其能串联运行测试,允许灵活和精确地报告结果,同时映射未捕获的异常用来纠正测试用例 。 57 | - `chai` : 断言库,配合mocha使用。 58 | - `faker.js`: 可以在浏览器和 node.js 中生成大量的虚假数据,非常方便。 59 | - `Karma `: 是一个测试任务管理工具,可以很容易和Jasmine、Mocha等测试框架打通,通过其插件可以快速集成到各种环境中。例如:本地环境、持续集成环境。 60 | - `jmeter` :java的测试工具,可自己编写测试案列进行功能,压力测试,其jar包可以轻松整合进Jenkins,以完成每次git commit后的自动测试工作,非常灵活方便,大赞。 61 | 62 | 63 | ### 设计 64 | - `sketch` :移动端设计的必备软件,基本上团队所有的设计,均用sketch来做。开发人员这边也会装sketch,用于自己进行2X,3X的切图,各个元素的间距测量。 65 | - `磨刀` : [移动端的原型设计工具](https://modao.cc) 66 | - `网上主题资源` : 其实对于pc web来说,所有的设计都有现成的主题,在设计偏弱的团队中,可以从[themeforest](http://themeforest.net)上寻找符合业务需要的主题,购买下来即可,但是有一个问题就是其是jquery的。所以最终就变成了设计师自己在[themeforest](http://themeforest.net)上寻找样式灵感,然后由开发人员进行vuejs的实现。 67 | 68 | -------------------------------------------------------------------------------- /用Docker构建开发环境.md: -------------------------------------------------------------------------------- 1 | # 用Docker轻松搭建开发环境 2 | 开发一个新项目,部署开发环境是第一步,但是会由于各种原因,导致配置开发环境很费时间,针对这种情况,其实可以轻松用docker来统一部署开发环境。详细如下: 3 | 4 | 我们的每个项目都在Bitbucket上有自己的group,创建了一个名为development的分支。分支的同一层级有README.md(理论上)和docker-compose.yml两个文件。 5 | 当新程序员加入项目时,只需在Bitbucket上浏览developmentrepository,根据README.md的步骤就可以快速搭建环境。具体步骤如下所示: 6 | ``` 7 | $ git -v 8 | $ docker -v 9 | $ docker-compose -v 10 | $ git clone git@bitbucket.com:/development.git && cd 11 | $ git submodule init && git submodule update 12 | $ git submodule foreach npm install 13 | $ docker-compose up -d 14 | ``` 15 | 至此,一切就都已经搭建好,并运行在本地机器上了。 16 | ### 实现原理 17 | 本章介绍我们是如何实现上述工作流的。 18 | 前提条件 19 | ``` 20 | $ git -v 21 | $ docker -v 22 | $ docker-compose 23 | ``` 24 | 由于我们的开发堆栈完全基于Docker,所以,程序员需要先安装Docker。这时他们不需要特别熟悉Docker,只需要在开发时使用Docker即可,我们间接地将他们引入到了容器的世界,之后会以此为桥梁向他们解释如何使用Docker实现持续集成、持续交付等等。README.md中并没有详细介绍如何安装Docker,因为安装很简单。 25 | 26 | 当docker-compose还叫Fig的时候我们就已经用它来编排开发堆栈里的容器。之后Docker收购了Fig,重命名为Docker Compose。有人提议将Docker Compose合并到Docker代码里,但是基于很多原因最终并没有这么做,所以Docker Compose仍然需要单独安装。 27 | 28 | 同样地,本文没有详细介绍Docker Compose的安装,因为很简单。 29 | 搭建仓库(Repository) 30 | 如前所述,需要创建一个开发仓库,以及为每个应用创建对应的仓库。这里我们创建了api、dashboard和cpanel。当创建这些仓库的时候,重点关注development仓库的搭建。 31 | $ git clone git@bitbucket.com:#project#/development.git #project# && cd 32 | 现在将应用程序的仓库添加为development仓库的子模块,只需要键入如下命令: 33 | ``` 34 | $ git submodule add git@bitbucket.org:/api.git 35 | $ git submodule add git@bitbucket.org:/dashboard.git 36 | $ git submodule add git@bitbucket.org:/cpanel.git 37 | ``` 38 | 这样,你的development仓库根目录下会创建出.gitmodules文件。程序员也就可以在克隆developmentrepository的时候一次得到所有的应用程序并运行: 39 | `$ git submodule init && git submodule update ` 40 | 更多子模块的信息,请参考Git官方文档。 41 | 42 | ### Docker化一切 43 | 现在我们已经搭建好了development仓库,可以通过cd的方式访问所有不同的应用程序。接下来我们要用之前提到的编排工具:Docker Compose来容器化所有的应用及其配置。 44 | 首先从api应用程序开始。打开docker-compose.yml,为API声明一个容器,并为这个容器选择基础镜像。本示例中的代码基于Node.js,因此选择官方Node.js镜像: 45 | ``` 46 | api: 47 | image: dockerfile/nodejs 48 | ``` 49 | 这时,运行命令docker-compose up -d会创建出一个名为<project>_api_1的容器,这个容器什么也不做(启动后立即退出)。运行命令docker-compose ps可以得到由docker-compose.yml编排的所有容器的信息。接下来配置api容器,使其多一些功能。为了实现这个目的,我们需要:将源代码挂载到容器中,声明用什么命令运行应用,暴露合适的端口以供访问应用 50 | 这样配置文件类似: 51 | ``` 52 | api: 53 | image: dockerfile/nodejs 54 | volumes: 55 | - ./api/:/app/ 56 | working_dir: /app/ 57 | command: npm start 58 | ports: 59 | - "8000:8000" 60 | ``` 61 | 62 | 现在再运行docker-compose up -d,就启动了api应用,可以在http://localhost:8000访问它。这个程序可能会崩溃,可以使用docker-compose logs api检查容器日志。 63 | 64 | 这里,我怀疑api的崩溃是因为它连不上数据库。因此需要添加database容器,并让api容器能够使用它。 65 | ``` 66 | api: 67 | image: dockerfile/nodejs 68 | volumes: 69 | - ./api/:/app/ 70 | working_dir: /app/ 71 | command: npm start 72 | ports: 73 | - "8000:8000" 74 | links: 75 | - database 76 | database: 77 | image: postgresql 78 | ports: 79 | - "5432:5432" 80 | ``` 81 | 通过创建database容器,并将其连接到api容器,我们就可以在api容器里找到database。要想展示API的环境(比如,console.log(process.env)),必须使用如下变量,比如`POSTGRES_1_PORT_5432_TCP_ADDR和POSTGRES_1_PORT_5432_TCP_PORT`。这是我们在API的配置文件里使用的关联到数据库的变量。 82 | 83 | 通过link指令,这个数据库容器被认为是API容器的依赖条件。这意味着Docker Compose在启动API容器之前一定会先启动数据库容器。 84 | 现在我们用同样的方式描述其它应用程序。这里,我们可以通过环境变量 `API_1_PORT_8000_TCP_ADDR和API_1_PORT_8000_TCP_PORT`,将api连接到dashboard和cpanel应用。 85 | ``` 86 | - ./api/:/app/ 87 | working_dir: /app/ 88 | command: npm start 89 | ports: 90 | - "8000:8000" 91 | links: 92 | - database 93 | database: 94 | image: postgresql 95 | dashboard: 96 | image: dockerfile/nodejs 97 | volumes: 98 | - ./dashboard/:/app/ 99 | working_dir: /app/ 100 | command: npm start 101 | ports: 102 | - "8001:8001" 103 | links: 104 | - api 105 | cpanel: 106 | image: dockerfile/nodejs 107 | volumes: 108 | - ./api/:/app/ 109 | working_dir: /app/ 110 | command: npm start 111 | ports: 112 | - "8002:8002" 113 | links: 114 | - api 115 | ``` 116 | 就像之前为数据库修改API配置文件一样,可以为dashboard和cpanel应用使用类似的环境变量,从而避免硬编码。 117 | 现在可以再次运行docker-compose up -d命令和docker-compose ps命令: 118 | ``` 119 | kytwb@continuous:~/path/to/$ docker-compose up -d 120 | Recreating _database_1... 121 | Recreating _api_1... 122 | Creating _dashboard_1... 123 | Creating _cpanel_1... 124 | kytwb@continuous:~/path/to/$ docker-compose ps 125 | Name Command State Ports 126 | ---------------------------------------------------------------------------------- 127 | _api_1 npm start Up 0.0.0.0:8000->8000/tcp 128 | _dashboard_1 npm start Up 0.0.0.0:8001->8001/tcp 129 | _cpanel_1 npm start Up 0.0.0.0:8002->8002/tcp 130 | _database_1 /usr/local/bin/run Up 0.0.0.0:5432->5432/tcp 131 | ``` 132 | 应用应该就已经启动并运行了。 133 | 从http://localhsot:8000可以访问api。 134 | 从http://localhsot:8001可以访问dashboard。 135 | 从http://localhsot:8002可以访问cpanel。 136 | 137 | ### 更进一步 138 | 本地路由 139 | 140 | 在使用`docker-compose up -d`运行所有容器之后,可以通过http://localhost:访问我们的应用。基于当前配置,我们可以很容易地使用jwilder/nginx-proxy加上本地路由功能,这样就可以使用和生产环境类似的URL访问本地应用了。比如,通过http://api.domain.local访问http://api.domain.com的本地版本。 141 | jwilder/nginx-proxy镜像将一切变得很简单。只需要在docker-compose.yml里加上描述去创建一个名为nginx的新容器。根据jwilder/nginx-proxy的README文件(挂载Docker守护进程socket,暴露80端口)配置该容器就可以了。之后,在现有容器里再添加额外的环境变量VIRTUAL_HOST和VIRTUAL_PORT,如下: 142 | ``` 143 | api: 144 | image: dockerfile/nodejs 145 | volumes: 146 | - ./api/:/app/ 147 | working_dir: /app/ 148 | command: npm start 149 | environment: 150 | - VIRTUAL_HOST=api.domain.local 151 | - VIRTUAL_PORT=8000 152 | ports: 153 | - "8000:8000" 154 | links: 155 | - database 156 | database: 157 | image: postgresql 158 | dashboard: 159 | image: dockerfile/nodejs 160 | volumes: 161 | - ./dashboard/:/app/ 162 | working_dir: /app/ 163 | command: npm start 164 | environment: 165 | - VIRTUAL_HOST=dashboard.domain.local 166 | - VIRTUAL_PORT=8001 167 | ports: 168 | - "8001:8001" 169 | links: 170 | - api 171 | cpanel: 172 | image: dockerfile/nodejs 173 | volumes: 174 | - ./api/:/app/ 175 | working_dir: /app/ 176 | command: npm start 177 | environment: 178 | - VIRTUAL_HOST=cpanel.domain.local 179 | - VIRTUAL_PORT=8002 180 | ports: 181 | - "8002:8002" 182 | links: 183 | - api 184 | nginx: 185 | image: jwilder/nginx-proxy 186 | volumes: 187 | - /var/run/docker.sock:/tmp/docker.sock 188 | ports: 189 | - "80:80" 190 | ``` 191 | nginx容器会检查所有运行在Docker守护进程之上(通过挂载的docker.sock文件)的容器,为每个容器创建合适的nginx配置文件,并设置VIRTUAL_HOST环境变量。 192 | 193 | 要想完成本地路由的搭建,还需要在etc/hosts里添加所有的VIRTUAL_HOST。我是手动用node.js的hostile包来完成这个工作的,不过我猜应该可以自动化实现,就像jwilder/nginx-proxy可以根据nginx配置文件动态变化一样。这里需要再研究一下。 194 | 195 | 现在可以再次运行`docker-compose up -d`,然后使用和生产环境一样的url访问应用程序,只需用.localTLD代替.comTLD。 196 | 197 | -------------------------------------------------------------------------------- /高姿态的使用CDN.md: -------------------------------------------------------------------------------- 1 | # 高姿态的使用CDN 2 | 3 | 前后端分离的这种开发模式,在移动时代迅速普及开。 4 | CDN在其中扮演的角色发生了一个巨大的变化,以前只是作为一个优化策略存在,如今其可是作为高性能的静态服务器而存在。 5 | ### 什么意思?CDN还可以做服务? 6 | 是的,它不但能做服务,而且其做的服务,具有非常大的性能优势。 7 | 我们来简单分析一下原因,大家知道CDN的主要作用是存放静态资源,从而提高网站访问速度。([为什么能提速网站?](http://mp.weixin.qq.com/s?__biz=MzI4MjA4ODU0Ng==&mid=402603447&idx=1&sn=a66afa8393ffe5b8272ec0733f3ad1fa&scene=2&srcid=03085jwKbPsa6GysR9gWgv6E&from=timeline&isappinstalled=0#wechat_redirect))在现代前端里面,前后端是分离开发的,后台只负责提供rest api,前端负责页面渲染。既然是这样,那么整个工程的前端部分都是静态资源,它是可以放到CDN上的(后端部分我们不在这篇文章里面讨论)。 8 | ### CDN做服务是否靠谱,优势是啥? 9 | 首先: 网站对抗DDOS的攻击,大家知道最有效的方法么?嗯,页面静态化,上CDN。 10 | 再次:带宽有限的情况下,最大化提高网站的承受能力。为什么? 11 | 看如下对比:(假设网站带宽为100M同时只考虑带宽的影响,其他服务器的性能优化默认为最优) 12 | ![a5data](http://iamhades.oss-cn-shanghai.aliyuncs.com/a5data.png) 13 | 哇,这个差距太明显了。这个其实说明了为什么像tomcat这种性能很糟糕的容器,wordpress这种php写的大众博客,他们的性能那么低,为什么还能广泛流传使用。因为对于大多数场景来说,其性能瓶颈不在服务本身,而是在网络带宽啊,亲们。 14 | 15 | ### 我们来看看实战中是如何利用CDN来构建服务 16 | 我们项目中用的云服务是阿里云的OSS(七牛也是可以的,思路一样),所以我们针对OSS来操作。 17 | 首先开通OSS服务后,在Bucket管理目录中,点击右上的新建Bucket,在弹出来的对话框中取好BucketName(我们取名为thisisatest),选择好所属地域,读写权限选择为‘公共读’,如下图: 18 | ![enter image description here](http://iamhades.oss-cn-shanghai.aliyuncs.com/a1.png) 19 | 创建好名称后,进入Bucket概览中,里面的OSS域名栏,如下: 20 | ![enter image description here](http://iamhades.oss-cn-shanghai.aliyuncs.com/a2.png) 21 | 里面的自定义绑定域名,点击进去,添加上自己的域名,然后点击下一步,添加CNAME记录勾选'自动添加‘ 就可以了。 22 | ### 域名的问题需要注意一下: 23 | 通过OSS默认的域名来访问OSS的文件全部默认为下载,而HTTP头相关参数如Content-Disposition=inline的设置都是无效的。要直接显示而不是下载,需要绑定用户自己的域名即可,通过测试,总结如下: 24 | - 绑定了自己的域名: 25 | - 图片显示:通过绑定域名访问图片文件,确实是直接显示; 26 | - 图片下载:如果有通过绑定域名而直接下载需求的话,可以设置HTTP头的Content-Disposition=attachement;filename=xxxx,即可实现文件另存为”xxxx”;Content-Disposition=attachement则按照原文件名另存为的下载模式,可以满足开发者的不同需求; 27 | - 若使用OSS的默认域名: 28 | 则Content-Disposition的设置均会被系统默认的下载策略覆盖,都是图片下载; 29 | 30 | ### Bucket的属性设置 31 | 进入Bucket的属性栏,关注‘Website设置’,‘Cors设置’ , 设置如下图: 32 | ![enter image description here](http://iamhades.oss-cn-shanghai.aliyuncs.com/a3.png) 33 | 里面的默认首页和404页,都是根据你自己需要进行设置。 34 | 35 | ![enter image description here](http://iamhades.oss-cn-shanghai.aliyuncs.com/a4.png) 36 | 添加进自己的主站域名,注意http://localhost:8080的设置,主要是为了本地开发需要而设定的。 37 | 38 | 进行到这里,阿里云上的配置部分就ok了。你可以测试一下,进入Object管理中,上传一个index.html的文件,在浏览器中输入域名,如果可以正常访问那么就是配置正确的。 39 | ### 前端代码的自动化配置 40 | 接下来我们开始前端代码方面的配置,以便让整个过程真正的高逼格起来,符合现在强调的自动化流程嘛。 41 | **在此推荐webpack的一个自己开发的插件[aliyunoss-webpack-plugin](https://www.npmjs.com/package/aliyunoss-webpack-plugin),它的作用就是上传指定的文件到oss上。** 42 | 将其引入到工程中,我们的前端项目是用vuejs开发的SPA项目,基于webpack构建的,入口文件是index.html。 43 | 在webpack的生产配置文件plugins中配置: 44 | 45 | new AliyunossWebpackPlugin({ 46 | buildPath:__dirname + '/build', 47 | region: 'oss-cn-shanghai', 48 | accessKeyId: 'your key', 49 | accessKeySecret: 'your secret', 50 | bucket: 'thisisatest', 51 | deleteAll: true, 52 | getObjectHeaders: function(filename) { 53 | return { 54 | 'Cache-Control': 'max-age=2592000' 55 | } 56 | } 57 | 58 | 59 | **buildPath**是构建的路径,你需要将工程的入口文件index.html也生成在里面,一并上传。 60 | **地域参数region**是要取决于你一开始新建Bucket的位置,目前支持的列表[点击这里查看](https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html?spm=5176.docoss/user_guide/oss_concept/concepts.2.3.tJhjs3) 61 | **参数getObjectHeaders** 设置上传文件的头信息,我们将静态资源的有效期设置为一个月,阿里的oss默认是不会给文件添加任何信息的,所以我们需要根据自己的需要来进行设置,目前支持的属性[点击查看](http://doc.oss.aliyuncs.com/#_Toc336676772) 62 | 63 | 这样在每次生产构建的时候,其就会自动删除oss之前的文件,上传新的文件,这样整个前端工程就完成部署更新。 64 | 65 | 66 | 是不是非常简单?云服务时代,不就是让所有的一切都变得简单美好么。 67 | 文章写到这里,不免有槽点,我只想起一个抛砖引玉的作用。 68 | 69 | --------------------------------------------------------------------------------