├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json └── src ├── config ├── common.js ├── dev.js ├── index.js ├── prod.js └── test.js ├── engine ├── amqp-client.js ├── cache-client.js ├── db-client.js ├── health-check.js ├── index.js └── web.js ├── helper ├── consul.js ├── regex.js └── utils.js ├── index.js ├── plugins ├── index.js └── microRouting.js ├── servers ├── httpRouting.js └── index.js └── tools ├── deploy-tool.js ├── index.js ├── logger.js └── server-register.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = false 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "space-before-function-paren": 0 4 | }, 5 | "parserOptions": { 6 | "ecmaVersion": 8 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .DStore 3 | .vscode 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 70, 3 | "tabWidth": 2, 4 | "semi": false, 5 | "trailingComma": "none", 6 | "singleQuote": true, 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "requirePragma": false, 10 | "insertPragma": false 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cecil0o0/vastify/975445130de8e9cac78cfd9b8e9c72ef991fa562/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vastify 2 | 3 | > 本仓库不再维护,对 nodejs 微服务感兴趣的同学可以浏览 [nestjs 文档](https://docs.nestjs.com/) 或阅读其[源代码](https://github.com/nestjs/nest)。感谢读者们! 4 | 5 | a lightweight nodejs microservices framework 6 | 7 | ## FEATURES 8 | 9 | - [x] 模式匹配做服务间调用:略不同于 SpringCloud 服务发现(http协议、IP + PORT模式),它使用更加灵活的模式匹配(Patrun模块)原则去进行微服务间的调用,实际上是一种 RPC 实现。 10 | - [x] 接入 koa2 对C端提供 RESTFUl API 11 | - [x] 插件:更灵活编写小而微的可复用模块 12 | - [x] seneca 内置日志输出与第三方日志库比较 winston(选用) 13 | - [x] RabbitMQ 消息队列 14 | - [x] PM2:node服务部署(服务集群)、管理与监控 15 | - [x] PM2:自动化部署(命令行一键部署) 16 | - [ ] PM2 集成 docker 17 | - [ ] K8S 做服务治理 18 | - [x] 请求追踪(重建用户请求流程) 19 | - [x] 梳理 Consul 服务注册与发现基本逻辑 20 | - [x] 框架集成 node-consul 21 | - [x] mongodb 持久化存储(集成第三方) 22 | - [x] 结合 seneca 与 consul 的路由服务中间件(可支持多个相同名字服务集群路由,通过 $$version 区别) 23 | - [ ] 支持流处理(文件上传/下载等) 24 | - [ ] jenkins 自动化部署 25 | - [x] nginx 做应用层负载均衡(集成第三方) 26 | - [ ] 持续集成方案 27 | - [x] redis 缓存(集成第三方) 28 | - [ ] Apollo 提供 GraphQL 接口 29 | 30 | ~~[Introduction](https://blog.qingf.me/?p=734)~~ 31 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vastify", 3 | "version": "1.3.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "http://registry.npm.taobao.org/accepts/download/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "requires": { 12 | "mime-types": "~2.1.18", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "any-promise": { 17 | "version": "1.3.0", 18 | "resolved": "http://registry.npm.taobao.org/any-promise/download/any-promise-1.3.0.tgz", 19 | "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" 20 | }, 21 | "archy": { 22 | "version": "1.0.0", 23 | "resolved": "http://registry.npm.taobao.org/archy/download/archy-1.0.0.tgz", 24 | "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" 25 | }, 26 | "async": { 27 | "version": "2.6.0", 28 | "resolved": "http://registry.npm.taobao.org/async/download/async-2.6.0.tgz", 29 | "integrity": "sha1-YaKau2/MAm/qd+VtHG7FOnlZUfQ=", 30 | "requires": { 31 | "lodash": "^4.14.0" 32 | } 33 | }, 34 | "backoff": { 35 | "version": "2.5.0", 36 | "resolved": "http://registry.npm.taobao.org/backoff/download/backoff-2.5.0.tgz", 37 | "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", 38 | "requires": { 39 | "precond": "0.2" 40 | } 41 | }, 42 | "bluebird": { 43 | "version": "3.5.0", 44 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", 45 | "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" 46 | }, 47 | "boom": { 48 | "version": "5.2.0", 49 | "resolved": "http://registry.npm.taobao.org/boom/download/boom-5.2.0.tgz", 50 | "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=", 51 | "requires": { 52 | "hoek": "4.x.x" 53 | } 54 | }, 55 | "bson": { 56 | "version": "1.0.6", 57 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.6.tgz", 58 | "integrity": "sha512-D8zmlb46xfuK2gGvKmUjIklQEouN2nQ0LEHHeZ/NoHM2LDiMk2EYzZ5Ntw/Urk+bgMDosOZxaRzXxvhI5TcAVQ==" 59 | }, 60 | "bytes": { 61 | "version": "3.0.0", 62 | "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz", 63 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 64 | }, 65 | "co": { 66 | "version": "4.6.0", 67 | "resolved": "http://registry.npm.taobao.org/co/download/co-4.6.0.tgz", 68 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 69 | }, 70 | "co-body": { 71 | "version": "5.2.0", 72 | "resolved": "http://registry.npm.taobao.org/co-body/download/co-body-5.2.0.tgz", 73 | "integrity": "sha1-WgpljEYCkTHg46MG9nZHMC9xwSQ=", 74 | "requires": { 75 | "inflation": "^2.0.0", 76 | "qs": "^6.4.0", 77 | "raw-body": "^2.2.0", 78 | "type-is": "^1.6.14" 79 | } 80 | }, 81 | "color": { 82 | "version": "0.8.0", 83 | "resolved": "http://registry.npm.taobao.org/color/download/color-0.8.0.tgz", 84 | "integrity": "sha1-iQwHw/1OZJU3Y4kRz2keVFi2/KU=", 85 | "requires": { 86 | "color-convert": "^0.5.0", 87 | "color-string": "^0.3.0" 88 | } 89 | }, 90 | "color-convert": { 91 | "version": "0.5.3", 92 | "resolved": "http://registry.npm.taobao.org/color-convert/download/color-convert-0.5.3.tgz", 93 | "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" 94 | }, 95 | "color-name": { 96 | "version": "1.1.3", 97 | "resolved": "http://registry.npm.taobao.org/color-name/download/color-name-1.1.3.tgz", 98 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 99 | }, 100 | "color-string": { 101 | "version": "0.3.0", 102 | "resolved": "http://registry.npm.taobao.org/color-string/download/color-string-0.3.0.tgz", 103 | "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", 104 | "requires": { 105 | "color-name": "^1.0.0" 106 | } 107 | }, 108 | "colornames": { 109 | "version": "0.0.2", 110 | "resolved": "http://registry.npm.taobao.org/colornames/download/colornames-0.0.2.tgz", 111 | "integrity": "sha1-2BH9bIT1kClJmorEQ2ICk1uSvjE=" 112 | }, 113 | "colors": { 114 | "version": "1.2.5", 115 | "resolved": "http://registry.npm.taobao.org/colors/download/colors-1.2.5.tgz", 116 | "integrity": "sha1-icetmjdLwDDfgBMkH2gTbtiDWvw=" 117 | }, 118 | "colorspace": { 119 | "version": "1.0.1", 120 | "resolved": "http://registry.npm.taobao.org/colorspace/download/colorspace-1.0.1.tgz", 121 | "integrity": "sha1-yZx5btMRKLmHalLh7l7gOkpxl0k=", 122 | "requires": { 123 | "color": "0.8.x", 124 | "text-hex": "0.0.x" 125 | } 126 | }, 127 | "consul": { 128 | "version": "0.31.0", 129 | "resolved": "https://registry.npmjs.org/consul/-/consul-0.31.0.tgz", 130 | "integrity": "sha512-Dxfbv3LrBTuMXWl2SqKSWeLF3p5zUXazUCF+6McaRwwHzMyfTt6I00pu22F5keUfdC8d3KDIY9IZ4KanDf9q9w==", 131 | "requires": { 132 | "papi": "^0.27.0" 133 | } 134 | }, 135 | "content-disposition": { 136 | "version": "0.5.2", 137 | "resolved": "http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz", 138 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 139 | }, 140 | "content-type": { 141 | "version": "1.0.4", 142 | "resolved": "http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz", 143 | "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" 144 | }, 145 | "cookies": { 146 | "version": "0.7.1", 147 | "resolved": "http://registry.npm.taobao.org/cookies/download/cookies-0.7.1.tgz", 148 | "integrity": "sha1-fIphX1SBxhq58WyDNzG8uPZjuZs=", 149 | "requires": { 150 | "depd": "~1.1.1", 151 | "keygrip": "~1.0.2" 152 | } 153 | }, 154 | "core-util-is": { 155 | "version": "1.0.2", 156 | "resolved": "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz", 157 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 158 | }, 159 | "debug": { 160 | "version": "3.1.0", 161 | "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz", 162 | "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", 163 | "requires": { 164 | "ms": "2.0.0" 165 | } 166 | }, 167 | "deep-equal": { 168 | "version": "1.0.1", 169 | "resolved": "http://registry.npm.taobao.org/deep-equal/download/deep-equal-1.0.1.tgz", 170 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" 171 | }, 172 | "delegates": { 173 | "version": "1.0.0", 174 | "resolved": "http://registry.npm.taobao.org/delegates/download/delegates-1.0.0.tgz", 175 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 176 | }, 177 | "depd": { 178 | "version": "1.1.2", 179 | "resolved": "http://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz", 180 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 181 | }, 182 | "destroy": { 183 | "version": "1.0.4", 184 | "resolved": "http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz", 185 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 186 | }, 187 | "diagnostics": { 188 | "version": "1.1.0", 189 | "resolved": "http://registry.npm.taobao.org/diagnostics/download/diagnostics-1.1.0.tgz", 190 | "integrity": "sha1-4QkJALSVI+hSe+IPCBJ1IF8q42o=", 191 | "requires": { 192 | "colorspace": "1.0.x", 193 | "enabled": "1.0.x", 194 | "kuler": "0.0.x" 195 | } 196 | }, 197 | "ee-first": { 198 | "version": "1.1.1", 199 | "resolved": "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz", 200 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 201 | }, 202 | "enabled": { 203 | "version": "1.0.2", 204 | "resolved": "http://registry.npm.taobao.org/enabled/download/enabled-1.0.2.tgz", 205 | "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", 206 | "requires": { 207 | "env-variable": "0.0.x" 208 | } 209 | }, 210 | "env-variable": { 211 | "version": "0.0.4", 212 | "resolved": "http://registry.npm.taobao.org/env-variable/download/env-variable-0.0.4.tgz", 213 | "integrity": "sha1-DWKAz1B9hCQr7+NaUSta5L53xU4=" 214 | }, 215 | "eraro": { 216 | "version": "1.0.0", 217 | "resolved": "http://registry.npm.taobao.org/eraro/download/eraro-1.0.0.tgz", 218 | "integrity": "sha1-iiFn8LxvlkIAovzeejhtLV27+RI=", 219 | "requires": { 220 | "lodash": "4.17" 221 | } 222 | }, 223 | "error-inject": { 224 | "version": "1.0.0", 225 | "resolved": "http://registry.npm.taobao.org/error-inject/download/error-inject-1.0.0.tgz", 226 | "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=" 227 | }, 228 | "escape-html": { 229 | "version": "1.0.3", 230 | "resolved": "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz", 231 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 232 | }, 233 | "fecha": { 234 | "version": "2.3.3", 235 | "resolved": "http://registry.npm.taobao.org/fecha/download/fecha-2.3.3.tgz", 236 | "integrity": "sha1-lI50FX3xoy/RsSw6PDzctuydls0=" 237 | }, 238 | "fresh": { 239 | "version": "0.5.2", 240 | "resolved": "http://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz", 241 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 242 | }, 243 | "gate-executor": { 244 | "version": "1.1.1", 245 | "resolved": "http://registry.npm.taobao.org/gate-executor/download/gate-executor-1.1.1.tgz", 246 | "integrity": "sha1-Zn9ncoSg2+hrbEigtK/oxISBLK8=" 247 | }, 248 | "gex": { 249 | "version": "0.3.0", 250 | "resolved": "http://registry.npm.taobao.org/gex/download/gex-0.3.0.tgz", 251 | "integrity": "sha1-IXMkzNblxh0khckT63YUshNYuX8=", 252 | "requires": { 253 | "lodash": "4.17" 254 | } 255 | }, 256 | "hoek": { 257 | "version": "4.2.1", 258 | "resolved": "http://registry.npm.taobao.org/hoek/download/hoek-4.2.1.tgz", 259 | "integrity": "sha1-ljRQKqEsRF3Vp8VzS1cruHOKrLs=" 260 | }, 261 | "http-assert": { 262 | "version": "1.3.0", 263 | "resolved": "http://registry.npm.taobao.org/http-assert/download/http-assert-1.3.0.tgz", 264 | "integrity": "sha1-oxpc+IyHPsu1eWkH1NbxMujAHko=", 265 | "requires": { 266 | "deep-equal": "~1.0.1", 267 | "http-errors": "~1.6.1" 268 | } 269 | }, 270 | "http-errors": { 271 | "version": "1.6.3", 272 | "resolved": "http://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz", 273 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 274 | "requires": { 275 | "depd": "~1.1.2", 276 | "inherits": "2.0.3", 277 | "setprototypeof": "1.1.0", 278 | "statuses": ">= 1.4.0 < 2" 279 | } 280 | }, 281 | "iconv-lite": { 282 | "version": "0.4.23", 283 | "resolved": "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.23.tgz", 284 | "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", 285 | "requires": { 286 | "safer-buffer": ">= 2.1.2 < 3" 287 | } 288 | }, 289 | "inflation": { 290 | "version": "2.0.0", 291 | "resolved": "http://registry.npm.taobao.org/inflation/download/inflation-2.0.0.tgz", 292 | "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=" 293 | }, 294 | "inherits": { 295 | "version": "2.0.3", 296 | "resolved": "http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", 297 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 298 | }, 299 | "is-generator-function": { 300 | "version": "1.0.7", 301 | "resolved": "http://registry.npm.taobao.org/is-generator-function/download/is-generator-function-1.0.7.tgz", 302 | "integrity": "sha1-0hMuUpuwAAp/gHlNS99c1eWBNSI=" 303 | }, 304 | "is-stream": { 305 | "version": "1.1.0", 306 | "resolved": "http://registry.npm.taobao.org/is-stream/download/is-stream-1.1.0.tgz", 307 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 308 | }, 309 | "isarray": { 310 | "version": "0.0.1", 311 | "resolved": "http://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz", 312 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 313 | }, 314 | "json-stringify-safe": { 315 | "version": "5.0.1", 316 | "resolved": "http://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz", 317 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 318 | }, 319 | "jsonic": { 320 | "version": "0.3.0", 321 | "resolved": "http://registry.npm.taobao.org/jsonic/download/jsonic-0.3.0.tgz", 322 | "integrity": "sha1-tUXalfVDkuWLPdoF9fLjd6bJ0b8=" 323 | }, 324 | "kareem": { 325 | "version": "2.1.0", 326 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.1.0.tgz", 327 | "integrity": "sha512-ycoMY1tVkcH1/NaxGn2erZaUC3CodmX7Fl6DUVXjN73+uecWYTaaldRkxNY3HeSKQnQTWnoxRKnZfVHcB8tIWg==" 328 | }, 329 | "keygrip": { 330 | "version": "1.0.2", 331 | "resolved": "http://registry.npm.taobao.org/keygrip/download/keygrip-1.0.2.tgz", 332 | "integrity": "sha1-rTKXxVcGneqLz+ek+kkbdcXd65E=" 333 | }, 334 | "koa": { 335 | "version": "2.5.1", 336 | "resolved": "http://registry.npm.taobao.org/koa/download/koa-2.5.1.tgz", 337 | "integrity": "sha1-efi5X41y0E/ppYqNpevW00EQP5w=", 338 | "requires": { 339 | "accepts": "^1.2.2", 340 | "content-disposition": "~0.5.0", 341 | "content-type": "^1.0.0", 342 | "cookies": "~0.7.0", 343 | "debug": "*", 344 | "delegates": "^1.0.0", 345 | "depd": "^1.1.0", 346 | "destroy": "^1.0.3", 347 | "error-inject": "~1.0.0", 348 | "escape-html": "~1.0.1", 349 | "fresh": "^0.5.2", 350 | "http-assert": "^1.1.0", 351 | "http-errors": "^1.2.8", 352 | "is-generator-function": "^1.0.3", 353 | "koa-compose": "^4.0.0", 354 | "koa-convert": "^1.2.0", 355 | "koa-is-json": "^1.0.0", 356 | "mime-types": "^2.0.7", 357 | "on-finished": "^2.1.0", 358 | "only": "0.0.2", 359 | "parseurl": "^1.3.0", 360 | "statuses": "^1.2.0", 361 | "type-is": "^1.5.5", 362 | "vary": "^1.0.0" 363 | } 364 | }, 365 | "koa-compose": { 366 | "version": "4.0.0", 367 | "resolved": "http://registry.npm.taobao.org/koa-compose/download/koa-compose-4.0.0.tgz", 368 | "integrity": "sha1-KAClE9nDYe8NY4UrA45Pby1adzw=" 369 | }, 370 | "koa-convert": { 371 | "version": "1.2.0", 372 | "resolved": "http://registry.npm.taobao.org/koa-convert/download/koa-convert-1.2.0.tgz", 373 | "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", 374 | "requires": { 375 | "co": "^4.6.0", 376 | "koa-compose": "^3.0.0" 377 | }, 378 | "dependencies": { 379 | "koa-compose": { 380 | "version": "3.2.1", 381 | "resolved": "http://registry.npm.taobao.org/koa-compose/download/koa-compose-3.2.1.tgz", 382 | "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", 383 | "requires": { 384 | "any-promise": "^1.1.0" 385 | } 386 | } 387 | } 388 | }, 389 | "koa-is-json": { 390 | "version": "1.0.0", 391 | "resolved": "http://registry.npm.taobao.org/koa-is-json/download/koa-is-json-1.0.0.tgz", 392 | "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=" 393 | }, 394 | "koa-router": { 395 | "version": "7.4.0", 396 | "resolved": "http://registry.npm.taobao.org/koa-router/download/koa-router-7.4.0.tgz", 397 | "integrity": "sha1-ruH3rcAtXLMdfWdGXJ6syCXoxeA=", 398 | "requires": { 399 | "debug": "^3.1.0", 400 | "http-errors": "^1.3.1", 401 | "koa-compose": "^3.0.0", 402 | "methods": "^1.0.1", 403 | "path-to-regexp": "^1.1.1", 404 | "urijs": "^1.19.0" 405 | }, 406 | "dependencies": { 407 | "koa-compose": { 408 | "version": "3.2.1", 409 | "resolved": "http://registry.npm.taobao.org/koa-compose/download/koa-compose-3.2.1.tgz", 410 | "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", 411 | "requires": { 412 | "any-promise": "^1.1.0" 413 | } 414 | } 415 | } 416 | }, 417 | "kuler": { 418 | "version": "0.0.0", 419 | "resolved": "http://registry.npm.taobao.org/kuler/download/kuler-0.0.0.tgz", 420 | "integrity": "sha1-tmu0a5NOVQ9Z2BiEjgq7pPf1VTw=", 421 | "requires": { 422 | "colornames": "0.0.2" 423 | } 424 | }, 425 | "lodash": { 426 | "version": "4.17.10", 427 | "resolved": "http://registry.npm.taobao.org/lodash/download/lodash-4.17.10.tgz", 428 | "integrity": "sha1-G3eTz3JZ6jj7NmHU04syYK+K5Oc=" 429 | }, 430 | "lodash.get": { 431 | "version": "4.4.2", 432 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 433 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" 434 | }, 435 | "logform": { 436 | "version": "1.6.0", 437 | "resolved": "http://registry.npm.taobao.org/logform/download/logform-1.6.0.tgz", 438 | "integrity": "sha1-EQTJOvECaYZPLZjnZhq76jaQ2D8=", 439 | "requires": { 440 | "colors": "^1.2.1", 441 | "fecha": "^2.3.3", 442 | "ms": "^2.1.1", 443 | "triple-beam": "^1.2.0" 444 | }, 445 | "dependencies": { 446 | "ms": { 447 | "version": "2.1.1", 448 | "resolved": "http://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz", 449 | "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" 450 | } 451 | } 452 | }, 453 | "lru-cache": { 454 | "version": "4.1.3", 455 | "resolved": "http://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.3.tgz", 456 | "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=", 457 | "requires": { 458 | "pseudomap": "^1.0.2", 459 | "yallist": "^2.1.2" 460 | } 461 | }, 462 | "media-typer": { 463 | "version": "0.3.0", 464 | "resolved": "http://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz", 465 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 466 | }, 467 | "methods": { 468 | "version": "1.1.2", 469 | "resolved": "http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz", 470 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 471 | }, 472 | "mime-db": { 473 | "version": "1.33.0", 474 | "resolved": "http://registry.npm.taobao.org/mime-db/download/mime-db-1.33.0.tgz", 475 | "integrity": "sha1-o0kgUKXLm2NFBUHjnZeI0icng9s=" 476 | }, 477 | "mime-types": { 478 | "version": "2.1.18", 479 | "resolved": "http://registry.npm.taobao.org/mime-types/download/mime-types-2.1.18.tgz", 480 | "integrity": "sha1-bzI/YKg9ERRvgx/xH9ZuL+VQO7g=", 481 | "requires": { 482 | "mime-db": "~1.33.0" 483 | } 484 | }, 485 | "minimist": { 486 | "version": "1.2.0", 487 | "resolved": "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz", 488 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 489 | }, 490 | "mongodb": { 491 | "version": "3.0.8", 492 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.8.tgz", 493 | "integrity": "sha512-mj7yIUyAr9xnO2ev8pcVJ9uX7gSum5LLs1qIFoWLxA5Il50+jcojKtaO1/TbexsScZ9Poz00Pc3b86GiSqJ7WA==", 494 | "requires": { 495 | "mongodb-core": "3.0.8" 496 | } 497 | }, 498 | "mongodb-core": { 499 | "version": "3.0.8", 500 | "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.8.tgz", 501 | "integrity": "sha512-dFxfhH9N7ohuQnINyIl6dqEF8sYOE0WKuymrFf3L3cipJNrx+S8rAbNOTwa00/fuJCjBMJNFsaA+R2N16//UIw==", 502 | "requires": { 503 | "bson": "~1.0.4", 504 | "require_optional": "^1.0.1" 505 | } 506 | }, 507 | "mongoose": { 508 | "version": "5.1.2", 509 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.1.2.tgz", 510 | "integrity": "sha512-k9hssPMgBnUYG5e9NoUbx/2ERDyelDY0Vf6BwjtmoETUhVT7pQUe1o+oelLLuHF3ZVY2qgienK8pnrI5pdvlxA==", 511 | "requires": { 512 | "async": "2.1.4", 513 | "bson": "~1.0.5", 514 | "kareem": "2.1.0", 515 | "lodash.get": "4.4.2", 516 | "mongodb": "3.0.8", 517 | "mongoose-legacy-pluralize": "1.0.2", 518 | "mpath": "0.4.1", 519 | "mquery": "3.0.0", 520 | "ms": "2.0.0", 521 | "regexp-clone": "0.0.1", 522 | "sliced": "1.0.1" 523 | }, 524 | "dependencies": { 525 | "async": { 526 | "version": "2.1.4", 527 | "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz", 528 | "integrity": "sha1-LSFgx3iAMuTdbL4lAvH5osj2zeQ=", 529 | "requires": { 530 | "lodash": "^4.14.0" 531 | } 532 | } 533 | } 534 | }, 535 | "mongoose-legacy-pluralize": { 536 | "version": "1.0.2", 537 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 538 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 539 | }, 540 | "mpath": { 541 | "version": "0.4.1", 542 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.4.1.tgz", 543 | "integrity": "sha512-NNY/MpBkALb9jJmjpBlIi6GRoLveLUM0pJzgbp9vY9F7IQEb/HREC/nxrixechcQwd1NevOhJnWWV8QQQRE+OA==" 544 | }, 545 | "mquery": { 546 | "version": "3.0.0", 547 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.0.0.tgz", 548 | "integrity": "sha512-WL1Lk8v4l8VFSSwN3yCzY9TXw+fKVYKn6f+w86TRzOLSE8k1yTgGaLBPUByJQi8VcLbOdnUneFV/y3Kv874pnQ==", 549 | "requires": { 550 | "bluebird": "3.5.0", 551 | "debug": "2.6.9", 552 | "regexp-clone": "0.0.1", 553 | "sliced": "0.0.5" 554 | }, 555 | "dependencies": { 556 | "debug": { 557 | "version": "2.6.9", 558 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 559 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 560 | "requires": { 561 | "ms": "2.0.0" 562 | } 563 | }, 564 | "sliced": { 565 | "version": "0.0.5", 566 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz", 567 | "integrity": "sha1-XtwETKTrb3gW1Qui/GPiXY/kcH8=" 568 | } 569 | } 570 | }, 571 | "ms": { 572 | "version": "2.0.0", 573 | "resolved": "http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", 574 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 575 | }, 576 | "ndjson": { 577 | "version": "1.5.0", 578 | "resolved": "http://registry.npm.taobao.org/ndjson/download/ndjson-1.5.0.tgz", 579 | "integrity": "sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg=", 580 | "requires": { 581 | "json-stringify-safe": "^5.0.1", 582 | "minimist": "^1.2.0", 583 | "split2": "^2.1.0", 584 | "through2": "^2.0.3" 585 | } 586 | }, 587 | "negotiator": { 588 | "version": "0.6.1", 589 | "resolved": "http://registry.npm.taobao.org/negotiator/download/negotiator-0.6.1.tgz", 590 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 591 | }, 592 | "nid": { 593 | "version": "0.3.2", 594 | "resolved": "http://registry.npm.taobao.org/nid/download/nid-0.3.2.tgz", 595 | "integrity": "sha1-l3qTGO1cKjjt1mJj8+r9gUPyJRo=" 596 | }, 597 | "norma": { 598 | "version": "0.4.1", 599 | "resolved": "http://registry.npm.taobao.org/norma/download/norma-0.4.1.tgz", 600 | "integrity": "sha1-C+LwW7pMA0xs5brJhE4SQWmdZYU=", 601 | "requires": { 602 | "eraro": "1.0", 603 | "lodash": "4.17" 604 | } 605 | }, 606 | "on-finished": { 607 | "version": "2.3.0", 608 | "resolved": "http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz", 609 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 610 | "requires": { 611 | "ee-first": "1.1.1" 612 | } 613 | }, 614 | "one-time": { 615 | "version": "0.0.4", 616 | "resolved": "http://registry.npm.taobao.org/one-time/download/one-time-0.0.4.tgz", 617 | "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" 618 | }, 619 | "only": { 620 | "version": "0.0.2", 621 | "resolved": "http://registry.npm.taobao.org/only/download/only-0.0.2.tgz", 622 | "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" 623 | }, 624 | "ordu": { 625 | "version": "0.1.1", 626 | "resolved": "http://registry.npm.taobao.org/ordu/download/ordu-0.1.1.tgz", 627 | "integrity": "sha1-nIEJSTaTyvCCmfyoTFlq64YLrqo=" 628 | }, 629 | "papi": { 630 | "version": "0.27.0", 631 | "resolved": "https://registry.npmjs.org/papi/-/papi-0.27.0.tgz", 632 | "integrity": "sha1-c5Kjjr76f7Z+mLNjvKPRNoJ8ryg=" 633 | }, 634 | "parseurl": { 635 | "version": "1.3.2", 636 | "resolved": "http://registry.npm.taobao.org/parseurl/download/parseurl-1.3.2.tgz", 637 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 638 | }, 639 | "path-to-regexp": { 640 | "version": "1.7.0", 641 | "resolved": "http://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-1.7.0.tgz", 642 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 643 | "requires": { 644 | "isarray": "0.0.1" 645 | } 646 | }, 647 | "patrun": { 648 | "version": "1.0.0", 649 | "resolved": "http://registry.npm.taobao.org/patrun/download/patrun-1.0.0.tgz", 650 | "integrity": "sha1-xQohdOPgKv5/vDXRbmWNIcZqHmY=", 651 | "requires": { 652 | "gex": "0.3", 653 | "lodash": "4.17" 654 | } 655 | }, 656 | "precond": { 657 | "version": "0.2.3", 658 | "resolved": "http://registry.npm.taobao.org/precond/download/precond-0.2.3.tgz", 659 | "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" 660 | }, 661 | "process-nextick-args": { 662 | "version": "2.0.0", 663 | "resolved": "http://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.0.tgz", 664 | "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" 665 | }, 666 | "pseudomap": { 667 | "version": "1.0.2", 668 | "resolved": "http://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz", 669 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" 670 | }, 671 | "qs": { 672 | "version": "6.5.2", 673 | "resolved": "http://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz", 674 | "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=" 675 | }, 676 | "raw-body": { 677 | "version": "2.3.3", 678 | "resolved": "http://registry.npm.taobao.org/raw-body/download/raw-body-2.3.3.tgz", 679 | "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", 680 | "requires": { 681 | "bytes": "3.0.0", 682 | "http-errors": "1.6.3", 683 | "iconv-lite": "0.4.23", 684 | "unpipe": "1.0.0" 685 | } 686 | }, 687 | "readable-stream": { 688 | "version": "2.3.6", 689 | "resolved": "http://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.6.tgz", 690 | "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", 691 | "requires": { 692 | "core-util-is": "~1.0.0", 693 | "inherits": "~2.0.3", 694 | "isarray": "~1.0.0", 695 | "process-nextick-args": "~2.0.0", 696 | "safe-buffer": "~5.1.1", 697 | "string_decoder": "~1.1.1", 698 | "util-deprecate": "~1.0.1" 699 | }, 700 | "dependencies": { 701 | "isarray": { 702 | "version": "1.0.0", 703 | "resolved": "http://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz", 704 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 705 | } 706 | } 707 | }, 708 | "reconnect-core": { 709 | "version": "1.3.0", 710 | "resolved": "http://registry.npm.taobao.org/reconnect-core/download/reconnect-core-1.3.0.tgz", 711 | "integrity": "sha1-+65SkZp4d9hE4yRtAaLyZwHIM8g=", 712 | "requires": { 713 | "backoff": "~2.5.0" 714 | } 715 | }, 716 | "regexp-clone": { 717 | "version": "0.0.1", 718 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz", 719 | "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=" 720 | }, 721 | "require_optional": { 722 | "version": "1.0.1", 723 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 724 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 725 | "requires": { 726 | "resolve-from": "^2.0.0", 727 | "semver": "^5.1.0" 728 | } 729 | }, 730 | "resolve-from": { 731 | "version": "2.0.0", 732 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 733 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 734 | }, 735 | "rolling-stats": { 736 | "version": "0.1.1", 737 | "resolved": "http://registry.npm.taobao.org/rolling-stats/download/rolling-stats-0.1.1.tgz", 738 | "integrity": "sha1-zVr3dKiJOzCmdIMvovSrqkeM/IA=" 739 | }, 740 | "safe-buffer": { 741 | "version": "5.1.2", 742 | "resolved": "http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz", 743 | "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" 744 | }, 745 | "safer-buffer": { 746 | "version": "2.1.2", 747 | "resolved": "http://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz", 748 | "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" 749 | }, 750 | "semver": { 751 | "version": "5.5.0", 752 | "resolved": "http://registry.npm.taobao.org/semver/download/semver-5.5.0.tgz", 753 | "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=" 754 | }, 755 | "seneca": { 756 | "version": "3.5.0", 757 | "resolved": "http://registry.npm.taobao.org/seneca/download/seneca-3.5.0.tgz", 758 | "integrity": "sha1-8ivRTbLulJNCjZyejd27eaO2Gqs=", 759 | "requires": { 760 | "archy": "1.0", 761 | "eraro": "1.0", 762 | "gate-executor": "1.1", 763 | "gex": "0.3", 764 | "json-stringify-safe": "5.0", 765 | "jsonic": "0.3", 766 | "lodash": "4.17", 767 | "minimist": "1.2", 768 | "nid": "0.3", 769 | "norma": "0.4", 770 | "ordu": "0.1", 771 | "patrun": "1.0", 772 | "qs": "6.5", 773 | "rolling-stats": "0.1", 774 | "semver": "5.5", 775 | "seneca-transport": "2.2", 776 | "use-plugin": "1.0", 777 | "wreck": "12.5" 778 | } 779 | }, 780 | "seneca-transport": { 781 | "version": "2.2.0", 782 | "resolved": "http://registry.npm.taobao.org/seneca-transport/download/seneca-transport-2.2.0.tgz", 783 | "integrity": "sha1-AFnn/Iqs9G3unK/TpvkeeQ6pMAs=", 784 | "requires": { 785 | "eraro": "1.0", 786 | "gex": "0.3", 787 | "jsonic": "0.3", 788 | "lodash": "4.17", 789 | "lru-cache": "4.1", 790 | "ndjson": "1.5", 791 | "nid": "0.3", 792 | "patrun": "1.0", 793 | "qs": "6.5", 794 | "reconnect-core": "1.3", 795 | "wreck": "12.5" 796 | } 797 | }, 798 | "seneca-web": { 799 | "version": "2.2.0", 800 | "resolved": "http://registry.npm.taobao.org/seneca-web/download/seneca-web-2.2.0.tgz", 801 | "integrity": "sha1-ol3xmtyqqHZZtCVHovlJ2Ln6RQs=", 802 | "requires": { 803 | "eraro": "^0.4.1", 804 | "lodash": "^4.17.4" 805 | }, 806 | "dependencies": { 807 | "eraro": { 808 | "version": "0.4.1", 809 | "resolved": "http://registry.npm.taobao.org/eraro/download/eraro-0.4.1.tgz", 810 | "integrity": "sha1-ZThzB2mh6J/8Pwx+LJTVofhmYp0=", 811 | "requires": { 812 | "lodash": "~2.4.1" 813 | }, 814 | "dependencies": { 815 | "lodash": { 816 | "version": "2.4.2", 817 | "resolved": "http://registry.npm.taobao.org/lodash/download/lodash-2.4.2.tgz", 818 | "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" 819 | } 820 | } 821 | } 822 | } 823 | }, 824 | "seneca-web-adapter-koa2": { 825 | "version": "1.1.0", 826 | "resolved": "http://registry.npm.taobao.org/seneca-web-adapter-koa2/download/seneca-web-adapter-koa2-1.1.0.tgz", 827 | "integrity": "sha1-o62kM02RWr0SmsskK1VG6Iq8vX4=", 828 | "requires": { 829 | "co-body": "^5.1.1", 830 | "lodash": "^4.16.2" 831 | } 832 | }, 833 | "setprototypeof": { 834 | "version": "1.1.0", 835 | "resolved": "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz", 836 | "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" 837 | }, 838 | "sliced": { 839 | "version": "1.0.1", 840 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 841 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 842 | }, 843 | "split2": { 844 | "version": "2.2.0", 845 | "resolved": "http://registry.npm.taobao.org/split2/download/split2-2.2.0.tgz", 846 | "integrity": "sha1-GGsldbz4PoW30YRldWI47k7kJJM=", 847 | "requires": { 848 | "through2": "^2.0.2" 849 | } 850 | }, 851 | "stack-trace": { 852 | "version": "0.0.10", 853 | "resolved": "http://registry.npm.taobao.org/stack-trace/download/stack-trace-0.0.10.tgz", 854 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 855 | }, 856 | "statuses": { 857 | "version": "1.5.0", 858 | "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz", 859 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 860 | }, 861 | "string_decoder": { 862 | "version": "1.1.1", 863 | "resolved": "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz", 864 | "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", 865 | "requires": { 866 | "safe-buffer": "~5.1.0" 867 | } 868 | }, 869 | "text-hex": { 870 | "version": "0.0.0", 871 | "resolved": "http://registry.npm.taobao.org/text-hex/download/text-hex-0.0.0.tgz", 872 | "integrity": "sha1-V4+8haapJjbkLdF7QdAhjM6esrM=" 873 | }, 874 | "through2": { 875 | "version": "2.0.3", 876 | "resolved": "http://registry.npm.taobao.org/through2/download/through2-2.0.3.tgz", 877 | "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", 878 | "requires": { 879 | "readable-stream": "^2.1.5", 880 | "xtend": "~4.0.1" 881 | } 882 | }, 883 | "triple-beam": { 884 | "version": "1.2.0", 885 | "resolved": "http://registry.npm.taobao.org/triple-beam/download/triple-beam-1.2.0.tgz", 886 | "integrity": "sha1-i1wfPUkSKb9t3PchwLqEzvWrUfo=" 887 | }, 888 | "type-is": { 889 | "version": "1.6.16", 890 | "resolved": "http://registry.npm.taobao.org/type-is/download/type-is-1.6.16.tgz", 891 | "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", 892 | "requires": { 893 | "media-typer": "0.3.0", 894 | "mime-types": "~2.1.18" 895 | } 896 | }, 897 | "unpipe": { 898 | "version": "1.0.0", 899 | "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", 900 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 901 | }, 902 | "urijs": { 903 | "version": "1.19.1", 904 | "resolved": "http://registry.npm.taobao.org/urijs/download/urijs-1.19.1.tgz", 905 | "integrity": "sha1-Ww/1MMDL3oOG9jQiNbpcpumV0lo=" 906 | }, 907 | "use-plugin": { 908 | "version": "1.0.2", 909 | "resolved": "http://registry.npm.taobao.org/use-plugin/download/use-plugin-1.0.2.tgz", 910 | "integrity": "sha1-kuTm2asDtpAxCTpnhkWdwIeR1DY=", 911 | "requires": { 912 | "eraro": "1.0", 913 | "nid": "0.3", 914 | "norma": "0.4" 915 | } 916 | }, 917 | "util-deprecate": { 918 | "version": "1.0.2", 919 | "resolved": "http://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz", 920 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 921 | }, 922 | "uuid": { 923 | "version": "3.2.1", 924 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", 925 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" 926 | }, 927 | "vary": { 928 | "version": "1.1.2", 929 | "resolved": "http://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz", 930 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 931 | }, 932 | "winston": { 933 | "version": "3.0.0-rc5", 934 | "resolved": "http://registry.npm.taobao.org/winston/download/winston-3.0.0-rc5.tgz", 935 | "integrity": "sha1-vDg9MrDndNOHpm53KQ/nh2ZGjzQ=", 936 | "requires": { 937 | "async": "^2.6.0", 938 | "diagnostics": "^1.0.1", 939 | "is-stream": "^1.1.0", 940 | "logform": "^1.4.1", 941 | "one-time": "0.0.4", 942 | "stack-trace": "0.0.x", 943 | "triple-beam": "^1.0.1", 944 | "winston-transport": "^3.1.0" 945 | } 946 | }, 947 | "winston-transport": { 948 | "version": "3.2.1", 949 | "resolved": "http://registry.npm.taobao.org/winston-transport/download/winston-transport-3.2.1.tgz", 950 | "integrity": "sha1-W3mylAlrGhi/5QLnaUkVU6LLEEI=" 951 | }, 952 | "wreck": { 953 | "version": "12.5.1", 954 | "resolved": "http://registry.npm.taobao.org/wreck/download/wreck-12.5.1.tgz", 955 | "integrity": "sha1-zS/84WdEnh8CQu2c+AVS4g+2kCo=", 956 | "requires": { 957 | "boom": "5.x.x", 958 | "hoek": "4.x.x" 959 | } 960 | }, 961 | "xtend": { 962 | "version": "4.0.1", 963 | "resolved": "http://registry.npm.taobao.org/xtend/download/xtend-4.0.1.tgz", 964 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 965 | }, 966 | "yallist": { 967 | "version": "2.1.2", 968 | "resolved": "http://registry.npm.taobao.org/yallist/download/yallist-2.1.2.tgz", 969 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" 970 | } 971 | } 972 | } 973 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vastify", 3 | "version": "1.3.0", 4 | "description": "轻量级nodejs微服务框架", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "node src/index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Cecil0o0/vastify.git" 12 | }, 13 | "keywords": [ 14 | "nodejs", 15 | "winston", 16 | "seneca", 17 | "koa", 18 | "rabbitmq", 19 | "mongodb", 20 | "reddis" 21 | ], 22 | "author": "Cecil", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/Cecil0o0/vastify/issues" 26 | }, 27 | "homepage": "https://github.com/Cecil0o0/vastify#readme", 28 | "dependencies": { 29 | "consul": "^0.31.0", 30 | "koa": "^2.5.1", 31 | "koa-router": "^7.4.0", 32 | "mongoose": "^5.1.2", 33 | "seneca": "^3.5.0", 34 | "seneca-web": "^2.2.0", 35 | "seneca-web-adapter-koa2": "^1.1.0", 36 | "uuid": "^3.2.1", 37 | "winston": "^3.0.0-rc5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/config/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-05-31 21:57:27 5 | * @Description 框架默认配置文件 6 | */ 7 | 8 | 'use strict' 9 | 10 | const isProduction = process.env.NODE_ENV === 'production' 11 | 12 | module.exports = { 13 | 14 | microservice: { 15 | healthCheckReturn: { 16 | version: 'default' 17 | }, 18 | requestTimeout: '5s' 19 | }, 20 | 21 | routing: { 22 | servicesRefresh: 12000 23 | }, 24 | 25 | seneca: { 26 | // seneca启动超时时间 27 | timeout: 5000, 28 | // 日志 29 | log: { 30 | level: 'info', 31 | short: !isProduction 32 | }, 33 | // 微服务唯一标识 34 | tag: 'micro1000', 35 | // 追踪 36 | trace: { 37 | act: true, 38 | stack: true 39 | }, 40 | debug: { 41 | // 当设置为true时,如软件运行中throw new Error()软件不会挂掉 42 | undead: isProduction 43 | } 44 | }, 45 | 46 | ampq: { 47 | type: 'rabbitmq', 48 | address: 'amqp://localhost' 49 | }, 50 | 51 | cache: { 52 | type: 'reddis', 53 | address: '' 54 | }, 55 | 56 | db: { 57 | type: 'mongodb', 58 | version: 'v3.4.9', 59 | address: 'mongodb://localhost:27017', 60 | FatalIfNotConnected: true 61 | }, 62 | 63 | pm2: { 64 | app: { 65 | args: '', 66 | max_memory_restart: '150M', 67 | // 默认环境配置 68 | env: { 69 | NODE_ENV: 'development' 70 | }, 71 | // 测试环境配置 72 | env_test: { 73 | NODE_ENV: 'test' 74 | }, 75 | // 生产环境配置 76 | env_production: { 77 | NODE_ENV: 'production' 78 | }, 79 | // source map 80 | source_map_support: true, 81 | // 不合并日志输出,用于集群服务 82 | merge_logs: false, 83 | // 常用于启动应用时异常,超时时间限制 84 | listen_timeout: 5000, 85 | // 进程SIGINT命令时间限制,即进程必须在监听到SIGINT信号后必须在以下设置时间结束进程 86 | kill_timeout: 2000, 87 | // 当启动异常后不尝试重启,运维人员尝试找原因后重试 88 | autorestart: false, 89 | // 不允许以相同脚本启动进程 90 | force: false, 91 | // 在Keymetrics dashboard中执行pull/upgrade操作后执行的命令队列 92 | post_update: ['npm install'], 93 | // 监听文件变化 94 | watch: false, 95 | // 忽略监听文件变化 96 | ignore_watch: ['node_modules'] 97 | }, 98 | deploy: { 99 | 'user': 'deploy', 100 | 'host': 'qingf.me', 101 | 'ref': 'remotes/origin/master', 102 | 'repo': 'https://github.com/Cecil0o0/account-server.git', 103 | 'path': '/home/deploy/apps/account-server', 104 | // 生命周期钩子,在ssh到远端之后setup操作之前执行 105 | 'pre-setup': '', 106 | // 生命周期钩子,在初始化设置即git pull之后执行 107 | 'post-setup': 'ls -la', 108 | // 生命周期钩子,在远端git fetch origin之前执行 109 | 'pre-setup': '', 110 | // 生命周期钩子,在远端git修改HEAD指针到指定ref之后执行 111 | 'post-deploy': 'npm install && pm2 startOrRestart deploy/ecosystem.config.js --env production', 112 | // 以下这个环境变量将注入到所有app中 113 | "env": { 114 | "NODE_ENV": "production" 115 | } 116 | } 117 | }, 118 | 119 | logger: { 120 | winston: { 121 | level: 'info', 122 | label: 'microservices', 123 | format: info => { 124 | return `${info.timestamp} [${info.label}] ${info.level}: ${info.message}` 125 | } 126 | } 127 | }, 128 | 129 | // 服务注册与发现 130 | // https://github.com/silas/node-consul#catalog-node-services 131 | 'serverR&D': { 132 | consulServer: { 133 | type: 'consul', 134 | host: '127.0.0.1', 135 | port: 8500, 136 | secure: false, 137 | ca: [], 138 | defaults: { 139 | token: '' 140 | }, 141 | promisify: true 142 | }, 143 | bizService: { 144 | name: 'defaultName', 145 | id: 'defaultId', 146 | address: '127.0.0.1', 147 | port: 1000, 148 | tags: [], 149 | meta: { 150 | version: '', 151 | description: '注册集群' 152 | }, 153 | check: { 154 | // 用于consul client主动请求consul server,如果设定时间内未更新check则失效(ex: 60s),这里由于我们的业务服务统一用pm2做集群处理,所以考虑到集群主动请求的效率较低,这里还是采用http形式 155 | // ttl: '15s', 156 | http: '', 157 | // check间隔时间(ex: 15s) 158 | interval: '10s', 159 | // check超时时间(ex: 10s) 160 | timeout: '2s', 161 | // 处于临界状态后自动注销服务的超时时间 162 | deregistercriticalserviceafter: '30s', 163 | // 初始化状态值为成功 164 | status: 'passing', 165 | // 备注 166 | notes: '{"version":"111","microservice-port":1115}' 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/config/dev.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | module.exports = Object.assign({ 3 | env: 'development' 4 | }, common) -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | const options = process.env.NODE_ENV === 'production' ? require('./prod') : process.env.NODE_ENV === 'test' ? require('./test') : require('./dev') 2 | 3 | module.exports = options -------------------------------------------------------------------------------- /src/config/prod.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | module.exports = Object.assign({ 3 | env: 'production' 4 | }, common) -------------------------------------------------------------------------------- /src/config/test.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | module.exports = Object.assign({ 3 | env: 'test' 4 | }, common) -------------------------------------------------------------------------------- /src/engine/amqp-client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Cecil 3 | * @description 该模块输出高级队列实例,比如rabbitmq、rocketmq等等 4 | */ 5 | 6 | 'use strict' 7 | 8 | const amqp = require('amqplib') 9 | const config = require('./config') 10 | 11 | let resolveHandler = null 12 | let rejectHandler = null 13 | 14 | let getChannel = new Promise((resolve, reject) => { 15 | resolveHandler = resolve 16 | rejectHandler = reject 17 | }) 18 | 19 | amqp.connect(config.amqpAddr, (err, conn) => { 20 | conn.createChannel((err, ch) => { 21 | ch ? resolveHandler(ch) : rejectHandler(err) 22 | }) 23 | }) 24 | 25 | module.exports = getChannel -------------------------------------------------------------------------------- /src/engine/cache-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-05-31 01:37:57 5 | * @Description 该模块输出缓存实例,比如redis等等 6 | */ 7 | 8 | 'use strict' 9 | -------------------------------------------------------------------------------- /src/engine/db-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-05-31 10:07:08 5 | * @Description mongodb实例 6 | */ 7 | 8 | 'use strict' 9 | 10 | const mongoose = require('mongoose') 11 | 12 | let instance = null 13 | 14 | function getInstance(DBConfig = {}) { 15 | if (!instance) { 16 | mongoose.connect(DBConfig.address || 'mongodb://localhost:27017') 17 | 18 | instance = mongoose.connection 19 | instance.on('error', console.error.bind(console, 'mongoose connection error:')) 20 | instance.once('open', function() { 21 | console.log('mongoose connection opened') 22 | }) 23 | instance.on('connected', () => { 24 | console.log('mongoose connection connected') 25 | }) 26 | instance.on('disconnected', () => { 27 | if (DBConfig.FatalIfNotConnected) { 28 | throw new Error('mongodb未连接') 29 | process.exit(1) 30 | } else { 31 | console.log('mongodb未连接') 32 | } 33 | }) 34 | } 35 | return instance 36 | } 37 | 38 | module.exports = { 39 | getInstance 40 | } 41 | -------------------------------------------------------------------------------- /src/engine/health-check.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-05-31 11:12:42 5 | * @Description 微服务提供consul健康检查接口的插件 6 | */ 7 | 8 | 'use strict' 9 | 10 | const Web = require('./web') 11 | const web = Web.getInstance() 12 | const { VastifyWebModule } = web 13 | 14 | const moduleName = 'module:base' 15 | 16 | const routes = [ 17 | { 18 | prefix: '/health', 19 | pin: `${moduleName},if:*`, 20 | map: { 21 | check: { 22 | GET: true, 23 | name: '' 24 | } 25 | } 26 | } 27 | ] 28 | 29 | function plugin (options = {}) { 30 | this.add(`${moduleName},if:check`, (msg, done) => { 31 | done(null, { 32 | version: options.version 33 | }) 34 | }) 35 | } 36 | 37 | module.exports = { 38 | plugin, 39 | routes, 40 | options: {} 41 | } 42 | -------------------------------------------------------------------------------- /src/engine/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 10:00:06 5 | * @Description 引擎入口文件 6 | */ 7 | 'use strict' 8 | 9 | let config = require('../config') 10 | const Seneca = require('seneca') 11 | const HealthCheckWebModule = require('./health-check') 12 | const Web = require('./web') 13 | const DB = require('./db-client') 14 | const { ObjectDeepSet } = require('../helper/utils') 15 | 16 | let instance = null 17 | 18 | function getInstance(externalConfig = {}) { 19 | if (!instance) { 20 | instance = { 21 | config 22 | } 23 | ObjectDeepSet(instance.config, externalConfig) 24 | // 微服务核心组件 25 | instance.seneca = new Seneca(instance.config.seneca) 26 | // 微服务对外提供http服务组件 27 | instance.web = Web.getInstance() 28 | // 提供http服务扩展 29 | instance.web.externalUseREST(instance.seneca) 30 | // 初始化健康查询接口 31 | HealthCheckWebModule.options = instance.config.microservice.healthCheckReturn 32 | instance.seneca.useREST(new instance.web.VastifyWebModule(HealthCheckWebModule)) 33 | // 持久化存储组件 34 | instance.db = DB.getInstance(instance.config.db) 35 | } 36 | return instance 37 | } 38 | 39 | module.exports = { 40 | getInstance 41 | } 42 | -------------------------------------------------------------------------------- /src/engine/web.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-05-31 10:42:24 5 | * @Description 该模块用于初始化web server实例 框架采用koa 6 | */ 7 | 8 | 'use strict' 9 | 10 | const Koa = require('koa') 11 | const Router = require('koa-router') 12 | const SenecaWebAdapterKoa = require('seneca-web-adapter-koa2') 13 | const SenecaWeb = require('seneca-web') 14 | const logger = new (require('../tools/logger'))().generateLogger() 15 | const app = new Koa() 16 | 17 | class VastifyWebModule { 18 | constructor ({ plugin, routes, options = {} }) { 19 | if (!plugin) throw new Error('plugin为必传参数,须符合http://senecajs.org/api/#plugin-init规范') 20 | if (!routes) throw new Error('routes为必传参数,须符合https://github.com/senecajs/seneca-web规范') 21 | // seneca插件 22 | this.plugin = plugin 23 | // seneca-web路由 24 | this.routes = routes 25 | this.options = options 26 | } 27 | } 28 | 29 | const isNormativeModule = m => { 30 | return m instanceof VastifyWebModule 31 | } 32 | 33 | const use = function (m, { context }) { 34 | // 初始化模块 35 | context.use(m.plugin, m.options) 36 | 37 | // 初始化seneca-web插件,并适配koa 38 | context.use(SenecaWeb, { 39 | context: Router(), 40 | adapter: SenecaWebAdapterKoa, 41 | routes: [...m.routes] 42 | }) 43 | } 44 | 45 | const warnMsg = '检测到传入的模块为非标准模块,已自动忽略' 46 | 47 | // 该方法用于koa注册路由,seneca注册模式,然后建立路由与模式之间一对一关系 48 | const externalUseREST = seneca => { 49 | let useREST = function(webServiceModules) { 50 | if (webServiceModules instanceof Array) { 51 | webServiceModules.forEach(function(m) { 52 | if (isNormativeModule(m)) { 53 | use(m, { 54 | context: seneca 55 | }) 56 | } else { 57 | logger.warn(warnMsg) 58 | } 59 | }) 60 | } else if (typeof webServiceModules === 'object' && webServiceModules instanceof VastifyWebModule) { 61 | use(webServiceModules, { 62 | context: seneca 63 | }) 64 | } else { 65 | logger.warn(warnMsg) 66 | } 67 | }.bind(seneca) 68 | 69 | seneca.useREST = useREST 70 | } 71 | 72 | class Web { 73 | constructor () { 74 | // 须配合userREST后生效 75 | this.app = app 76 | // koa-router构造器 77 | this.Router = Router, 78 | // 使用seneca插件并提供REST接口,须配合app后生效 79 | this.externalUseREST = externalUseREST 80 | // 如需对外提供http服务,所编写模块需要符合的规则 81 | this.VastifyWebModule = VastifyWebModule 82 | // seneca-web适配koa 83 | this.SenecaWebAdapterKoa = SenecaWebAdapterKoa 84 | } 85 | } 86 | 87 | let instance = null 88 | 89 | function getInstance() { 90 | if (!instance) { 91 | instance = new Web() 92 | } 93 | return instance 94 | } 95 | 96 | module.exports = { 97 | getInstance 98 | } 99 | -------------------------------------------------------------------------------- /src/helper/consul.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 14:20:25 5 | * @Description consul相关的工具类 6 | */ 7 | 8 | 'use strict' 9 | 10 | const uuidv4 = require('uuid/v4') 11 | const logger = new (require('../tools/logger'))().generateLogger() 12 | 13 | const getServiceNameByServiceKey = function getServiceNameByServiceKey(key = '') { 14 | return key.replace(/service:/, '').split('@').shift() 15 | } 16 | 17 | const getServiceIdByServiceKey = function getServiceIdByServiceKey(key = '') { 18 | return key.replace(/service:/, '') 19 | } 20 | 21 | const generateServiceName = function generateServiceName(name = '') { 22 | return `${name}@${uuidv4()}` 23 | } 24 | 25 | const generateCheckHttp = function generateCheckHttp(host = '127.0.0.1', port = 80) { 26 | return `http://${host}:${port}/health` 27 | } 28 | 29 | module.exports = { 30 | getServiceNameByServiceKey, 31 | getServiceIdByServiceKey, 32 | generateServiceName, 33 | generateCheckHttp 34 | } 35 | -------------------------------------------------------------------------------- /src/helper/regex.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-07 17:26:16 5 | * @Description 依赖的正则表达式集合 6 | */ 7 | 8 | 'use strict' 9 | 10 | const IPV4_REGEX = /(?=(\b|\D))(((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))(?=(\b|\D))/ 11 | 12 | const bailRE = /[^\w.$]/ 13 | 14 | module.exports = { 15 | IPV4_REGEX, 16 | bailRE 17 | } 18 | -------------------------------------------------------------------------------- /src/helper/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 深度遍历赋值对象属性 3 | * @param {Object} o1 4 | * @param {Object} o2 5 | */ 6 | 'use strict' 7 | 8 | const { bailRE } = require('./regex') 9 | 10 | const ObjectDeepSet = function ObjectDeepSet(o1 = {}, o2 = {}) { 11 | for (let key in o1) { 12 | if (o1.hasOwnProperty(key) && o2.hasOwnProperty(key)) { 13 | if (o1[key] instanceof Array && o2[key] instanceof Array) { 14 | let length = (o1[key].length < o2[key].length ? o1[key].length : o2[key].length) || 0 15 | for (let i = 0; i < length; i++) { 16 | if (typeof o1[key][i] !== 'object' && typeof o2[key][i] !== 'object') { 17 | o1[key][i] = o2[key][i] 18 | } else { 19 | ObjectDeepSet(o1[key][i], o2[key][i]) 20 | } 21 | } 22 | } else if (o1[key] instanceof Date && o2[key] instanceof Date) { 23 | o1[key] = new Date(o2[key].getTime()) 24 | } else if (typeof o1[key] === 'object' && typeof o2[key] === 'object') { 25 | ObjectDeepSet(o1[key], o2[key]) 26 | } else { 27 | o1[key] = o2[key] 28 | } 29 | } 30 | } 31 | return o1 32 | } 33 | 34 | const curry = type => param => typeof param === type 35 | 36 | const isString = curry('string') 37 | const isNumber = input => input === +input 38 | 39 | /** 40 | * Parse simple path. 41 | */ 42 | 43 | const parsePath = function parsePath (path) { 44 | if (bailRE.test(path)) { 45 | return 46 | } 47 | const segments = path.split('.') 48 | return function (obj) { 49 | for (let i = 0; i < segments.length; i++) { 50 | if (!obj) return 51 | obj = obj[segments[i]] 52 | } 53 | return obj 54 | } 55 | } 56 | 57 | module.exports = { 58 | ObjectDeepSet, 59 | isString, 60 | isNumber, 61 | parsePath 62 | } 63 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 13:52:12 5 | * @Description 入口文件 6 | */ 7 | 8 | 'use strict' 9 | 10 | const Engine = require('./engine') 11 | const Tools = require('./tools') 12 | const Plugins = require('./plugins') 13 | 14 | module.exports = { 15 | ...Engine, 16 | // 以下对象单独实例化是因为考虑到业务代码中可能会**单独**使用 17 | ...Tools, 18 | // 框架内置插件 19 | Plugins 20 | } 21 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 15:40:58 5 | * @Description 框架内置seneca插件 6 | */ 7 | 8 | 'use strict' 9 | 10 | const Routing = require('./microRouting') 11 | 12 | module.exports = { 13 | Routing 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/microRouting.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 16:22:02 5 | * @Description 微服务内部路由中间件,暂不支持自定义路由匹配策略 6 | */ 7 | 8 | 'use strict' 9 | 10 | const Consul = require('consul') 11 | const defaultConf = require('../config') 12 | const { ObjectDeepSet, isNumber } = require('../helper/utils') 13 | const { getServiceNameByServiceKey, getServiceIdByServiceKey } = require('../helper/consul') 14 | const logger = new (require('../tools/logger'))().generateLogger() 15 | const { IPV4_REGEX } = require('../helper/regex') 16 | 17 | let services = {} 18 | let consul = null 19 | 20 | /** 21 | * @author Cecil0o0 22 | * @description 同步consul服务中心的所有可用服务以及对应check并组装成对象以方便取值 23 | */ 24 | function syncCheckList () { 25 | return new Promise((resolve, reject) => { 26 | consul.agent.service.list().then(allServices => { 27 | if (Object.keys(allServices).length > 0) { 28 | services = allServices 29 | consul.agent.check.list().then(checks => { 30 | Object.keys(checks).forEach(key => { 31 | allServices[getServiceIdByServiceKey(key)]['check'] = checks[key] 32 | }) 33 | resolve(services) 34 | }).catch(err => { 35 | throw new Error(err) 36 | }) 37 | } else { 38 | const errmsg = '未发现可用服务' 39 | logger.warn(errmsg) 40 | reject(errmsg) 41 | } 42 | }).catch(err => { 43 | throw new Error(err) 44 | }) 45 | }) 46 | } 47 | 48 | function syncRoutingRule(senecaInstance = {}, services = {}) { 49 | Object.keys(services).forEach(key => { 50 | let service = services[key] 51 | let name = getServiceNameByServiceKey(key) 52 | let $$addr = service.Address 53 | let $$microservicePort = '' 54 | let $$version = '' 55 | try { 56 | let base = JSON.parse(service.check.Notes) 57 | $$microservicePort = base.$$microservicePort 58 | $$version = base.$$version 59 | } catch (e) { 60 | logger.warn(`服务名为${serviceName}。该服务check.Notes为非标准JSON格式,程序已忽略。请检查服务注册方式(请确保调用ServerRegister的register来注册服务)`) 61 | } 62 | 63 | if (IPV4_REGEX.test($$addr) && isNumber($$microservicePort)) { 64 | if (service.check.Status === 'passing') { 65 | senecaInstance.client({ 66 | host: $$addr, 67 | port: $$microservicePort, 68 | pin: { 69 | $$version, 70 | $$target: name 71 | } 72 | }) 73 | } else { 74 | logger.warn(`${$$target}@${$$version || '无'}服务处于critical,因此无法使用`) 75 | } 76 | } else { 77 | logger.warn(`主机(${$$addr})或微服务端口号(${$$microservicePort})有误,请检查`) 78 | } 79 | }) 80 | } 81 | 82 | 83 | function startTimeInterval() { 84 | setInterval(syncCheckList, defaultConf.routing.servicesRefresh) 85 | } 86 | 87 | function microRouting(consulServer) { 88 | var self = this 89 | consul = Consul(ObjectDeepSet(defaultConf['serverR&D'].consulServer, consulServer)) 90 | syncCheckList().then(services => { 91 | syncRoutingRule(self, services) 92 | }) 93 | } 94 | 95 | module.exports = microRouting 96 | -------------------------------------------------------------------------------- /src/servers/httpRouting.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 15:41:59 5 | * @Description 内置http路由服务 6 | */ 7 | 'use strict' 8 | 9 | const Consul = require('consul') 10 | const defaultConf = require('../config') 11 | const Seneca = require('seneca') 12 | const { ObjectDeepSet } = require('../helper/utils') 13 | const logger = new (require('../tools/logger'))().generateLogger() 14 | const Web = require('../engine/web') 15 | 16 | const seneca = new Seneca(ObjectDeepSet(defaultConf.seneca, { 17 | tag: 'routing-server' 18 | })) 19 | let consul = null 20 | let services = {} 21 | 22 | function syncCheckList () { 23 | return new Promise((resolve, reject) => { 24 | consul.agent.service.list().then(allServices => { 25 | if (Object.keys(allServices).length > 0) { 26 | services = allServices 27 | consul.agent.check.list().then(checks => { 28 | Object.keys(checks).forEach(key => { 29 | allServices[key.replace('service:', '')]['check'] = checks[key] 30 | }) 31 | console.log(services) 32 | resolve(services) 33 | }).catch(err => { 34 | throw new Error(err) 35 | }) 36 | } else { 37 | const errmsg = '未发现可用服务' 38 | logger.warn(errmsg) 39 | throw new Error(errmsg) 40 | } 41 | }).catch(err => { 42 | throw new Error(err) 43 | }) 44 | }) 45 | } 46 | 47 | function start(consulServer) { 48 | 49 | consul = Consul(ObjectDeepSet(defaultConf['serverR&D'].consulServer, consulServer)) 50 | syncCheckList().then(() => { 51 | seneca.add({ 52 | $$action: 'routing', 53 | timeout$: defaultConf.microservice.requestTimeout 54 | }, (msg, done) => { 55 | const { $$target, $$version } = msg 56 | }) 57 | 58 | startTimeInterval() 59 | }) 60 | } 61 | 62 | function startTimeInterval() { 63 | setInterval(syncCheckList, defaultConf.routing.servicesRefresh) 64 | } 65 | 66 | start() 67 | 68 | module.exports = start 69 | -------------------------------------------------------------------------------- /src/servers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 15:40:22 5 | * @Description 框架内置服务集合 6 | */ 7 | 8 | 'use strict' 9 | 10 | const httpRouting = require('./httpRouting') 11 | 12 | module.exports = { 13 | httpRouting 14 | } 15 | -------------------------------------------------------------------------------- /src/tools/deploy-tool.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-05-31 10:02:35 5 | * @Description 该模块输出自动化部署配置实例生成器,用pm2 6 | */ 7 | 8 | 'use strict' 9 | 10 | const defaultConfig = require('../config').pm2 11 | 12 | function GeneratePM2AppConfig({ name = '', script = '', error_file = '', out_file = '', exec_mode = 'fork', instances = 1 }) { 13 | if (name) { 14 | return Object.assign(defaultConfig.app, { 15 | name, 16 | script: script || `${name}.js`, 17 | error_file: error_file || `${name}-err.log`, 18 | out_file: out_file|| `${name}-out.log`, 19 | instances, 20 | exec_mode: instances > 1 ? 'cluster' : 'fork' 21 | }) 22 | } else { 23 | throw new Error('创建pm2 app config时必须传入应用名称') 24 | return null 25 | } 26 | } 27 | 28 | function GeneratePM2DeployConfig({ name = '', user = 'deploy', host = '', ref = 'remotes/origin/master', repo = '', path = '', env = {} } = {}) { 29 | if (user && host && repo && path) { 30 | return Object.assign(defaultConfig.deploy, { 31 | user, 32 | host, 33 | ref, 34 | repo, 35 | path, 36 | env 37 | }) 38 | } else { 39 | throw new Error('创建pm2 deploy config时必须传入user,host,repo,path') 40 | return null 41 | } 42 | } 43 | 44 | module.exports = class DeployTool { 45 | constructor() { 46 | this.GeneratePM2AppConfig = GeneratePM2AppConfig 47 | this.GeneratePM2DeployConfig = GeneratePM2DeployConfig 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tools/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 13:52:06 5 | * @Description 微服务内置工具类 6 | */ 7 | 8 | 'use strict' 9 | 10 | const DeployTool = require('./deploy-tool') 11 | const Logger = require('./logger') 12 | const ServerRegister = require('./server-register') 13 | 14 | module.exports = { 15 | // pm2自动化部署相关工具 16 | DeployTool: new DeployTool(), 17 | // 日志组件 18 | Logger: new Logger(), 19 | // 服务注册(用于pm2编程作集群注册) 20 | ServerRegister: new ServerRegister(), 21 | } 22 | -------------------------------------------------------------------------------- /src/tools/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-05-31 01:55:47 5 | * @Description 该模块输出日志实例生成器,比如winston 6 | */ 7 | 8 | 'use strict' 9 | 10 | const { createLogger, format, transports } = require('winston') 11 | const { combine, timestamp, label, printf } = format 12 | const defaultWinstonConfig = require('../config').logger.winston 13 | 14 | // highest to lowest 15 | const levels = { 16 | error: 0, 17 | warn: 1, 18 | info: 2, 19 | verbose: 3, 20 | debug: 4, 21 | silly: 5 22 | } 23 | 24 | let instance = null 25 | 26 | function generateLogger(winstonConfig = {}) { 27 | if (!instance) { 28 | instance = createLogger({ 29 | level: winstonConfig.level || defaultWinstonConfig.level, 30 | format: combine( 31 | label({label: winstonConfig.label || defaultWinstonConfig.label}), 32 | timestamp(), 33 | printf(winstonConfig.format || defaultWinstonConfig.format) 34 | ), 35 | transports: [ new transports.Console() ] 36 | }) 37 | } 38 | return instance 39 | } 40 | 41 | module.exports = class Logger { 42 | constructor() { 43 | this.generateLogger = generateLogger 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/tools/server-register.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Cecil 3 | * @Last Modified by: Cecil 4 | * @Last Modified time: 2018-06-02 15:38:23 5 | * @Description 微服务注册方法 6 | */ 7 | const defaultConf = require('../config')['serverR&D'] 8 | const { ObjectDeepSet, isString } = require('../helper/utils') 9 | const Consul = require('consul') 10 | const { generateServiceName, generateCheckHttp } = require('../helper/consul') 11 | const logger = new (require('./logger'))().generateLogger() 12 | 13 | // 注册服务 14 | 15 | function register({ consulServer = {}, bizService = {} } = {}) { 16 | if (!bizService.name && isString(bizService.name)) throw new Error('name is invalid!') 17 | if (bizService.port !== +bizService.port) throw new Error('port is invalid!') 18 | if (!bizService.host && isString(bizService.host)) throw new Error('host is invalid!') 19 | if (!bizService.meta.$$version) throw new Error('meta.$$version is invalid!') 20 | if (!bizService.meta.$$microservicePort) throw new Error('meta.$$microservicePort is invalid!') 21 | const consul = Consul(ObjectDeepSet(defaultConf.consulServer, consulServer)) 22 | const service = defaultConf.bizService 23 | service.name = generateServiceName(bizService.name) 24 | service.id = service.name 25 | service.address = bizService.host 26 | service.port = bizService.port 27 | service.check.http = generateCheckHttp(bizService.host, bizService.port) 28 | service.check.notes = JSON.stringify(bizService.meta) 29 | 30 | return new Promise((resolve, reject) => { 31 | consul.agent.service.list().then(services => { 32 | // 检查主机+端口是否已被占用 33 | Object.keys(services).some(key => { 34 | if (services[key].Address === service.address && services[key].Port === service.port) { 35 | throw new Error(`该服务集群endpoint[${service.address}, ${service.port}]已被占用!`) 36 | } 37 | }) 38 | // 注册集群服务 39 | consul.agent.service.register(service).then(() => { 40 | logger.info(`${bizService.name}服务已成功注册!`) 41 | resolve(services) 42 | }).catch(err => { 43 | throw new Error(err) 44 | }) 45 | }).catch(err => { 46 | throw new Error(err) 47 | }) 48 | }) 49 | } 50 | 51 | module.exports = class ServerRegister { 52 | constructor() { 53 | this.register = register 54 | } 55 | } 56 | --------------------------------------------------------------------------------