关于koa:koakoabodyparser源码

/**! * koa-body-parser - index.js * Copyright(c) 2014 * MIT Licensed * * Authors: * dead_horse <dead_horse@qq.com> (http://deadhorse.me) * fengmk2 <m@fengmk2.com> (http://fengmk2.com) */'use strict';/** * Module dependencies. */var parse = require('co-body');var copy = require('copy-to');/** * @param [Object] opts * - {String} jsonLimit default '1mb' * - {String} formLimit default '56kb' * - {string} encoding default 'utf-8' * - {Object} extendTypes */module.exports = function (opts) { opts = opts || {}; var detectJSON = opts.detectJSON; var onerror = opts.onerror; var enableTypes = opts.enableTypes || ['json', 'form']; var enableForm = checkEnable(enableTypes, 'form'); var enableJson = checkEnable(enableTypes, 'json'); var enableText = checkEnable(enableTypes, 'text'); var enableXml = checkEnable(enableTypes, 'xml'); opts.detectJSON = undefined; opts.onerror = undefined; // force co-body return raw body opts.returnRawBody = true; // default json types var jsonTypes = [ 'application/json', 'application/json-patch+json', 'application/vnd.api+json', 'application/csp-report', ]; // default form types var formTypes = [ 'application/x-www-form-urlencoded', ]; // default text types var textTypes = [ 'text/plain', ]; // default xml types var xmlTypes = [ 'text/xml', 'application/xml', ]; var jsonOpts = formatOptions(opts, 'json'); var formOpts = formatOptions(opts, 'form'); var textOpts = formatOptions(opts, 'text'); var xmlOpts = formatOptions(opts, 'xml'); var extendTypes = opts.extendTypes || {}; extendType(jsonTypes, extendTypes.json); extendType(formTypes, extendTypes.form); extendType(textTypes, extendTypes.text); extendType(xmlTypes, extendTypes.xml); return async function bodyParser(ctx, next) { // 判断ctx.request.body是否曾经被设置过,如果是则间接通过 if (ctx.request.body !== undefined) return await next(); // disableBodyParser 为True,间接通过不解析,见API if (ctx.disableBodyParser) return await next(); try { // 解析body的入口 const res = await parseBody(ctx); ctx.request.body = 'parsed' in res ? res.parsed : {}; if (ctx.request.rawBody === undefined) ctx.request.rawBody = res.raw; } catch (err) { if (onerror) { onerror(err, ctx); } else { throw err; } } await next(); }; // 别离对json、from、text、xml四种格局进行解析,其余的间接返回{} async function parseBody(ctx) { // enableJson 是否开启JSON解析, option 参数配置 // detectJSON 自定义检测是否为JSON申请 option 参数配置 // Content-Type是否为JSON相干 if (enableJson && ((detectJSON && detectJSON(ctx)) || ctx.request.is(jsonTypes))) { // 最终还是应用co-body去解析 return await parse.json(ctx, jsonOpts); } if (enableForm && ctx.request.is(formTypes)) { return await parse.form(ctx, formOpts); } if (enableText && ctx.request.is(textTypes)) { return await parse.text(ctx, textOpts) || ''; } if (enableXml && ctx.request.is(xmlTypes)) { return await parse.text(ctx, xmlOpts) || ''; } return {}; }};function formatOptions(opts, type) { var res = {}; copy(opts).to(res); res.limit = opts[type + 'Limit']; return res;}function extendType(original, extend) { if (extend) { if (!Array.isArray(extend)) { extend = [extend]; } extend.forEach(function (extend) { original.push(extend); }); }}function checkEnable(types, type) { return types.includes(type);}由下面的代码能够看出,koa-bodyparser最终还是通过co-body去解析申请内容并生成ctx.req.body.上面以parse.json为例,探索下大略过程: ...

June 28, 2022 · 3 min · jiezi

关于koa:koa异常处理详解

文章不易,请关注公众号 毛毛虫的小小蜡笔,多多反对,谢谢 问题koa是怎么解决异样的? 剖析首先理解下node.js是怎么解决异样的 一般来说,node.js顶层有个uncaughtException事件,当异样没被捕捉的时候,就会一层层回升,直到触发定义好的uncaughtException事件。 但有个问题,node.js最大的特点是异步机制。比方读取文件信息的stat的异步写法: require('fs').stat('test.txt', function(err, res) { if (err) { throw err; }})如果读取文件信息过程中出错了,比方文件不存在。那就会执行throw err。 就算用了try catch,也不能捕捉异步函数。 详情 请查看:毛毛虫的小小蜡笔

June 24, 2022 · 1 min · jiezi

关于koa:koa原理详解

文章不易,请关注公众号 毛毛虫的小小蜡笔,多多反对,谢谢 先看个Demoapp.use(async (ctx, next) => { console.log(1) await next() console.log(2) ctx.body = 'Hello Koa';});app.use(async (ctx, next) => { console.log(3) await next() console.log(4)});app.use(async (ctx, next) => { console.log(5)});输入后果是:13542 这就是所谓的洋葱模型。第一个中间件在最外层,而后第二个在第二层,第三个则是最外面一层。所以先执行1,而后到第二层的3,再到最外面的5,执行完后再到第二层的4,最初返回到第一层的2。 看源码app.use最次要的是把中间件存起来:this.middleware.push(fn)。 详情 请查看:毛毛虫的小小蜡笔

May 31, 2022 · 1 min · jiezi

关于koa:koa源码

源码目录构造Applicationapplication.js次要是对 App 做的一些操作,包含创立服务、在 ctx 对象上挂载 request、response 对象,以及解决异样等操作。接下来将对这些实现进行具体论述。 Koa 创立服务的原理Node 原生创立服务const http = require("http");const server = http.createServer((req, res) => { res.writeHead(200); res.end("hello world");});server.listen(4000, () => { console.log("server start at 4000");});module.exports = class Application extends Emitter { /** * Shorthand for: * * http.createServer(app.callback()).listen(...) * * @param {Mixed} ... * @return {Server} * @api public */ listen(...args) { debug("listen"); const server = http.createServer(this.callback()); return server.listen(...args); } /** * Return a request handler callback * for node's native http server. * * @return {Function} * @api public */ callback () { const fn = compose(this.middleware) if (!this.listenerCount('error')) this.on('error', this.onerror) const handleRequest = (req, res) => { const ctx = this.createContext(req, res) return this.handleRequest(ctx, fn) } return handleRequest } /** * Handle request in callback. * * @api private */ handleRequest (ctx, fnMiddleware) { const res = ctx.res res.statusCode = 404 const onerror = err => ctx.onerror(err) const handleResponse = () => respond(ctx) onFinished(res, onerror) return fnMiddleware(ctx).then(handleResponse).catch(onerror) }};中间件实现原理中间件应用例子 ...

May 4, 2022 · 4 min · jiezi

关于koa:koa搭建nodejs项目并注册接口

应用nodejs注册接口逻辑解决会比较复杂,间接通过express或者koa可能简化开发流程,这里记录用koa来搭建nodejs我的项目并注册接口,对koa不太熟悉的话能够参考这一篇。让nodejs开启服务更简略--koa篇 我的项目构造我的项目整体构造如下,将不同性能的文件按模块划分,使得代码逻辑更为清晰 node_modules // 装置的包src // 本人编码的局部 app // 注册的app constants // 定义常量 controller // 注册接口所应用到的办法 middleware // 解决数据的中间件 router // 路由 service // 定义sql语句 utils // 解决数据的办法 main.js // 入口.env // 放到环境变量的配置文件 package-lock.json // 包的依赖关系package.json // 须要装置哪些包注册appkoa中所有的操作都须要通过注册的这个app对象来实现,先在 app/index.js 中注册并导出 const Koa = require('koa')const app = new Koa() module.exports = apphttp开启服务我的项目根目录建设 main.js文件,引入app对象,开启http服务 const app = require('./app')const { APP_PORT } = require('./app/config')app.listen(APP_PORT, ()=>{ console.log('开启服务啦')})账号密码、端口号等信息间接写在文件中是不平安的,上传或共享我的项目的时候容易泄露,所以保留到不影响我的项目的的文件当中,根目录中新增 .env 文件,应用 dotenv 将 .env 文件中的配置项注册到环境变量中 APP_PORT=8000app文件夹中新增config.js文件用于保留配置信息,避免随便更改 const dotenv = require('dotenv')dotenv.config();module.exports = { APP_PORT} = process.env启动 main.js就曾经能够监听8000端口了,因为没有对申请做出响应,所以此时拜访8000,只能返回 Not Find ...

September 19, 2021 · 1 min · jiezi

关于koa:koa项目热更新和es6语法兼容

热更新很简略,须要应用到nodemon。 用法如下: npm i nodemon -D// package.json"scripts": { "start": "nodemon src/index.js" },而后运行npm run start es语法反对这里须要用到如下插件: webpack & webpack-cliclean-webpack-plugin: 革除dist目录webpack-node-externals: 不对node_modules进行解决@babel/core: es6反对@babel/node: 调试应用@babel/preset-env: 新语法的反对babel-loadercross-env: 环境变量的解决 webpack.config.js 配置如下: const path = require('path');var nodeExternals = require('webpack-node-externals');const { CleanWebpackPlugin } = require('clean-webpack-plugin');const webpackconfig = { target: 'node', mode: 'development', entry: { server: path.join(__dirname, 'src/index.js') }, devtool: 'eval-source-map', output: { filename: '[name].bundle.js', path: path.join(__dirname, './dist') }, module: { rules: [ { test: /\/(js|jsx)$/, use: { loader: 'babel-loader' }, exclude: [path.join(__dirname, '/node_modules')] } ], }, externals: [nodeExternals()], plugins: [ new CleanWebpackPlugin() ], node: { console: true, global: true, process: true, Buffer: true, __filename: true, __dirname: true, setImmediate: true, path: true } }module.exports = webpackconfig.babelrc 配置如下: ...

April 23, 2021 · 1 min · jiezi

关于koa:Koa初探一

koa初探(一)当咱们在学习某个新的知识点的时候,大部分状况下都是关上百度,或者谷歌,而后在搜寻框中输出某个名词,这个时候大抵在搜寻项的后面几个,咱们就能够看到一个对于这个货色的官网文档,而后点进去,根本就是对于这个货色的所有api和应用办法啦~ 当然,一开始的时候我兴许会饶有兴致地依据官网给出的例子在本人的电脑上跑一跑,看一看。很快,当我把例子跑完,我仿佛明确了这到底是怎么一回事的时候,我想要持续摸索,于是就看到了无穷无尽的api,看了两眼,困意????袭来~~~ 讲了一堆废话,该进入正题啦~ 官网给了这样一个例子:大抵解读一下,就是new进去一个koa对象,利用它来创立一个服务器,并且应用了3000端口来监听状态。两头对于申请的解决就是有一堆所谓middleware(中间件)来解决,假如咱们的koa服务器就是一个盒子,丢一个申请(request)进入盒子,而后就能够从盒子中失去一个通过了一系列解决的响应后果(response)。 const Koa = require('koa');const app = new Koa();// loggerapp.use(async (ctx, next) => { await next(); const rt = ctx.response.get('X-Response-Time'); console.log(`${ctx.method} ${ctx.url} - ${rt}`);});// x-response-timeapp.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set('X-Response-Time', `${ms}ms`);});// responseapp.use(async ctx => { ctx.body = 'Hello World';});app.listen(3000);以上这段代码的调用程序对于只看官网的文字描述还是有点不够清晰的。这看起来就像咱们在写作业的时候,忽然脑海中呈现了奇思妙想,于是在某个工夫点就跑出去游玩了,玩到兴致快没有的时候,还记得作业没有写完,而后就又跑回去,从方才停留的作业的中央又持续往下写,当然在玩的过程中还会有其余更好玩的,于是不停地跑出去,不过这个贪玩的小孩至多还记得以后玩完了还会跑回去把上一次没有玩完的事件做完。

January 17, 2021 · 1 min · jiezi

关于koa:Koa入门教程6初探源码

本文纲要express与koa的比照Koa1 内核源码简要介绍 Koa2 内核与 koa1 的区别理解 Koa 中 http 协商缓存的实现机制koa-router 源码koa-view 源码express本文咱们不解说express的源码。然而express的实现机制对于咱们理解 TJ 在设计框架时的思路有肯定的参考意义。express 实现了一个相似于流的申请处理过程,其源码比 Koa 还要略微简单一点(次要是其内置了Router概念来实现路由)。如果对 express 的源码感兴趣的能够参考这两篇文章: 从express源码中探析其路由机制 NodeJS框架之Express4.x源码剖析 exporess和koa都是用来对http申请进行 接管、解决、响应。在这个过程中,express和koa都有提供中间件的能力来对申请和响应进行串联。同时要提供一个封装好的 执行上下文 来串联中间件。 因而,koa和express就是把这些http解决能力打包在一起的一个残缺的后端利用框架。波及到了一个申请解决的残缺流程,其中蕴含了这些常识概念:Application、Request、Response、COntext、Session、Cookie。 express跟koa的区别是,express应用的ES5时代的语言能力(没有应用generator和async),因而express实现的中间件机制是传统的串行的流式运行(从第一个运行到最初一个后输入响应);而koa应用了generator或async从而实现了一种洋葱模型的中间件机制,所谓洋葱模型实际上就是中间件函数在运行过程中能够停下来,把执行权交给前面的中间件,等到适合的机会再回到函数内持续往下执行 Koa1 内核本文咱们还是次要剖析 Koa1 的代码(因为 Generator 比 async 要绕一些难一些),我看的代码是基于 Koa 1.6.0。 对于 Koa1 来说,其实现是基于 ES6 的 Generator 函数。Generator 给了咱们用同步代码编写异步的可能,他能够让程序执行流 流向 下方,在异步完结之后再返回之前的中央执行。Generator 就像一个迭代器,能够通过它的 next 办法一直去迭代来实现函数的步进式执行。对于 Generator 函数解决异步问题的学习能够参考 阮一峰的 ES6 教程 Generator函数与异步 Koa 内核只有 1千 行左右的代码。共蕴含 4 个文件: application.jsrequest.jsresponse.jscontext.js咱们从 package.json 中能够看到 Koa 的主入口是 lib/application.js. 这个入口做的事件便是导出了一个 Application 的class类。(能够看到 Koa 的实现相比express曾经比拟面向对象了) ...

November 13, 2020 · 5 min · jiezi

关于koa:Koa入门教程5升级为Koa2

先来翻译一波 官网的 migration指南: 从 Koa v1.x 迁徙到 v2.x新的中间件函数签名Koa v2 引入了一个新的中间件签名 老的中间件签名形式 (v1.x) 将在v3版本删除 新的 middleware 签名如下: // 用async箭头函数app.use(async (ctx, next) => { try { await next() // next不再是generator迭代器,而是一个asycn函数了 } catch (err) { ctx.body = { message: err.message } ctx.status = err.status || 500 }})app.use(async ctx => { const user = await User.getById(this.session.userid) // 用await代替yield ctx.body = user //用ctx变量代替this})你不是只能用async函数-你只须要保障传递一个返回promise的函数一个一般返回promise的函数照样能够工作。 这个中间件函数签名改成了传入一个严格的ctx参数 ( Context 对象),而不是用this来拜访以后申请上下文. 这个扭转使得 koa 更能兼容es6箭头函数捕捉this. 在v2.x中应用v1.x的中间件Koa v2.x 在调用app.use的时候能够兼容generator的middleware,它应用koa-convert.来干这件事。然而还是倡议你连忙迁徙这些v1的中间件。 ...

November 13, 2020 · 2 min · jiezi

关于koa:Koa入门教程4开发并部署todolist应用

我的项目简介todo-list 利用是一个罕用的练手利用。他次要蕴含以下几个性能: input框增加工作,回车后增加到工作列表点击工作列表条目,或点击条目后的删除按钮,能够删除一个todo我的项目点击 实现 按钮,能够把某个条目标记为已实现这个我的项目咱们采纳前后端齐全拆散的形式来开发 前端技术栈: Vue2.x、Axios、Vue-Router、Vuex 、css3 Flex后端技术栈: Koa1.x 、 koa-body-parser 、 koa-logger 、 koa-json 我的项目目录构造组织在前后端拆散的我的项目中,我倡议采纳前端目录驱动的形式。即优先以前端架构进行组织,在前端目录架构中搁置一个后端目录 用于api服务并同时作为前端编译后果的托管容器进行部署。 起因在于,后端目录个别是用于部署的,而前端利用如果不是独自部署的话 则须要搁置到后端目录中一起托管。 基于前端编译后输入的方便性,把后端目录放在前端目录外面,build时就比拟不便了。 当然,你如果保持本人的目录哲学,也无可非议。 初始化在我的项目根目录下,先应用 vue-cli 工具初始化一个基于 webpack 脚手架的 Vue2.x我的项目。 npm i vue-cli -g# 进入我的项目目录根vue init webpack .npm install此时 前端目录和文件曾经创立结束。前端依赖也曾经装置。基于前端的 package.json 根底之上,咱们再 在我的项目根目录下执行 npm i koa-logger未完待续

November 2, 2020 · 1 min · jiezi

关于koa:Koa入门教程3错误和异常处理

Node.js 中的异样Node.js 跟 JavaScript一样,同步代码中的异样咱们能够通过 try catch 来捕捉. 异步回调异样但异步代码呢? 咱们来看一个 http server 启动的代码,这个也是个典型的异步代码。 const http = require('http')try { const server = http.createServer(function (req, res) { console.log('来了') throw new Error('hi') res.end('helo') }) server.listen(3002)}catch (err) { console.log('出错了')}咱们发现异步代码的异样无奈间接捕捉。这会导致 Node.js 过程退出。最显著的就是 web server 间接挂掉了。 异步代码也有解决办法,咱们间接把try catch 写在异步代码的回调外面: const http = require('http')try { const server = http.createServer(function (req, res) { try { throw new Error('hi') } catch (err) { console.log('出错了') } res.end('helo') }) server.listen(3002)}catch (err) { console.log('出错了')}这样也能catch到谬误。 ...

November 1, 2020 · 2 min · jiezi

关于koa:Koa入门教程2常用中间件

中间件执行流程中间件的执行流程,能够用上面这张图片来活泼的阐明(图片应用了 Koa 2 的 async 语法): 对于 Koa 1 来说也相似,只是 async 函数换作 generator 函数,await 换作 yield 关键字。 对于前端程序员,能够把 yield 之前的代码认为是捕捉阶段,yield 之后的认为的冒泡阶段,从而了解多个中间件之间代码的执行流程。 路由中间件路由个别属于业务代码,咱们个别放在其余根底中间件之后来注册。路由的基本原理就是判断 url path, 而后决定是否执行某个中间件逻辑。 简略实现能够相似这样: const Koa = require('koa')const app = new Koa()app.use(function *(next) { if (this.path === '/home') { this.body = '首页' } else { yield next } console.log('这里会执行哦')})app.use(function *(next) { if (this.path === '/admin') { this.body = '治理端' }})app.listen(3000)能够看到,对于不合乎本中间件的申请 path, 就间接抛弃,并去执行下一个中间件。如果所有中间件都匹配不到,会返回 404(Koa 默认行为). ...

October 30, 2020 · 3 min · jiezi

关于koa:Koa入门教程1开端

前言Koa 是一个粗劣玲珑的基于 Node.js 的 Web 框架。目前有 1.x 和 2.x 2 个大的版本 其中 2.x 版本应用了 Node.js v7.6.0 之后反对的 async await 语法糖,提供了更优雅的异步编程模式。 Koa 有如下特点: 内核精简,不内置中间件. 玲珑但富裕表现力。相似栈的形式运行中间件,Koa 调用上游,而后堆栈开展再将管制再流回上游。简略实用实用async await或generator防止了callback hell优雅的异样捕捉Koa 通过下面的机制防止了以往 connect 等实现的一些问题,例如要实现一个耗时统计时须要将 startTime 层层传递到开端中间件。Koa 与 express 和 connect 的差异如下: 为了一探 Koa 的全貌,咱们基于 Koa 1.x 的版本来开始学习之旅(次要是为了学习generator,其余方面1和2其实原理是一样的)。前面打算的教程如下: 开始。装置和启动罕用中间件错误处理和最佳实际开发并部署一个 todo-list 利用降级为 Koa2初探 Koa 源码装置应用 Koa 搭建一个 Web 利用是极其简略的。Koa 模块裸露了一个 Application 的 class 给 Web 开发者,咱们只需实例化这个 Application,并给它注入适当的 申请和响应解决逻辑. 实际上,整个 Koa 利用的开发模式就是如此简略。 ...

October 11, 2020 · 2 min · jiezi

关于koa:优雅-koa处理异常

一个良好的编码习惯必然离不开异样解决,本文将会介绍如何在koa框架上面如何抛出谬误,并对立解决返回异样。 失常错误处理koa是一个优良的NodeJs web框架,在咱们开发web我的项目的时候,防止不了任何错误处理,包含http谬误以及自定义的业务逻辑解决。在Node.js 中,抛出谬误如下 if(someCondition){ throw Error("Error");}Http错误处理这里应用ctx.throw(400)的形式,抛出http谬误,同时返回一些信息。 ctx.status = 400ctx.body = { msg: "some params is invalid"}此时既返回了状态码,又返回了相干的错误信息。 业务逻辑错误处理如果须要开发Restful API server,这个时候须要定义若干的业务逻辑错误代码,像上面这样的 code码阐明0success-1server error4001token 过期这个时候,就须要在业务层中进行解决。 router.get("/", (ctx, next) => { if(tokenExpire(token)){ const errcode = ERROR_CODE.TOKEN_EXPIRED; ctx.body = { errcode, msg: ERROR_MSG[errcode] } return }})这里,就返回了相干的errcode,通过errcode的形式返回了相干的错误代码 全局捕捉异样解决这里在koa里,全局捕捉异样,这里应用中间件的形式,确保异样能够捕捉到在middlewares建设一个catcherror中间件,达到捕捉到异样的形式 // middlewares/catcherror.jsconst catchError = async(ctx, next) => { try{ await next(); }catch(error){ if(error.errorCode){ console.log("捕捉到异样") return ctx.body = errror.msg; } }}module.exports = catchError这样定义一个中间件,在中间件进行相干的捕捉,确保捕捉到相干的异样,并把这个中间件进行导出。 放在next外面,如果next程序出现异常,就能够实现在中间件进行相干的捕捉。 ...

July 22, 2020 · 1 min · jiezi

基于Koa和React搭建的基础项目框架

引言在个人开发过程中,因为个人主要是做前端方向的,所以在使用后端的时候更加偏向于Node这一块,目前Node比较热门的框架主要有Express和Koa两款,当然还有各大厂商基于这两款框架的二次封装框架比如Eggjs,Thinkjs。但是个人使用之后觉得并不适合个人开发,主要是框架封装太多可扩展性以及自由度不高,基础框架过于笨重即使很小的项目依赖也很多。由此引发自己弄一套基础的开发框架(可能使用框架这个词语不太合适)。目前项目主要是基于Koa和React实现。后端主要使用Koa,Knex(Sql Builder)主要支持Mysql,Redis等常见数据库,支持controller,service自动加载。前端基于React使用React hooks,React Context等较为新型的技术,加入了ant design,less等功能。 项目地址>>>https://github.com/southorange1228/so.fullstack.system目前项目处于初期开发阶段,可能存在很多问题和不足的地方,希望各位大佬能提供意见和帮助。如有任何问题请提issue或者邮件至15280970040@163.com。最后厚颜无耻的求一个star

October 14, 2019 · 1 min · jiezi

搭建一个好用的API-Mock服务

上篇文章讲述了怎么用Node实现一个API服务 现在开始讲述如何搭建一个好用的API Mock服务 达到的效果: 在开发环境中就可以在url后面添加?ismock=1参数来实现数据mock,(没有该参数就访问正常数据),且不会对测试环境和生产环境造成任何影响 实现步骤: 通过Webpack设置代理。 //webpack.config.js proxy: { '/mock': { target: 'mock', changeOrigin:true, pathRewrite: { '^/mock': '' } } }拦截请求(比如Axios自带的拦截器) 判断url参数(如?ismock=1)判断当前环境(如process.env.NODE_ENV == 'development')添加baseUrl = /mock在webpack的压缩处理中删除不可达代码(见webpack配置表)源码地址

August 20, 2019 · 1 min · jiezi

serverless在微店node领域的探索应用

背景目前微店中台团队为了满足公司大部分产品、运营以及部分后端开发人员的尝鲜和试错的需求,提供了一套基于图形化搭建的服务端接口交付方案,利用该方案及提供的系统可生成一副包含运行时环境定义可立即运行的工程代码,最后,通过 “某种serverless平台” 实现生成后代码的部署、CI、运行、反向代理、进程守、日志上报、进程分组扩容等功能。 这样,产品和运营人员可基于此种方式搭建的接口配合常用的cms系统实现简单查询需求如活动大促的自主“研发”上线,代码的可靠性、稳定性由中台研发侧提供的“某种serverless平台”保障,有效支撑了多个业务快速上线,节省后端开发人员的人力与硬件资源的开销(大多数需求下,nodejs业务对虚拟机的资源开销小于java业务)。 接口搭建系统此处并不讲解接口搭建系统的原理与实现,只说明其与上文提到的 “某种serverless平台” 的关系。 ](https://si.geilicdn.com/viewm... 这是系统的全貌,部分细节由于敏感信息而省略。平台使用方可基于每个功能组件搭建出一套复杂的业务流,在搭建阶段,提供在线debug和日志功能,可用于排错;在部署CI阶段,可集成不同的运行时平台,既可以是自主实现的运行时,也可是第三方云平台;在运行阶段,通过使用agentool工具实时监控当前服务的性能,并可通过traceId一览请求在各系统的全貌。 serverless方案本节以资源隔离粒度为度量,介绍了我对三种serverless方案的取舍以及最终为何选择了隔离程度更高的kubeless云平台。 基于函数隔离的Parse Server方案Parse Server提供了基础功能:基于类与对象的权限控制、基于document的nosql存储、简易的用户身份认证、基于hook的自定义逻辑等,但经过笔者的调查与论证,最终并没有采用类似单进程下基于函数隔离的Parse Server及类似方案,这有几点原因: Parse Server方案很重,额外引入了非常多不需要的功能,如权限控制、认证、存储等服务隔离级别低,多个服务在一个进程运行,多个服务会在libuv层互相抢占CPU,互相影响对方的业务处理水平扩容难度大,针对单个服务的扩容无法做到底层基于express框架,无法满足运行时接口调用链路的trace追踪当多个服务同时引入不同的资源如db、es或者服务创建的对象足够多时,会存在Parse Server主进程溢出的风险,毕竟64位机的node堆内存是有1.4GB上限的,尽管这个上限是可配置的Parser Server发布的接口需通过其client调用,这在公司商用情况下需要做许多额外的配置才能满足Parse Server官网基于进程隔离的super-agent方案为了解决多个服务抢占libuv的问题,我选择了自主研发的 super-agent方案,通过其名称便可知它是一个超级代理,但它不仅是代理,还是一个具有极简功能且可靠的发布系统和运行时容器;它是一个分布式应用,节点间功能划分明确;它同时提供实时调试功能。 super-agent是一个多角色分布式系统,它即可以看做node容器,也可看成serverless的node实现,它以进程为粒度管理服务。它分为“协调者”和“参与者”,协调者实现 应用CI部署、启动、进程维护与管理和反向代理功能,参与者实现 业务请求代理、接受协调者调度。 在super-agent架构中,端口是区分服务的唯一标识,端口对客户端而言是透明的,这层端口资源的隔离由super-agent来做掉,因此多个服务可避免在libuv层的互相竞争,提供水平扩容的可能性。 反向代理super-agent最核心的功能在于反向代理。由于每个服务都被包装成有单独端口的独立HTTP应用,因此当用户请求流量经过前端转发层后进入super-agent,由其根据相关规则做二次转发,目前支持基于 “路径、端口”规则的转发。 部署后端应用部署需要进行 “优雅降级、流量摘除、健康检查、应用初始化完毕检查、流量导入、所有参与节点的部署状态查询” 等步骤,需要妥善处理;同时,协调者负责协调众多参与节点全部完成部署操作,这是一个分布式事务,需要做好事务出错后的相关业务补偿。 关于流量上图并未画出节点角色的区别,实际上只有参与者节点才真正接受用户请求。 协调者流量全部来自于内部系统,包括接受 “接口搭建系统”调用或者其他系统实现的dashboard服务;同时其会向参与者发送相关信令信息,如部署、扩容、下线、debug等。 参与者流量来自于内部系统和外部流量,其中大部分来自于外部流量。内部流量则承载协调者的信令信息。 水平扩容服务的水平扩容重要性不言而喻。super-agent方案中,协调者负责管理服务的扩容与逻辑分组。 此处的服务是通过服务搭建平台通过拖拽生成的nodejs代码,它是一个包含复杂业务逻辑的函数,可以是多文件。具体的,super-agent通过将该服务包装成一个HTTP服务在单独的进程中执行。因此,如果要对服务进行水平扩容,提供多种策略的选择: 默认每台虚拟机或物理机一个服务进程,总体上N个机器N个服务进程扩容时默认每台机器再fork一个服务进程,N机器2*N个服务进程为了更充分利用资源,可为每台机器划分为逻辑组,同时选择在某几个组的机器单独扩容这些策略对下游应用透明,只需选择相关策略即可。 水平扩容的缺点:每台虚拟机或物理机资源有上限,一个正常的node进程最多消耗1.4GB内存,计算资源共享,在一台8C16G的虚拟机上,最多可同时运行16个服务。及时通过分组扩容的方式,每当扩展新的虚拟机或物理机,都需要由super-agent根据分组信息实现进程守护,同时每次服务CI部署也同样如此。运维管理上需要配合非常清晰明了的dashboard后台才能快速定位问题,这点在多服务的问题上尤其突出。 在线调试super-agent提供消息机制,由搭建平台中组件开发人员使用提供的serverless-toolkit工具debug相关逻辑,最终可在super-agent的协调者后台查看实时debug结果。 总结super-agent是符合常规的基于业务进程隔离的解决方案,它有效的支撑了微店的几个活动及产品,虽然峰值QPS不高(100左右),但它也论证了super-agent的稳定性及可靠性(线上无事故,服务无重启,平稳升级)。 但是,super-agent仍然存在几个问题,它让我们不得不另觅他法: 日常运维困难,需要开发一系列后台系统辅助运维,这需要不少人力成本这是一个典型的一机多应用场景,当部署super-agent时会对运行其上的服务有所影响(重启),尽管这个影响并不影响用户访问(此时流量已摘除),但仍然是个风险点水平扩容实现繁琐,当下掉某几台参与节点时会带来不少影响基于内核namespace隔离的kubeless方案基于kubeless的方案则是隔离最为彻底的解决方法,kubeless是建立在K8s之上的serverless框架,因此它可以利用K8s实现一些非常有用的特性: 敏捷构建 - 能够基于用户提交的源码迅速构建可执行的函数,简化部署流程;灵活触发 - 能够方便地基于各类事件触发函数的执行,并能方便快捷地集成新的事件源;自动伸缩 - 能够根据业务需求,自动完成扩容缩容,无须人工干预。其中,自动伸缩则解决了 super-agent 的痛点。 kubeless中与我们紧密相关的有两个概念:“function和runtime” ,function是一个统称,它包括运行时代码、依赖以及其他配置文件;runtime则定义运行时依赖的环境,是一个docker镜像。 若要接入kubeless平台,我们需要解决如下几个问题: 开发自定义运行时,满足商用需求,如trace、日志分片、上报采集自定义构建镜像,实现ts编译基于yaml的function创建、更新、删除流程探索,并自动化function部署,包括流量摘除、流量导入、业务健康检查中间件日志、业务日志、trace日志隔离与挂载function运行时用户权限问题水平扩容探索与尝试资源申请规范指定与部署规范约定因此,前进的道路仍然很曲折,而且很多需求需要自己从源码上去寻找解决方法。 一些说明kubeless实现的serverless体系中,function所在pod中的所有容器共享网络和存储namespace,但是默认外网是不可访问k8s集群的所有pods,因此需要通过一层代理实现请求的转发,这就是“Service”。Service负责服务发现及转发(iptables四层),因此在Kubeless或者K8s中不会直接通过pod IP来访问服务,而是通过Service转发四层流量完成。Service有K8s分配的cluserIp,clusterIp是集群内部虚拟IP,无法被外部寻址,而是通过Kube-Proxy在容器网络之上又抽象了一层虚拟网络,Kube-Proxy负责Service的路由与转发(关于kube-proxy细节,请看参考资料)。 Service后端对应是一个或多个pods,这些pods中的一个容器则运行相同的业务代码。那么流量是如何路由至Service上来呢?这就涉及到Service的“发布”,常用的是Ingress。Ingress包括HTTP代理服务器和ingress controller,代理服务器负责将请求按照规则路由至对应Service,这层需要Kube-Proxy实现虚拟网络的路由;ingress controller负责从K8s API拉取最新的HTTP匹配规则。](https://si.geilicdn.com/viewm... 问题解决自定义镜像:这里的镜像包括两部分:构建镜像和运行时镜像。运行时镜像需要解决宿主代码的鲁棒性,以及提供 livenessProbe、readinessProbe、metric接口实现;构建镜像则负责构建阶段的操作,如编译、依赖安装、环境变量注入等操作。具体编写可参考 implement runtime images)。构建镜像参考1关于function的CRUD操作,笔者先通过命令行走通整个流程后,又切换成基于yaml的配置文件启动,使用yaml启动好处在于:a,可使用kubeless自带的流量导入与摘除策略 b,水平扩容简单 c,命令简单 d,配置文件模板化,自动化部署策略由于涉及到业务特点,此处不详细介绍日志的挂载是必要的,否则pod一旦重启,容器内的所有日志全部丢失。尽管会存在日志收集的操作,可是日志收集进程大多数都是异步进行,因此会存在丢失日志的情况。因此,必须通过挂载volumn的形式在K8s node上映射文件。但在这过程中会出现权限的问题,这在下一点说明权限问题在于kubeless将function的执行权限设置为非root。这是安全且符合常理的设定,但有时function需要root权限,这需要修改K8s的security context配置,需要谨慎处理水平扩容基于K8s的HPA组件完成,目前支持基于CPU和QPS等指标进行扩容,目前笔者并未专门测试这项内容,因为它足够可靠资源申请的指定需要符合每个公司的实际情况以及业务特点,以node技术栈为例,pod中每个容器设置1C2GB的内存符合实际情况;而至于部署规范,则要兼顾运行时容器的特点,合理配置K8s的node与pod、function的对应关系总结运行在kubeless 中的函数和运行在super-agent的代码没有什么不同,可是周边的环境准备可大大不同。为了让kubeless中的function可以接入公司内部中间件服务,笔者费了不少功夫,主要集中在日志及收集部分。好在事在人为,解决的办法总是多于失败的方法。 ...

August 20, 2019 · 1 min · jiezi

基于koa2的前后端管理系统

koa2koa2框架,mongodb作为数据库,Es6/7语法编写,babel编译ES语法。 增加ts语法支持,进行ing 前后端分离,后台管理系统, Koa后端 系统目前包含 文章发布管理系统、标签系统、评论系统、用户系统,四大模块 技术栈使用koa+mongoose 开发; 使用koa2.0作为开发框架mongoose作为数据库,保存数据使用jwt进行token的生成和校验通过Es6语法进行项目编写文件结构采用MC拆分babel-register编译Es6/7/8esLint语法规则server下为目录结构:.|——server|  |—— config 全局配置| |—— constant 常量|  |   |—— index.js             暴露全部常量| | └── user.js 用户常量|  |—— controller 对应路由的逻辑处理|  |   |—— article.js             文章 控制器 接口| | └── comment.js 评论 控制器 接口| | └── tag.js 标签 控制器 接口| | └── user.js 用户 控制器 接口| |—— middleware 路由中间件|  |—— model mongoose数据库模型| |   |—— ArticleModel.js        文章模型| |   |—— TagModel.js            标签模型| |   └── UserModel.js           用户模型| | └── CommentModel.js 评论模型| |—— mongoose 数据库方法暴露| |—— public 静态资源目录| |—— router 路由文件| |   |—— index.js               路由| |   |—— api.js                 api路由| |   └── user.js                user路由| |—— utils                     公共方法| |—— app.js app入口文件调试运行$ yarn install <!-- 需要开启管理权限设置 -->$ mongod //开启mongoDB$ npm run dev //本地测试服务API接口后端 接口文档 ...

July 26, 2019 · 1 min · jiezi

借助URLOS快速安装nodejs环境

环境需求最低硬件配置:1核CPU,1G内存(1+1)提示:如果你的应用较多,而主机节点的硬件配置较低,建议在部署节点时开通虚拟虚拟内存;生产环境建议使用2G或以上内存;推荐安装系统:Ubuntu-16.04、Ubuntu-18.04、CentOS7.X、Debian9X的64位的纯净的操作系统;URLOS安装curl -LO www.urlos.com/iu && sh iunodejs环境安装流程登录URLOS系统后台,在应用市场中搜索“nodejs”,找到之后,直接点击安装按钮 填写服务名称、选择运行节点、服务端口、选择智能部署 填写ssh密码(这里的密码是root密码) 然后点击“提交”按钮,等待部署完成;

July 15, 2019 · 1 min · jiezi

基于WebpackTypeScriptKoa的环境配置

TypeScript是一种开源编程语言,在软件开发社区中越来越受欢迎。TypeScript带来了可选的静态类型检查以及最新的ECMAScript特性。作为Javascript的超集,它的类型系统通过在键入时报告错误来加速和保障我们的开发,同时越来越多对的库或框架提供的types文件能够让这些库/框架的API一目了然。我对这门语言垂涎已久,但是迟迟无法找到练手的地方。很显然的,个人博客又一次的成了我的学习试验田????。我放弃了上一版Vue单页面的框架,改为基于TypeScript/Koa的多页面应用。在改造的过程中,我试着将服务端(Koa)代码以及前端代码都使用TypeScript来开发,中间使用了webpack作为开发时前后端的桥梁。 目录结构.├── .babelrc├── bin│ ├── dev.server.ts│ ├── pm2.json│ └── app.ts├── config # 配置目录│ ├── dev.ts│ └── prod.ts├── nodemon.json├── package.json├── postcss.config.js├── scripts│ └── webpack.config.js├── src # 源码│ ├── assets # 静态资源│ │ ├── imgs│ │ ├── scss│ │ └── ts│ ├── entries # webpack入口│ │ ├── blog.ts│ │ └── index.ts│ └── views # 模板(文件名与入口一一对应)│ ├── blog.html│ ├── index.html│ └── layout # 模板布局│ ├── footer.html│ └── header.html├── server # 服务端│ ├── app.ts│ └── middleware│ └── webpack-dev-middleware.ts├── test # 单元测试│ └── .gitkeep ├── tsconfig.front.json└── tsconfig.json安装项目依赖npm i --save koa koa-{router,bodyparser,static,ejs}npm i -D typescript ts-node nodemon @types/{node,koa,koa-router,koa-bodyparser}开发环境(development)流程 ...

July 2, 2019 · 3 min · jiezi

nodejs的Web开发框架的选择

node.js的Web开发框架的选择?这个问题貌似在其它的后端开发领域不存在。没错,我说的就是隔壁的Java。我要是写java的应用,可以毫不犹豫的选择Spring。但是node可选择的余地多的多。 现有node服务端框架1. Express、Koaexpress框架肯定不用说了,写node服务这块的同学肯定是非常熟悉的框架了。我早期的时候也是express的粉丝。 优点:express的框架结构非常的简单。经过短暂的学习就可以用来开发一个项目。非常适合作为node新手的入门框架。 缺点:开发阶段:Express的缺点也很明显,由于结构简单,自由度高。每个人会有不同的文件编排方式。前期设计阶段需要人工的把项目约定做好。但是团队来新人了,又要重新学习项目约定,无形中增加了学习成本。说到底还是缺乏项目的工程化约束。在项目的开发初期需要自己手工的搭建一些通用的脚手架代码,来方便的之后的开发工作。开发流程会拖的比较长。 运维阶段:由于node单进程,js主线程运行的机制。如果在js主线程中没有做好错误的处理。会导致进程意外退出的问题。这在项目运行阶段是不可接受的问题。需要进程守护的机制来保证程序的健壮性。Express和Koa需要依赖第三方的工具来实现。如PM2。讲道理这些功能应该是一个web开发框架应该具备的基础功能。 总结:不管是Express还是Koa框架。还是处于比较简单的基于http模块的封装。在Reuest和Response这两个对象基础上进行扩展开发。我们业务开发团队需要的是稳定、快速的开发框架。实际开发中往往需要在Express和Koa的基础上封装大量的代码,来适应不同的业务场景,这对追求快速开发的互联网行业是不受欢迎的。 2. Egg.js我在2018年3月份开始接触egg框架。发现这是一个具备较完善功能的web开发框架。 优点:方便、好用、少写很多的脚手架级别的代码。专注于业务逻辑的开发。内建插件机制,兼容koa插件。约定大于配置。内置多进程管理。阿里巴巴开源。文档是中文的。估计没点自虐倾向的同学一般都会选择母语版本的文档来看吧。 缺点:由于目前的使用层面还不够深入。除了对应用配置方式的不太满意外,没有发现大的开发痛点。项目开发实践下来,开发效率杠杠的。 总结:估计写到这里,应该能看出我对egg框架的喜爱程度了。那么下面学习一下egg入门。

June 30, 2019 · 1 min · jiezi

Koa源码浅析

Koa源码十分精简,只有不到2k行的代码,总共由4个模块文件组成,非常适合我们来学习。 参考代码: learn-koa2 我们先来看段原生Node实现Server服务器的代码: const http = require('http');const server = http.createServer((req, res) => { res.writeHead(200); res.end('hello world');});server.listen(3000, () => { console.log('server start at 3000');});非常简单的几行代码,就实现了一个服务器Server。createServer方法接收的callback回调函数,可以对每次请求的req res对象进行各种操作,最后返回结果。不过弊端也很明显,callback函数非常容易随着业务逻辑的复杂也变得臃肿,即使把callback函数拆分成各个小函数,也会在繁杂的异步回调中渐渐失去对整个流程的把控。 另外,Node原生提供的一些API,有时也会让开发者疑惑: res.statusCode = 200;res.writeHead(200);修改res的属性或者调用res的方法都可以改变http状态码,这在多人协作的项目中,很容易产生不同的代码风格。 我们再来看段Koa实现Server: const Koa = require('koa');const app = new Koa();app.use(async (ctx, next) => { console.log('1-start'); await next(); console.log('1-end');});app.use(async (ctx, next) => { console.log('2-start'); ctx.status = 200; ctx.body = 'Hello World'; console.log('2-end');});app.listen(3000);// 最后输出内容:// 1-start// 2-start// 2-end// 1-endKoa使用了中间件的概念来完成对一个http请求的处理,同时,Koa采用了async和await的语法使得异步流程可以更好的控制。ctx执行上下文代理了原生的res和req,这让开发者避免接触底层,而是通过代理访问和设置属性。 看完两者的对比后,我们应该会有几个疑惑: ctx.status为什么就可以直接设置状态码了,不是根本没看到res对象吗?中间件中的next到底是啥?为什么执行next就进入了下一个中间件?所有中间件执行完成后,为什么可以再次返回原来的中间件(洋葱模型)?现在让我们带着疑惑,进行源码解读,同时自己实现一个简易版的Koa吧! ...

June 28, 2019 · 7 min · jiezi

适合初学者的koa2mongodb初体验

前言     笔者的前端开发已经有些时日了,对于node一直保留着最初的恐惧,倘若一座不可跨越的高山,思前想后终于迈出最后一步,踏入了开拓自己视野的新视界,希望在看这篇文章的你可以一起跟我动手尝试。     如果你是个急性子,我就提供一波传送门 github:https://github.com/tenggouwa/...      你可以先star,再拉代码,慢慢的看这篇文章。 KOAnext generation web framework for node.js面向node.js的下一代web框架。 由Express团队打造,特点:优雅、简洁、灵活、体积小。几乎所有功能都需要通过中间件实现。 环境搭建node node官方下载地址: https://nodejs.org/zh-cn/down...mongodb mac下mongodb安装教程: https://www.jianshu.com/p/724...windows下mongodb安装教程: https://blog.csdn.net/zhongka...robomongo(mongodb数据库可视化--免费): https://robomongo.org/download本地安装nodemon nodemon会监听你的代码,当有变动的时候自动帮你重启项目(好东西)npm: https://www.npmjs.com/package...yarn(选装)---代替npm/cnpmhomebrew(选装)---包版本管理工具Hello World!!!创建目录 mkdir node-app && cd node-appnpm inityarn add koa -Stouch app.js在编辑器中打开app.js并输入以下代码 const Koa = require('koa');const app = new Koa(); app.use(async ctx => { // ctx.body 即服务端响应的数据 await ctx.body = 'Hello world!!!';})// 监听端口、启动程序app.listen(3000, err => { if (err) throw err; console.log('runing at 3000'); })启动app.js node app 或 nodemon app本地访问localhost:3000got it!!!!KoaRouter安装koa-router ...

June 27, 2019 · 2 min · jiezi

我用Nodejs的Koa框架搭建了一个静态站点

缘起我用Node.js的Koa2框架搭建了一个静态站点,当然这个站点只是部署在我自己的电脑上,主要用来做一些测试:比如写个小页面,尝试下新技术。以前我在自己的电脑上搭建过很多类似的静态站点,因为用过一年的php,所以我之前都是用php+nginx来搭建站点,最近两年我一直在做前端,php也懒得碰了。前段时间在看一个公开课时(忘了哪个机构和哪位老师了,真抱歉),这个公开课的主要内容是教你如何实现一个简单的koa框架,我当时听了下,然后照葫芦画瓢自己写了个简陋的"koa框架"。后来我想用这个简陋的框架搭建一个静态站点,折腾了下,基本可行,但我好奇koa的源码,于是去瞅了下,然后觉得自己写的实在不咋地。于是还是打算用koa来搭建个静态站点。 Koa用起来很方便其实Node.js已经为web开发提供了很多好的api接口,koa只是对其中的一些api进行了下简单的封装,使我们开发起来更方便。按我的理解,koa的核心(或者说比较好的地方)就是其提供的中间件机制: const app = new Koa()app.use(async ctx => { // 中间件内容1})app.use(async ctx => { // 中间件内容2})app.listen(port, () => { console.log('启动')})这种方式用起来也比较方便。它的核心实现在于(从公开课中学到的),将所有传入的中间件合并为一个,然后递归调用,这里不展开说了。 静态站点需求其实很简单那么用koa搭建一个静态站点也就比较容易了,静态站点也就是主要展示静态文字内容(html)、图片,另外加上一些简单的样式美化(css)和交互(js),也就是我只需要一个web服务器能够提供html、图片(jpg、jpg等)、js、css的发布功能即可。下面通过一个页面的请求来简单分析下站点的实现:1,用户通过浏览器访问站点中的a.html2,web服务器接收到请求后解析请求的文件名、文件类型3,根据上面拿到的文件名去服务器上找相应的文件4,找到了,则设置响应头:状态码(200)、响应内容类型和响应体;没有找到,则设置错误的状态码(如404)和对应的响应体。当然,还有很多细节需要考虑:比如服务器的页面存放目录、以及目录是否可读等等情况。 代码目录src |- app-koa.js // 核心文件,处理请求及返回响应 |- file-util.js // 读取文件的方法集合 |- mime.js // mime类型映射 |- status-code.js // 状态码映射 |- views/ // 存放html文件 |- static/ // 存放js、css、图片等静态资源 代码下面我先把我写的代码贴在下面:主文件app-koa.js const url = require('url')const path = require('path')const Koa = require('koa')const CONTENT_TYPE = require('./mime')const STATUS_CODE = require('./status-code')const { accessFilePromise, statFilePromise, readFilePromise} = require('./file-util')const ROOT = __dirnameconst app = new Koa()app.use(async ctx => { const reqUrl = ctx.request.url let pathname = url.parse(reqUrl).pathname let filePath = '' let fileformat = '' // 对web根路径的访问做单独处理 if (pathname === '/') { pathname = '/index.html' } const ext = pathname.indexOf('.') !== -1 ? pathname.match(/(\.[^.]+)$/)[0] : '.html' if (ext === '.html') { filePath = path.join(ROOT, `/views${pathname}`) fileformat = 'utf-8' } else { filePath = path.join(ROOT, pathname) fileformat = 'binary' } try { const isAccessed = await accessFilePromise(filePath) let fileData = null if (!isAccessed) { const code = STATUS_CODE.ENOENT // 文件或目录不可访问,直接返回404 ctx.res.writeHead(code, { 'Content-Type': CONTENT_TYPE['.html'] }) fileData = await readFilePromise(path.join(ROOT, `/views/error/${code}.html`), 'utf-8') ctx.body = fileData } else { const isFile = await statFilePromise(filePath) if (isFile === true) { fileData = await readFilePromise(filePath, fileformat) } else { // 尝试读取该目录下的index.html fileData = await readFilePromise(`${filePath}/index.html`, 'utf-8') } ctx.res.setHeader('Content-Type', CONTENT_TYPE[ext]) if (ext !== '.html') { ctx.res.setHeader('Content-Length', Buffer.byteLength(fileData)) } ctx.res.writeHead(STATUS_CODE.SUCCESS) ctx.body = fileData if (ext !== '.html') { ctx.res.write(ctx.body, 'binary') } } } catch (err) { ctx.res.writeHead(STATUS_CODE[err.code], { 'Content-Type': CONTENT_TYPE[ext] }) }})app.listen(3000, () => { console.log('Your application is running at http://localhost:3000')})file-util.js -- 文件处理工具 ...

June 25, 2019 · 3 min · jiezi

深入koa源码二核心库原理

最近读了 koa2 的源码,理清楚了架构设计与用到的第三方库。本系列将分为 3 篇,分别介绍 koa 的架构设计和 3 个核心库,最终会手动实现一个简易的 koa。这是系列第 2 篇,关于 3 个核心库的原理。 本文来自《心谭博客·深入koa源码:核心库原理》所有系列文章都放在了Github。欢迎交流和Star ✿✿ ヽ(°▽°)ノ ✿is-generator-function:判断 generatorkoa2 种推荐使用 async 函数,koa1 推荐的是 generator。koa2 为了兼容,在调用use添加中间件的时候,会判断是否是 generator。如果是,则用covert库转化为 async 函数。 判断是不是 generator 的逻辑写在了 is-generator-function 库中,逻辑非常简单,通过判断Object.prototype.toString.call 的返回结果即可: function* say() {}Object.prototype.toString.call(say); // 输出: [object GeneratorFunction]delegates:属性代理delegates和 koa 一样,这个库都是出自大佬 TJ 之手。它的作用就是属性代理。这个代理库常用的方法有getter,setter,method 和 access。 用法假设准备了一个对象target,为了方便访问其上request属性的内容,对request进行代理: const delegates = require("delegates");const target = { request: { name: "xintan", say: function() { console.log("Hello"); } }};delegates(target, "request") .getter("name") .setter("name") .method("say");代理后,访问request将会更加方便: ...

June 24, 2019 · 2 min · jiezi

深入koa源码一架构设计

本文来自《心谭博客·深入koa源码:架构设计》前端面试、设计模式手册、Webpack4教程、NodeJs实战等更多专题,请来导航页领取食用所有系列文章都放在了Github。欢迎交流和Star ✿✿ ヽ(°▽°)ノ ✿最近读了 koa 的源码,理清楚了架构设计与用到的第三方库。本系列将分为 3 篇,分别介绍 koa 的架构设计和 3 个核心库的原理,最终会手动实现一个简易的 koa。 koa 的实现都在仓库的lib目录下,如下图所示,只有 4 个文件: 对于这四个文件,根据用途和封装逻辑,可以分为 3 类:req 和 res,上下文以及 application。 req 和 res对应的文件是:request.js 和 response.js。分别代表着客户端请求信息和服务端返回信息。 这两个文件在实现逻辑上完全一致。对外暴露都是一个对象,对象上的属性都使用了getter或setter来实现读写控制。 上下文对应的文件是:context.js。存了运行环境的上下文信息,例如cookies。 除此之外,因为request和response都属于上下文信息,所以通过delegate.js库来实现了对request.js和response.js上所有属性的代理。例如以下代码: /** * Response delegation. */delegate(proto, "response") .method("attachment") .method("redirect");/** * Request delegation. */delegate(proto, "request") .method("acceptsLanguages") .method("acceptsEncodings");使用代理的另外一个好处就是:更方便的访问 req 和 res 上的属性。比如在开发 koa 应用的时候,可以通过ctx.headers来读取客户端请求的头部信息,不需要写成ctx.res.headers了(这样写没错)。 注意:req 和 res 并不是在context.js中被绑定到上下文的,而是在application被绑定到上下文变量ctx中的。原因是因为每个请求的 req/res 都不是相同的。 Application对应的文件是: application.js。这个文件的逻辑是最重要的,它的作用主要是: 给用户暴露服务启动接口针对每个请求,生成新的上下文处理中间件,将其串联对外暴露接口使用 koa 时候,我们常通过listen或者callback来启动服务器: const app = new Koa();app.listen(3000); // listen启动http.createServer(app.callback()).listen(3000); // callback启动这两种启动方法是完全等价的。因为listen方法内部,就调用了callback,并且将它传给http.createServer。接着看一下callback这个方法主要做了什么: ...

June 21, 2019 · 1 min · jiezi

QQ音乐API-koa2实现-全接口实现

QQMusicAPIQQ音乐API koa2 版本, 通过Web网页版请求QQ音乐接口数据, 有问题请提 issue, 或者你有其他想法欢迎PR.Github 知乎 掘金 环境要求因为本项目采用的是koa2, 所以请确保你的node版本是7.6.0+node -v安装git@github.com:Rain120/qq-music-api.gitnpm install项目启动// npm i -g nodemonnpm run start// or don't install nodemonnode app.js项目监听端口是3200 使用文档使用apis详见文档 关于本人Rain120: 前端菜鸟, 入职前端1年, 公司的技术栈是React, 因为公司官网由我重构过, 我使用的Vue.js重构的。目前正在脱坑, 求大佬内推呀API结构图 API接口koa接口说明(参数, 地址, 效果图)获取QQ音乐产品的下载地址接口说明: 调用此接口, 可获取QQ音乐标准产品下载链接 接口地址: /downloadQQMusic 调用例子: /downloadQQMusic 示例截图: 获取歌单分类接口说明: 调用此接口, 可获取歌单分类, 包含category信息 接口地址: /getSongListCategories 调用例子: /getSongListCategories <details> <summary>SortID</summary> sortId: 1, sortName: 默认sortId: 2, sortName: 最新sortId: 3, sortName: 最热sortId: 4, sortName: 评分sortId: 5, sortName: none</details> ...

June 4, 2019 · 4 min · jiezi

koa2一部分源码解析

koa2一部分源码解析

June 3, 2019 · 1 min · jiezi

vue全家桶

vue全家桶知识记录,只做记录不深入原理。官网都有为什么还要再写一遍?为了熟悉语法并且每个知识点尽量用一个简单例子说明用法 1.vue基础知识1.1钩子一个实例从创建到销毁,在每个特定的时间都会自动触发某个函数,这种函数就叫做生命周期函数,也叫钩子,vue实例有哪些钩子呢? //组件实例刚被创建,组件属性计算之前,如data属性等beforeCreate//组件实例创建完成,属性已绑定,但DOM还未生成,$el属性还不存在created//模板编译 / 挂载之前beforeMount//模板编译 / 挂载之后mounted//组件更新之前beforeUpdate//组件更新之后updated//组件被激活时调用activated//组件被移除时调用deactivated//组件销毁前调用beforeDestory//组件销毁后调用destoryed每个钩子的触发时间是什么?网上找了个图: 写点代码测试一下: <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>index</title> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script></head><body> <div id="app">{{msg}}</div> <script> var app = new Vue({ el:'#app', data:{ msg:'hello' }, beforeCreate(){ console.group('beforeCreate 创建前状态 ------------>'); console.log("%c%s", "color:red" , "el : " + this.$el); //undefined console.log("%c%s", "color:red","data : " + this.$data); //undefined console.log("%c%s", "color:red","message: " + this.message) }, created() { console.group('created 创建完毕状态 ------------>'); console.log("%c%s", "color:red","el : " + this.$el); //undefined console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeMount() { console.group('beforeMount 挂载前状态 ------------>'); console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化 console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, mounted() { console.group('mounted 挂载结束状态 ------------>'); console.log("%c%s", "color:red","el : " + this.$el); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeUpdate() { console.group('beforeUpdate 更新前状态 ------------>'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log('真实dom结构:' + document.getElementById('app').innerHTML); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, updated() { console.group('updated 更新完成状态 ------------>'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log('真实dom结构:' + document.getElementById('app').innerHTML); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, beforeDestroy() { console.group('beforeDestroy 销毁前状态 ------------>'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, destroyed() { console.group('destroyed 销毁完成状态 ------------>'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message) } }) </script></body></html> ...

May 31, 2019 · 20 min · jiezi

koajwt实现token验证与刷新

JWTJSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。 本文只讲Koa2 + jwt的使用,不了解JWT的话请到这里)进行了解。 koa环境要使用koa2+jwt需要先有个koa的空环境,搭环境比较麻烦,我直接使用koa起手式,这是我使用koa+typescript搭建的空环境,如果你也经常用koa写写小demo,可以点个star,方便~ 安装koa-jwtkoa-jwt主要作用是控制哪些路由需要jwt验证,哪些接口不需要验证: import * as koaJwt from 'koa-jwt';//路由权限控制 除了path里的路径不需要验证token 其他都要app.use( koaJwt({ secret: secret.sign }).unless({ path: [/^\/login/, /^\/register/] }));上面代码中,除了登录、注册接口不需要jwt验证,其他请求都需要。 使用jsonwebtoken生成、验证token执行npm install jsonwebtoken安装jsonwebtoken相关代码: import * as jwt from 'jsonwebtoken';const secret = 'my_app_secret';const payload = {user_name:'Jack', id:3, email: '1234@gmail.com'};const token = jwt.sign(payload, secret, { expiresIn: '1h' });上面代码中通过jwt.sign来生成一个token,参数意义: payload:载体,一般把用户信息作为载体来生成tokensecret:秘钥,可以是字符串也可以是文件expiresIn:过期时间 1h表示一小时在登录中返回tokenimport * as crypto from 'crypto';import * as jwt from 'jsonwebtoken';async login(ctx){ //从数据库中查找对应用户 const user = await userRespository.findOne({ where: { name: user.name } }); //密码加密 const psdMd5 = crypto .createHash('md5') .update(user.password) .digest('hex'); //比较密码的md5值是否一致 若一致则生成token并返回给前端 if (user.password === psdMd5) { //生成token token = jwt.sign(user, secret, { expiresIn: '1h' }); //响应到前端 ctx.body = { token } }}前端拦截器前端通过登录拿到返回过来的token,可以将它存在localStorage里,然后再以后的请求中把token放在请求头的Authorization里带给服务端。这里以axios请求为例,在发送请求时,通过请求拦截器把token塞到header里: ...

May 30, 2019 · 2 min · jiezi

从零编写一个Koa-graphQL的案例

在Nest.js的文档中看到了有集成GraphQL的指导,所以在本地尝试下先用Koa写出一个DEMO,之后再去与Nest.js集成起来。 先写出数据库模型(这个文件是之前就存在的,没有做更改,将文件名改成了models.ts): /** * Created by w on 2018/4/13. */const mongoose = require('mongoose');mongoose.Promise = global.Promise;mongoose.connect('mongodb://localhost/ticket', { server: { socketOptions: { keepAlive: 1 } }});const models = { users: { username: { type: String, required: true }, password: { type: String, required: true }, description: { type: String }, createTime: { type: Date, default: new Date() } }, tickets: { name: { type: String, required: true }, price: { type: Number, requred: true }, holdTime: { //举办时间 type: Date, required: true }, count: { //剩余数量 type: String, required: true }, place:{ type: String, required: true }, img: { type: String }, description: { type: String }, publicTime: { type: Date, default: new Date() }, }};for (let m in models) { mongoose.model(m, mongoose.Schema(models[m]));}module.exports = { getModules: (name) => { return mongoose.model(name); }};之后编写各自模型的GraphQL查询文件(user.ts)): ...

May 30, 2019 · 4 min · jiezi

koa-router-多文件引入

背景koa-router路由越来越多,api下的router都要使用下面的方式引入,怎么才能方便快捷的将api下的所有文件都引入呢这次记录的就是如果将koa-router 一次性循环引入 const book = require('./app/api/v1/book')const classic = require('./app/api/v1/classic')// ...app.use(book.routes(), book.allowedMethods())app.use(classic.routes(), classic.allowedMethods())//...文件目录koa-demo/ |-api/ |-books.js |-classic.js |-users.js |-articles.js |-package.json |-app.js传统方式引入router app.js const Koa = require('koa')const app = new Koa()const book = require('./app/api/v1/book')const classic = require('./app/api/v1/classic')app.use(book.routes(), book.allowedMethods())app.use(classic.routes(), classic.allowedMethods())app.listen(3333)require-directory引入require-directory用来递归地迭代指定的目录,并返回这些模块。github随着文件增加,如何高效的开发就是我们要追求的事情了 首先 npm install require-directoryapp.js const Koa = require('koa')const app = new Koa()const Router = require('koa-router')// 使用require-directory加载路由文件夹下的所有routerconst requireDirectory = require('require-directory')// 将所有的路由加载上,自动加载代码const modules = requireDirectory(module, './api', { visit: whenLoadModule })function whenLoadModule(obj) { if (obj instanceof Router) { app.use(obj.routes(), obj.allowedMethods()) }}app.listen(3333)路由文件就按照传统的方式写就行books.js ...

May 22, 2019 · 1 min · jiezi

pm2-发布-node-配置文件ecosystemjson

背景最近在搭建一个node+koa+vue的项目使用到了pm2发布这里简单的记录一下在根目录新建文件ecosystem.json { "apps": [ { "name": "ant-help-center", "script": "./bin/www", //启动脚本 "env": { "COMMON_VARIABLE": "true" }, // 测试服务器 "env_development": { "NODE_ENV": "development", "PORT": 8087 }, // 生产环境 "env_production": { "NODE_ENV": "production", "PORT": 8087 } } ], "deploy": { // 生产环境 "production": { "user": "root", //Nginx服务器上的username "host": ["xxx.xxx.xxx.xxx"], // 服务器地址 "port": "22", "ref": "origin/master", //从指定的分支拉取代码 "repo": "git@gitee.com:xxx/xxxx.git", // 使用 "path": "/www/website/production", //发布到服务器指定的目录下 "ssh_options": "StrictHostKeyChecking=no", //构建在发布 "post-deploy": "npm install && pm2 startOrRestart ecosystem.json --env production", "env": { "NODE_ENV": "production" } }, // 测试环境 "development": { "user": "root", //Nginx服务器上的username "host": ["xxx.xxx.xxx.xxx"], // 服务器地址 "port": "22", "ref": "origin/master", //从指定的分支拉取代码 "repo": "git@gitee.com:xxx/xxxx.git", "path": "/www/website/development", //发布到服务器指定的目录下 "ssh_options": "StrictHostKeyChecking=no", //构建在发布 "post-deploy": "npm install && pm2 startOrRestart ecosystem.json --env development", "env": { "NODE_ENV": "development" } } }}需要注意点:一:repo参数要使用git ssh的地址二:先在服务器创建path 目录目录要有权限 ...

May 15, 2019 · 1 min · jiezi

axioskoa-开发小记

Access to XMLHttpRequest at 'http://localhost:8888/' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.axios如果携带着data参数发送请求,会添加一个自定义请求头:Access-Control-Request-Headers: content-type,在发送正式请求前会发送OPTIONS预检请求,如果后端没有对应的设置响应头,将无法通过而报出以上错误。 解决方法: server.use(async (ctx, next) => { console.log(`visited PATH: ${chalk.blue(ctx.path)}`) ctx.set({ 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type' }) await next()})后端放行这个请求头

May 14, 2019 · 1 min · jiezi

KoaTypescript起手式空环境-不用每次玩node都要搭环境了

本人平常喜欢用nodejs瞎 JB 写写东西,但是每次都要创建项目、安装依赖等等前戏工作,这让我很烦。于是乎写了个空的起手式,这样以后写东西直接 clone 下来就行了。 我觉得跟我一样的人应该很多,所以也把这个小东西分享给大家。 ts-koa-starter这是一个koa+typescript的起手式(简单的空环境) 如果你想再加个typeorm来玩玩数据库,请 clone 下来后切换到bt-ts-koa-typeorm分支 ps:typeorm是一个非常好的数据库 ORM,如果你没玩过,请务必尝试一下 ???????????? 项目结构.├── src│ ├── controller //controller层│ ├── service //service层│ ├── routes.ts //路由│ └── index.ts //项目入口index.js├── ecosystem.config.js //pm2配置├── nodemon.json //nodemon配置├── package.json└── tsconfig.json使用git clone https://github.com/Vibing/ts-...yarn 或者 npm iyarn start 或 npm start在浏览器中开打localhost:3000打包yarn build 或 npm run build生产环境启动生产环境使用 pm2 启动 可以达到负载均衡 执行:yarn pro 或 npm run pro (生产环境端口默认:8080)友情链接Koa2 Koa (koajs) -- 基于 Node.js 平台的下一代 web 开发框架 | Koajs 中文文档Typescript TypeScript 中文网 · TypeScript——JavaScript 的超集github地址https://github.com/Vibing/ts-... ...

May 11, 2019 · 1 min · jiezi

Nodejs中Koa2如何使用Session完成登录状态保持

项目要用到登录注册,就需要使用到Cookie和Session来保持登录状态,于是就简单研究了一下Cookie和Session的工作原理前面已经专门发过一篇帖子记录Cookie和Session的工作原理了,不明白的小伙伴可以看看Cookie、Session是如何保持登录状态的?。 使用Koa的Session中间件Koa是一个简洁的框架,把许多小功能都拆分成了中间件,用一个洋葱模型保证了中间件丰富的可拓展性,我们要使用Session来保持登录状态,就需要引用Session中间件。 安装Koa-Session中间件npm install koa-session --save如果需要使用TypeScript进行开发,则需要引入对应的TS类型声明 npm install @types/koa-session --save配置SessionKoa-Session需要做一些配置: const session_signed_key = ["some secret hurr"]; // 这个是配合signed属性的签名keyconst session_config = { key: 'koa:sess', /** cookie的key。 (默认是 koa:sess) */ maxAge: 4000, /** session 过期时间,以毫秒ms为单位计算 。*/ autoCommit: true, /** 自动提交到响应头。(默认是 true) */ overwrite: true, /** 是否允许重写 。(默认是 true) */ httpOnly: true, /** 是否设置HttpOnly,如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能有效的防止XSS攻击。 (默认 true) */ signed: true, /** 是否签名。(默认是 true) */ rolling: true, /** 是否每次响应时刷新Session的有效期。(默认是 false) */ renew: false, /** 是否在Session快过期时刷新Session的有效期。(默认是 false) */};我们需要关注这几个配置: ...

May 6, 2019 · 2 min · jiezi

Apollo-Server-集成性能监控

Apollo Server开箱支持Apollo Engine,只是由于某些不可知的原因Apollo Engine的 API 在国内不可访问(我是真不知道为什么这个 API 会被墙的),所以只能另外想办法了. Apollo Server本身有一个Apollo Tracing可以用于性能监控的扩展,通过扩展Apollo Tracing收集指标传输到分布式跟踪系统中.另外有一个开源库Apollo Opentracing可以收集指标,传输到Jaeger或者Zipkin中,通过Jaeger或Zipkin实现性能监控和分析. 秉着方便,直接使用Apollo Opentracing.分布式跟踪系统使用Jaeger. 使用 Docker 搭建JaegerJaeger 官方文档 在浏览器打开 http://localhost:16686 访问Jaeger 搭建Apollo Servermkdir apollo-opentracing-democd apollo-opentracing-demoyarn init -yyarn add apollo-server// index.jsconst { ApolloServer, gql } = require('apollo-server')const typeDefs = gql` type Query { hello: String }`const resolvers = { Query: { hello: () => 'world', },}const server = new ApolloServer({ typeDefs, resolvers,})server.listen().then(({ url }) => { console.log(`???? Server ready at ${url}`)})运行 ...

April 29, 2019 · 1 min · jiezi

从项目中由浅入深的学习koa-mongodb4

序列文章从项目中由浅入深的学习vue,微信小程序和快应用 (1)从项目中由浅入深的学习react (2)从项目中由浅入深的学习typescript (3) 前言node.js的出现前端已经可以用js一把梭,从前端写到后台。本文从后台利用node的框架koa+mongodb实现数据的增删改查和注册接口,前端利用umi + dva +ant-design-pro来实现数据渲染。实现一个小全栈project,就是so-easy1.效果图react-koa 全栈项目,欢迎star 2.技术栈koa:node框架koa-bodyparser:解析body的中间件koa-router :解析router的中间件mongoose :基于mongdodb的数据库框架,操作数据nodemon:后台服务启动热更新 3.项目目录├── app // 主项目目录│ ├── controllrts // 控制器目录(数据处理)│ │ └── ... // 各个表对应的控制器│ ├── middleware // 中间件目录│ │ └── resFormat.js // 格式化返回值│ ├── models // 表目录(数据模型)│ │ ├── course.js // 课程表│ │ └── user.js // 用户表│ └── utils // 工具库│ │ ├── formatDate.js // 时间格式化│ │ └── passport.js // 用户密码加密和验证工具├── db-template // 数据库导出的 json 文件├── routes // 路由目录│ └── api // 接口目录│ │ ├── course_router.js // 课程相关接口│ │ └── user_router.js // 用户相关接口├── app.js // 项目入口└── config.js // 基础配置信息 ...

April 26, 2019 · 1 min · jiezi

如何选择正确的Node框架ExpressKoa还是Hapi

简介Node.js是10年前首次推出的,目前它已经成为世界上最大的开源项目,在GitHub上有+59,000颗星,下载次数超过10亿。流行度快速增长的部分原因是Node.js允许开发人员在应用程序的客户端和服务器端部分使用相同的语言:JavaScript。Node.js是一个开源和跨平台的JavaScript运行时环境,专为构建可扩展的服务器端WEB应用而设计,自身具有高并发、扩展性强等特点。由于社区其呈指数级增长和普及,因此创建了许多框架来提高生产力。在本文中,我们将探讨Node.js中三个最流行的框架之间的差异:Express,Koa和Hapi。在以后的文章中,我们将研究Next,Nuxt和Nest。比较基于: GitHub Stars和npm下载安装基本的Hello World应用程序好处缺点性能安全社区参与ExpressExpress是一个最小且灵活的Web应用程序框架,为Web和移动应用程序提供了一组强大的功能,它的行为就像一个中间件,可以帮助管理服务器和路由star GitHub star:+43,000npm每周下载 6,881,035安装 确保你已经安装node和npm // 你可以将express安装到项目依赖 npm install express --save // 如果要临时安装Express而不是将其添加到依赖项列表,则可以使用 npm install express --no-saveHello World 这是关于如何创建一个侦听端口3000并响应“Hello World!”的快速应用程序的最基本示例 // 这里只创建根目录 其他目录返回404 const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => res.send('Hello World!')) app.listen(port, () => console.log(`Example app listening on port ${port}!`))好处 几乎是Node.js Web中间件的标准简单,简约,灵活和可扩展快速开发应用程序完全可定制学习曲线低轻松集成第三方服务和中间件主要关注浏览器,模板和渲染集成开箱即用缺点 尽管Express.js是一个非常方便且易于使用的框架,但它有一些可能影响开发过程的小缺点。组织需要非常清楚,以避免在维护代码时出现问题随着代码库大小的增加,重构变得非常具有挑战性需要大量的手工劳动,因为您需要创建所有端点性能 Express是对web应用的一层基本封装,继承了Node.js的特性当天也有一些express性能的最佳实践包括: 使用gzip压缩不要使用同步功能正确记录(用于调试,使用特殊模块,如调试,应用程序活动使用winston或bunyan)使用try-catch或promises正确处理异常确保您的应用程序使用流程管理器自动重新启动,或使用systemd或upstartinit等系统在群集中运行您的应用。您可以通过启动进程集群来大大提高Node.js应用程序的性能缓存请求结果,以便您的应用不会重复操作以反复提供相同的请求使用负载均衡器运行它的多个实例并分配流量,如Nginx或HAProxy对静态资源使用反向代理。它可以处理错误页面,压缩,缓存,提供文件和负载平衡等更多性能最佳实践一个简单的“Hello World”应用程序每秒具有以下性能请求: 安全 Node.js漏洞直接影响Express,因此确保使用最新的稳定版Node.js查看express 最佳安全实践社区参与 贡献者数量:220Pull Requests:821Express社区定期活动包括 Gitter,IRC channel, issues, Wiki等等最后,express可能是Node.js最流行的框架,还有许多其他流行的框架都是基于Express构建的。koaKoa 是一个新的 web 框架,由 Express幕后的原班人马打造,致力于成为web应用和API开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa帮你丢弃回调函数,并有力地增强错误处理Koa并没有捆绑任何中间件而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序star ...

April 24, 2019 · 1 min · jiezi

不到300行代码构建精简的koa和koa-router(mini-koa)

前言鉴于之前使用express和koa的经验,这两天想尝试构建出一个koa精简版,利用最少的代码实现koa和koa-router,同时也梳理一下Node.js网络框架开发的核心内容。实现后的核心代码不超过300行,源代码配有详细的注释。核心设计API调用在mini-koa的API设计中,参考koa和koa-router的API调用方式。Node.js的网络框架封装其实并不复杂,其核心点在于http/https的createServer方法上,这个方法是http请求的入口。首先,我们先回顾一下用Node.js来启动一个简单服务。// https://github.com/qzcmask/mini-koa/blob/master/examples/simple.jsconst http = require(‘http’)const app = http.createServer((request, response) => { response.end(‘hello Node.js’)})app.listen(3333, () => { console.log(‘App is listening at port 3333…’)})路由原理既然我们知道Node.js的请求入口在createServer方法上,那么我们可以在这个方法中找出请求的地址,然后根据地址映射出监听函数(通过get/post等方法添加的路由函数)即可。其中,路由列表的格式设计如下:// binding的格式{’/’: [fn1, fn2, …],’/user’: [fn, …],…}// fn/fn1/fn2的格式{ method: ‘get/post/use/all’, fn: ‘路由处理函数’}难点分析next()方法设计我们知道在koa中是可以添加多个url监听函数的,其中决定是否传递到下一个监听函数的关键在于是否调用了next()函数。如果调用了next()函数则先把路由权转移到下一个监听函数中,处理完毕再返回当前路由函数。在mini-koa中,我把next()方法设计成了一个返回Promise fullfilled的函数(这里简单设计,不考虑next()传参的情况),用户如果调用了该函数,那么就可以根据它的值来决定是否转移路由函数处理权。判断是否转移路由函数处理权的代码如下:let isNext = falseconst next = () => { isNext = true return Promise.resolve()}await router.fn(ctx, next)if (isNext) { continue} else { // 没有调用next,直接中止请求处理函数 return}use()方法设计mini-koa提供use方法,可供扩展日志记录/session/cookie处理等功能。use方法执行的原理是根据请求地址在执行特定路由函数之前先执行mini-koa调用use监听的函数。所以这里的关键点在于怎么找出use监听的函数列表,假设现有监听情况如下:app.use(’/’, fn1)app.use(’/user’, fn2)如果访问的url是/user/add,那么fn1和fn2都必须要依次执行。我采取的做法是先根据/字符来分割请求url,然后循环拼接,查看路由绑定列表(binding)中有没有要use的函数,如果发现有,添加进要use的函数列表中,没有则继续下一次循环。详细代码如下:// 默认use函数前缀let prefix = ‘/’// 要预先调用的use函数列表let useFnList = []// 分割url,使用use函数// 比如item为/user/a/b映射成[(‘user’, ‘a’, ‘b’)]const filterUrl = url.split(’/’).filter(item => item !== ‘’)// 该reduce的作用是找出本请求要use的函数列表filterUrl.reduce((cal, item) => { prefix = cal if (this.binding[prefix] && this.binding[prefix].length) { const filters = this.binding[prefix].filter(router => { return router.method === ‘use’ }) useFnList.push(…filters) } return ( ‘/’ + [cal, item] .join(’/’) .split(’/’) .filter(item => item !== ‘’) .join(’/’) )}, prefix)ctx.body响应通过ctx.body = ‘响应内容’的方式可以响应http请求。它的实现原理是利用了ES6的Object.defineProperty函数,通过设置它的setter/getter函数来达到数据追踪的目的。详细代码如下:// 追踪ctx.body赋值Object.defineProperty(ctx, ‘body’, { set(val) { // set()里面的this是ctx response.end(val) }, get() { throw new Error(ctx.body can't read, only support assign value.) }})子路由mini-koa-router设计子路由mini-koa-router设计这个比较简单,每个子路由维护一个路由监听列表,然后通过调用mini-koa的addRoutes函数添加到主路由列表上。mini-koa的addRoutes实现如下:addRoutes(router) { if (!this.binding[router.prefix]) { this.binding[router.prefix] = [] } // 路由拷贝 Object.keys(router.binding).forEach(url => { if (!this.binding[url]) { this.binding[url] = [] } this.binding[url].push(…router.binding[url]) })}用法使用示例如下,源代码可以在github上找到:// examples/server.js// const { Koa, KoaRouter } = require(‘mini-koa’)const { Koa, KoaRouter } = require(’../index’)const app = new Koa()// 路由用法const userRouter = new KoaRouter({ prefix: ‘/user’})// 中间件函数app.use(async (ctx, next) => { console.log(请求url, 请求method: , ctx.req.url, ctx.req.method) await next()})// 方法示例app.get(’/get’, async ctx => { ctx.body = ‘hello ,app get’})app.post(’/post’, async ctx => { ctx.body = ‘hello ,app post’})app.all(’/all’, async ctx => { ctx.body = ‘hello ,/all 支持所有方法’})// 子路由使用示例userRouter.post(’/login’, async ctx => { ctx.body = ‘user login success’})userRouter.get(’/logout’, async ctx => { ctx.body = ‘user logout success’})userRouter.get(’/:id’, async ctx => { ctx.body = ‘用户id: ’ + ctx.params.id})// 添加路由app.addRoutes(userRouter)// 监听端口app.listen(3000, () => { console.log(’> App is listening at port 3000…’)})总结挺久没有造轮子了,这次突发奇想造了个精简版的koa,虽然跟常用的koa框架有很大差别,但是也实现了最基本的API调用和原理。造轮子是一件难能可贵的事,程序员在学习过程中不应该崇尚拿来主义,学习到一定程度后,要秉持能造就造的态度,去尝试理解和挖掘轮子背后的原理和思想。当然,通常来说,自己造的轮子本身不具备多大的实用性,没有经历过社区大量的测试和实际应用场景的打磨,但是能加深自己的理解和提高自己的能力也是一件值得坚持的事。人生是一段不断攀登的高峰,只有坚持向前,才能看到新奇的东西。最后附上项目的Github地址,欢迎Star或Fork支持,谢谢。 ...

April 18, 2019 · 2 min · jiezi

TKoa - 使用 TypeScript 重构的 Node.js Koa开发框架

????Tkoa是使用 typescript 编写的 koa 框架!尽管它是基于 typescript 编写,但是你依然还是可以使用一些 node.js 框架和基于 koa 的中间件。不仅如此,你还可以享受 typescript 的类型检查系统和方便地使用 typescript 进行测试!安装TKoa 需要 >= typescript v3.1.0 和 node v7.6.0 版本。$ npm install tkoaHello T-koaimport tKoa = require(’tkoa’);interface ctx { res: { end: Function }}const app = new tKoa();// responseapp.use((ctx: ctx) => { ctx.res.end(‘Hello T-koa!’);});app.listen(3000);MiddlewareTkoa 是一个中间件框架,拥有两种中间件:异步中间件普通中间件下面是一个日志记录中间件示例,其中使用了不同的中间件类型:async functions (node v7.6+):interface ctx { method: string, url: string}app.use(async (ctx: ctx, next: Function) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(${ctx.method} ${ctx.url} - ${ms}ms);});Common function// Middleware normally takes two parameters (ctx, next), ctx is the context for one request,// next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.interface ctx { method: string, url: string}app.use((ctx: ctx, next: Function) => { const start = Date.now(); return next().then(() => { const ms = Date.now() - start; console.log(${ctx.method} ${ctx.url} - ${ms}ms); });});Getting startedTkoa - 教程en - english readmeGitter - 聊天室SupportTypeScript大于等于 v3.1 版本Node.js大于等于 v7.6.0 版本LicenseMIT ...

April 17, 2019 · 1 min · jiezi

中台到底是个啥?

文章来自健荐微信公众号,作者王健,ThoughtWorks高级咨询师。王健将于5月18-19日在上海A2M峰会分享关于中台的话题,更多A2M峰会内容可点击此处查看。从去开始,好像就有一只无形的手一直将我与“微服务”、“平台化”、“中台化”撮合在一起,给我带来了很多的困扰和思考与收获。平台化正兴起,从基础设施到人工智能等各个领域不断涌现的各类平台,对于软件开发人员及企业带来了深远的影响。然而,在中国提“数字化平台战略”大家可能会觉得比较抽象,比较远大空;若是提“中台”大家则会更熟悉一些。那…中台到底是什么?会不会又是另一个Buzzword呢?这个从名字上看像是从前台与后台中间硬挤出来的新断层,它与前台和后台的区别和界限到底在哪儿?什么应该放到中台,什么又应该放到前台或是后台?它的出现到底是为了解决什么问题呢?这一个接一个的问题就不断的涌出并萦绕在我的脑子里。直到一年多后的今天,随着参与的几个平台化、企业中台相关的项目已顺利步上正轨,终于可以坐下来回顾这一年的实践与思考,再次试图回答这些问题,并梳理成文,与大家交流探讨。一、中台迷思到处都在喊中台,到处都是中台,中台这个词在我看来已经被滥用了。在一部分人眼里:中台就是技术平台,像微服务开发框架、DevOps平台、PaaS平台,容器云之类的,人们都叫它“技术中台”;在一部份人眼里:中台就是微服务业务平台,像最常见的什么用户中心、订单中心,各种微服务集散地,人们都叫它“业务中台”;在一些人眼里:中台应该是组织的事情,在释放潜能:平台型组织的进化路线图 (豆瓣)中就提出了平台型组织和组织中台的概念,这类组织中台在企业中主要起到投资评估与投后管理的作用,类似于企业内部资源调度中心和内部创新孵化组织,人们叫它“组织中台”。看完本篇你就会理解,上边的这几类“中台”划分还是靠谱的,更多我看到的情况是,大家为了响应企业的“中台战略”,干脆直接将自己系统的“后端”或是“后台”改个名,就叫“中台”。中台到底是什么?它对于企业的意义到底是什么?当我们谈中台时我们到底在谈些什么?想要寻找到答案,仅仅沉寂在各自“中台”之中,如同管中窥豹,身入迷阵,是很难想清楚的。不如换个⾓度,从各类的“中台迷阵”中跳脱出来,尝试以更高的视角,从企业均衡可持续发展的角度来思考中台,反推它存在的价值。为了搞明白中台存在的价值,我们需要回答以下两个问题:企业为什么要平台化?企业为什么要建中台?1、企业为什么要平台化?先给答案,其实很简单:因为在当今互联网时代,⽤户才是商业战场的中心,为了快速响应用户的需求,借助平台化的力量可以事半功倍。不断快速响应、探索、挖掘、引领⽤户的需求,才是企业得以⽣存和持续发展的关键因素。那些真正尊重用户,甚⾄不惜调整⾃己颠覆⾃己来响应⽤户的企业,将在这场以⽤户为中心的商业战争中得以⽣存和发展;反之,那些在过去的成就上故步⾃封,存在侥幸⼼理希望⽤户会像之前一样继续追随⾃己的企业,则会被用户淘汰。很残酷,但这就是这个时代最基本的企业⽣存法则。平台化之所以重要,就是因为它赋予或加强了企业在以用户为中心的现代商业战争中最最最核心的能力:⽤户响应力。这种能力可以帮助企业在商战上先发制⼈,始终抢得先机。可以说,在互联网时代,商业的斗争就是对于用户响应力的比拼。又有点远大空是不是?我们来看⼏个经典的例子:例子1说起中台,最先想到的应该就属是阿⾥的“⼤中台,⼩前台”战略。阿⾥⼈通过多年不懈的努力,在业务的不断催化滋养下,将⾃己的技术和业务能力沉淀出一套综合能力平台,具备了对于前台业务变化及创新的快速响应能力。例子2海尔也早在⼗年前就已经开始推进平台化组织的转型,提出了“平台⾃营体⽀撑⼀线⾃营体”的战略规划和转型⽬标。构建了“⼈单合一”、“⽤户付薪”的创客文化,真正将平台化提⾼到了组织的⾼度。例子3华为在几年前就提出了“⼤平台炮火支撑精兵作战”的企业战略,“让听得到炮声的人能呼唤到炮火”,这句话形象的诠释了大平台⽀撑下小前台的作战策略。这种极度灵活又威力巨⼤的战法,使之可以迅速响应瞬息万变的战场,一旦锁定目标,通过大平台的炮火群,迅速精准对于战场进行强大的火⼒支援。可⻅,在互联⽹热火朝天,第四次工业革命的曙光即将到来的今日,企业能否真正做到“以用户为中心”,并不断提升自己的用户响应力来追随甚至引领用户的脚步,持续规模化创新,终将决定企业能否在这样充满挑战和机遇的市场上笑到最后,在商业上长久保持创新活力与竞争力。而平台化恰好可以助力企业更快更好的做到这些,所以这回答了第一个问题——企业需要平台化。2、企业为什么要建中台?好,到此我们想明白了为什么需要平台化。但是平台化并不是一个新概念,很多企业在这个方向上已经做了多年的努力和积淀。那为什么最近几年“中台”这个相对较新的概念又会异军突起?对于企业来讲,传统的“前台+后台”的平台化架构又为什么不能满足企业的要求呢?这就引出了我们的第二个问题:企业为什么要建中台?定义一下前台与后台因为平台这个词过于宽泛了,为了能让大家理解我在说什么,我先定义一下本文上下文我所说的前台和后台各指什么:前台:由各类前台系统组成的前端平台。每个前台系统就是一个用户触点,即企业的最终用户直接使用或交互的系统,是企业与最终用户的交点。例如用户直接使用的网站、手机App、微信公众号等都属于前台范畴。后台:由后台系统组成的后端平台。每个后台系统一般管理了企业的一类核心资源(数据+计算),例如财务系统、产品系统、客户管理系统、仓库物流管理系统等,这类系统构成了企业的后台。基础设施和计算平台作为企业的核心计算资源,也属于后台的一部分。后台并不为前台而生定义了前台和后台,对于第二个问题(企业为什么要建中台),同样先给出我的答案:因为企业后台往往并不能很好的支撑前台快速创新响应用户的需求,后台更多解决的是企业管理效率问题,而中台要解决的才是前台的创新问题。大多数企业已有的后台,要么前台根本就用不了,要么不好用,要么变更速度跟不上前台的节奏。我们看到的很多企业的后台系统,在创建之初的目标,并不是主要服务于前台系统创新,更多的是为了实现后端资源的电子化管理,解决企业管理的效率问题。这类系统要不就是当年花大价钱外购,需要每年支付大量的服务费,并且版本老旧,定制化困难;要不就是花大价钱自建,年久失修,一身的补丁,同样变更困难,也是企业所谓的“遗留系统”的重灾区。总结下来就两个字“慢”和“贵”,对业务的响应慢,动不动改个小功能就还要花一大笔钱。有人会说了,你不能拿遗留系统说事儿啊,我们可以新建后台系统啊,整个2.0问题不就解决了?但就算是新建的后台系统,因为其管理的是企业的关键核心数据,考虑到企业安全、审计、合规、法律等限制,导致其同样往往⽆法被前台系统直接使用,或是受到各类限制⽆法快速变化,以⽀持前台快速的创新需求。此时的前台和后台就像是两个不同转速的⻮轮,前台由于要快速响应前端用户的需求,讲究的是快速创新迭代,所以要求转速越快越好;⽽后台由于⾯对的是相对稳定的后端资源,⽽且往系统陈旧复杂,甚至还受到法律法规审计等相关合规约束,所以往往是稳定至上,越稳定越好,转速也自然是越慢越好。所以,随着企业务的不断发展,这种“前台+后台”的⻮轮速率“匹配失衡”的问题就逐步显现出来。随着企业业务的发展壮大,因为后台修改的成本和⻛险较⾼,也就驱使我们尽量选择保持后台系统的稳定性。但因为还要响应用户持续不断的需求,自然就会将大量的业务逻辑(业务能力)直接塞到了前台系统中,引入重复的同时还会致使前台系统不断膨胀,变得臃肿,形成了一个个⼤泥球的“烟囱式单体应用”,渐渐拖垮了前台系统的“⽤户响应⼒”。用户满意度降低,企业竞争力也随之不断下降。对于这样的问题,Gatner在2016年提出的一份《Pace-Layered Application Strategy》报告中,给出了一种解决方案,即按照“步速”将企业的应用系统划分为三个层次(正好契合前中后台的三个层次),不同的层次采用完全不同的策略。而Pace-Layered Application Strategy也为“中台”产生的必然性,提供了理论上的支撑。Pace-Layered Application Strategy在这份报告中Gatner提出,企业构建的系统从Pace-Layered的⾓度来看可以划分为三类: SOR(Systems of record ),SOD(Systems of differentiation)和SOI(Systems of innovation)。处于不同Pace-Layered的系统因为⽬的不同、关注点不同、要求不同,变化的“速率”自然也不同,匹配的也需要采⽤不同的技术架构、管理流程、治理架构甚至投资策略。⽽前面章节我们提到的后台系统,例如CRM、ERP、财务系统等,它们⼤多都处于SOR的Pace-Layered。这些系统的建设之初往往是以规范处理企业底层资源和企业的核⼼可追溯单据(例如财务单据,订单单据)为主要⽬的。它们的变更周期往往比较⻓,⽽且由于法律律审计等其他限制,导致对于它们的变更需要严谨的申报审批流程和更高级别的测试部署要求,这就导致了它们往往变化频率低、变化成本高、变化⻛险高、变化周期⻓,⽆法满⾜由⽤户驱动的快速变化的前台系统要求。我们又要尽力保持后台(SOR)系统的稳定可靠,⼜要前台系统(SOI)能够⼩而美,快速迭代。就出现了上文提到的“齿轮匹配失衡”的问题,感觉鱼与熊掌不可兼得。正当陷入僵局的时候,天空中飘来一声IT谚语:软件开发中遇到的所有问题,都可以通过增加⼀层抽象而得以解决!⾄此,⼀声惊雷滚过,“中台”脚踏七彩祥云,承载着SOD(Systems of differentiation) 的前世寄托,横空出世。我们先试着给中台下个定义:中台是真正为前台而生的平台(可以是技术平台,业务能力甚至是组织机构),它存在的唯一目的就是更好的服务前台规模化创新,进而更好的响应服务引领用户,使企业真正做到自身能力与用户需求的持续对接。中台就像是在前台与后台之间添加的⼀组“变速齿轮”,将前台与后台的速率进行匹配,是前台与后台的桥梁。它为前台而生,易于前台使用,将后台资源顺滑流向用户,响应用户。中台很像Pace-Layered中的SOD,提供了比前台(SOI)更强的稳定性,以及⽐后台(SOR)更高的灵活性,在稳定与灵活之间寻找到了⼀种美妙的平衡。中台作为变速齿轮,链接了用户与企业核心资源,并解决了配速问题:有了“中台”这⼀新的Pace-Layered断层,我们就可以将早已臃肿不堪的前台系统中的稳定通用业务能力“沉降”到中台层,为前台减肥,恢复前台的响应力;又可以将后台系统中需要频繁变化或是需要被前台直接使用的业务能力“提取”到中台层,赋予这些业务能力更强的灵活度和更低的变更成本,从而为前台提供更强大的“能力炮火”支援。所以,企业在平台化的过程中,需要建设自己的中台层(同时包括技术中台、业务中台和组织中台)。3、小结思考并回答了文初提出的两个关于中台价值的核心问题,解决了我对于中台产生的一些困惑,不知道对你有没有启发?我最后再来总结一下:以用户为中心的持续规模化创新,是中台建设的核心目标。企业的业务响应能⼒和规模化创新能力,是互联⽹时代企业综合竞争⼒的核⼼体现。平台化包括中台化,只是帮助企业达到这个目标的⼿段,并不是⽬标本身。中台(无论是技术中台、业务中台还是组织中台)的建设,根本上是为了解决企业响应力困境, 弥补创新驱动快速变化的前台,稳定可靠驱动变化周期相对较慢的后台之间的⽭盾,提供⼀个中间层来适配前台与后台的配速问题,沉淀能⼒,打通并顺滑链接前台需求与后台资源,帮助企业不断提升用户响应⼒。所以,中台到底是什么根本不重要,如何想方设法持续提高企业对于⽤户的响应力才是最重要的。而平台化或是中台化,只是恰巧走在了了这条正确的大道上。二、到底中台长啥样?列举了这么多各式各样的中台,最后都扯到了组织层面,是不是有种越听越晕的感觉?好像什么东西加个“中台”的后缀都可以靠到中台上来?那估计很快就会看到例如AI中台、VR中台、搜索中台、算法中台……对了,算法中台已经有了……让我们引用一段阿里玄难在接受采访时提到对于中台的一段我非常认同的描述:本文中我们一直提到的一个词就是“能力”,从玄难的这段采访也可以看出,在阿里“能力”也是中台的核心。甄别是不是中台,还要回到中台要解决的问题上,也就是我上面主要关注的问题。我认为一切以”以用户为中心的持续规模化创新”为目的,将后台各式各样的资源转化为前台易于使用的能力,帮助我们打赢这场以用户为中心的战争的平台,我们都可以称之为中台:业务中台提供重用服务,例如用户中心、订单中心之类的开箱即用可重用能力,为战场提供了强大的后台炮火支援能力,随叫随到,威力强大;数据中台提供了数据分析能力,帮助我们从数据中学习改进、调整方向,为战场提供了强大及时的雷达监测能力,帮助我们掌控战场;移动及算法中台提供了战场一线火力支援能力,帮助我们提供更加个性化的服务,增强用户体验,为战场提供了陆军支援能力,随机应变,所向披靡;技术中台提供了自建系统部分的技术支撑能力,帮助我们解决了基础设施,分布式数据库等底层技术问题,为前台特种兵提供了精良的武器装备;研发中台提供了自建系统部分的管理和技术实践支撑能力,帮助我们快速搭建项目、管理进度、测试、持续集成、持续交付,是前台特种兵的训练基地及快速送达战场的机动运输部队;组织中台为我们的项目提供投资管理、风险管理、资源调度等,是战场的指挥部,战争的大脑,指挥前线,调度后方。所以,评判一个平台是否称得上中台,最终评判标准不是技术,也不是长什么模样,还是得前台说了算,毕竟前台才是战争的关键,是感受得到战场的残酷、看得见用户的那部分人。前台想不想用,爱不爱用,好不好用;帮了前台多大的忙,从中台获得了多大的好处,愿意掏出多少利润来帮助建设中台,这些才是甄别中台建设对错好坏的标准。对于中台来讲,前台就是用户,以用户为中心,在中台同样适用。三、中台就是「企业级能力复用平台」如果让我给出一个定义,目前我认为最贴切的应该是: 中台就是「企业级能力复用平台」。很简单,有点失望是不是?但是为了找到一个靠谱的定义,我几乎花了快两年的时间,期间有各种各样的定义曾浮现出来,但至少到目前为止,只有这个定义我觉得最贴切、最简单、也最准确,它能解释几乎所有我碰到的关于中台的问题,例如:为什么会有那么多中台,像上文提到业务中台,数据中台,搜索中台,移动中台,哪些才是中台,哪些是蹭热点的?中台与前台的划分原则是什么?中台化与平台化的区别是什么?中台化和服务化的区别是什么?中台该怎么建设?等等…这9个字看起来简单,重要的是其背后对「中台」价值的阐释,下面就让我为大家一一拆解来看。企业级当做中台建设的时候,一定是跳出单条业务线站在企业整体视角来审视业务全景,寻找可复用的能力进行沉淀,从而希望通过能力的复用一方面消除数据孤岛业务孤岛,一方面支撑企业级规模化创新,助力企业变革,滋生生态。所以虽然中台的建设过程虽然可以自下而上,以点及面。但驱动力一定是自上而下的,全局视角的,并且需要一定的顶层设计。这也解释了为什么在企业中推动中台建设的往往都是跨业务部门,例如CIO级别领导或是企业的战略规划部门,因为只有这些横跨多条业务线的角色和组织,才会去经常反思与推动企业级的能力复用问题。这一点也引出了中台建设的一个关键难点,就是组织架构的调整和演进以及利益的重新分配,这是技术所不能解决的,也是中台建设的最强阻力。同时企业级也是区分企业中台化与应用系统服务化的关键点,简而言之中台化是企业级、是全局视角,服务化更多是系统级、是局部视角。所以从中台的兴起与爆发可以看到一种趋势,就是越来越多的企业无论是由于企业运营效率的原因还是由于创新发展的需要,对于企业全局视角跨业务线的能力沉淀都提高到了前所未有的战略高度。能力提到中台,最常听到的一个词就是「能力」。可能是因为能力这个词足够简单,又有着足够的包容度与宽度。企业的能力可能包含多个维度,常见的例如计算能力,技术能力,业务能力,数据能力,AI能力,运营能力,研发能力…其中大部分的能力还可以继续细化和二次展开,从而形成一张多维度的企业能力网。可以说,中台就是企业所有可以被「多前台产品团队」复用能力的载体。复用虽然我们一直讲「去重复用」讲了很多年,但仔细想想,大多平台化建设会将重点放在「去重」(消除重复)上,而对于「复用」则没有足够的关注。很多企业都号称已经建成了各种各样成熟的平台,但是我们问心自问一下,有多少平台是业务驱动的?有多少前台产品团队又是自愿将自己的产品接入到平台上的?有多少平台建设者是在真正关注前台产品团队的平台用户体验?「去重」讲的更多是向后看,是技术驱动的;「复用」讲的更多是向前看,是业务驱动和用户驱动的。所以「去重」与「复用」虽然经常一起出现,一起被提及,但是谈论的完全不是一件事情,目的不同,难度也不同,做到「去重」已然非常困难,关注「复用」的就更是寥寥无几,所以:「复用」是中台更加关注的目标;「可复用性」和「易复用性」是衡量中台建设好坏的重要指标;「业务响应力」和「业务满意度」也才是考核中台建设进度的重要标准。而实现更好的复用,常常改进的方向有两个方面:一方面将更高抽象(例如业务模式级别)的通用业务逻辑通过抽象后下沉到中台,这样前台就会更轻,学习成本和开发维护成本更低,越能更快的适应业务变化;缺点是,抽象级别越高,越难被复用,需要架构师对于各业务有深入的理解和非常强的抽象能力。另一方面就是通过对于中台能力的SaaS化包装,减少前台团队发现中台能力和使用中台能力的阻力,甚至通过自助式(Self-Service)的方式就快速定位和使用中台能力。目前很多企业在尝试的内部API集市或是数据商店就是在这方面的努力和尝试。平台这里的平台主要是区别于大单体的应用或是系统。传统的企业数字化规划更多的是围绕业务架构,应用架构和数据架构展开。产出也是一个个基于应用和系统的数字化建设规划,例如要采购或是自建哪些具体的系统,例如ERP、CRM等。当然这个过程并没有什么问题,可以理解此时这些独立的系统就承载了企业的各种能力,由于企业各业务线统一使用一个应用或系统,也自然实现了能力的复用。但问题常常出现在两个方面:一个是大单体系统的业务响应力有限,缺少「柔性」,当业务发展到一定阶段后,必然产生大量定制化需求,随着内部定制化模块的比例逐渐上升,响应力成指数下降,成为业务的瓶颈点。另一个则是系统间的打通通常比较困难,容易形成业务孤岛和数据孤岛。所以越来越多的企业开始像互联网学习,以平台化的方式重塑企业IT架构,从而对于业务提供足够的「柔性」,来满足对于业务的快速响应和复用的需求。小结「企业级能力复用平台」这个定义虽然看起来简单,但经过这么长时间对于中台的实践与思考,我觉得如上文所述的这个定义背后所代表的意义是目前对中台价值的最贴切的阐释:「企业级」定义了中台的范围,区分开了单系统的服务化与微服务;「能力」定义了中台的主要承载对象,能力的抽象解释了各种各样中台的存在;「复用」定义了中台的核心价值,传统的平台化对于易复用性并没有给予足够的关注,中台的提出和兴起,让人们通过可复用性将目光更多的从平台内部转换到平台对于前台业务的支撑上;「平台」定义了中台的主要形式,区别于传统的应用系统拼凑的方式,通过对于更细力度能力的识别与平台化沉淀,实现企业能力的柔性复用,对于前台业务更好的支撑。有了定义后,如何建中台的思路也就豁然开朗:如果说中台是「企业级能力复用平台」的话,那中台化就是「利用平台化手段发现、沉淀与复用企业级能力的过程」。

March 29, 2019 · 1 min · jiezi

Koa源码阅读-详解

承接上一章Application函数module.exports = class Application extends Emitter { constructor() { super(); this.proxy = false; this.middleware = []; this.subdomainOffset = 2; this.env = process.env.NODE_ENV || ‘development’; // context,request,response是空对象,能访问相应原型对象上的属性和方法 this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }}EmitterApplication继承自EmitterEmitter主要是观察订阅者模式,在index.js可以这样写,会出现结果const app = new Koa();app.on(‘a’,(element)=>{ // 111 console.log(element)})app.emit(‘a’,111)学习一下Object.createconst a = {a:1,b:2}const b = Object.create(a); // {}console.log(b.proto === a) //trueconsole.log(b.a) //1a在b的原型链上,b能访问a的属性单元测试toJSON() { return only(this, [ ‘subdomainOffset’, ‘proxy’, ’env’ ]);}inspect() { return this.toJSON();}only函数返回新对象,并且只返回包含key,key的值不为nullmodule.exports = function(obj, keys){ obj = obj || {}; if (‘string’ == typeof keys) keys = keys.split(/ +/); return keys.reduce(function(ret, key){ if (null == obj[key]) return ret; ret[key] = obj[key]; return ret; }, {});};//执行app.inspect() describe(‘app.inspect()’, () => { it(‘should work’, () => { const app = new Koa(); const util = require(‘util’); const str = util.inspect(app); // 相等则通过 assert.equal("{ subdomainOffset: 2, proxy: false, env: ’test’ }", str); });});这里有个疑问,为什么在index.js 实例后的对象也只有这3个属性???listen调用listen执行callback listen() { debug(’listen’); const server = http.createServer(this.callback()); return server.listen.apply(server, arguments); } callback() { const fn = compose(this.middleware); if (!this.listeners(’error’).length) this.on(’error’, this.onerror); const handleRequest = (req, res) => { res.statusCode = 404; const ctx = this.createContext(req, res); const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); onFinished(res, onerror); fn(ctx).then(handleResponse).catch(onerror); }; return handleRequest; }在这里顺便说一下,原生 http创建一个服务是这样子滴const http = http.createServer((req, res) => { res.writeHead(200, { ‘Content-Type’: ’text/plain’ }); res.end(‘okay’);});http.listen(8000)createContextcreateContext(req, res) { // 以this.context为蓝本建立对象,此时引入的this.context已经两次实例了。request和response同理,并且作为属性挂在context上 const context = Object.create(this.context); const request = context.request = Object.create(this.request); const response = context.response = Object.create(this.response); // context request response都能拿到this,req,res context.app = request.app = response.app = this; context.req = request.req = response.req = req; context.res = request.res = response.res = res; // request response有context; request.ctx = response.ctx = context; // request和response能互相拿到各自的对象 request.response = response; response.request = request; // context 和 request的originalUrl有了req.url context.originalUrl = request.originalUrl = req.url; // context能拿到cookie context.cookies = new Cookies(req, res, { keys: this.keys, secure: request.secure }); request.ip = request.ips[0] || req.socket.remoteAddress || ‘’; context.accept = request.accept = accepts(req); context.state = {}; // 返回 context return context;}respond经过中间件的处理,respondfunction respond(ctx) { // allow bypassing koa if (false === ctx.respond) return; const res = ctx.res; if (!ctx.writable) return; //拿到body let body = ctx.body; const code = ctx.status; // 如果body不存在,那么返回默认not found // status body if (null == body) { body = ctx.message || String(code); if (!res.headersSent) { ctx.type = ’text’; ctx.length = Buffer.byteLength(body); } return res.end(body); } // 如果body是二进制,是字符串,Stream流,直接返回 // responses if (Buffer.isBuffer(body)) return res.end(body); if (‘string’ == typeof body) return res.end(body); if (body instanceof Stream) return body.pipe(res); // body: json json的处理 body = JSON.stringify(body); if (!res.headersSent) { ctx.length = Buffer.byteLength(body); } res.end(body);}context函数前面主要做了onerror和暴露属性的处理来看最重要的/*** context能够使用response下attachment redirect的方法, * 能够设置respons下status message的属性, * 能读headerSent和writable的值*/delegate(proto, ‘response’) .method(‘attachment’) .method(‘redirect’) .method(‘remove’) .method(‘vary’) .method(‘set’) .method(‘append’) .method(‘flushHeaders’) .access(‘status’) .access(‘message’) .access(‘body’) .access(’length’) .access(’type’) .access(’lastModified’) .access(’etag’) .getter(‘headerSent’) .getter(‘writable’);function Delegator(proto, target) { // 调用函数即返回实例后的自己,每调用一次都是不同的this,好处能够调用原型对象上的方法 if (!(this instanceof Delegator)) return new Delegator(proto, target); this.proto = proto; this.target = target; this.methods = []; this.getters = []; this.setters = []; this.fluents = [];}// 把target的上的方法挂载在proto上,而执行的this却是target的Delegator.prototype.method = function(name){ var proto = this.proto; var target = this.target; this.methods.push(name); proto[name] = function(){ return this[target][name].apply(this[target], arguments); }; return this;};proto[name]能读取target[name]Delegator.prototype.getter = function(name){ var proto = this.proto; var target = this.target; this.getters.push(name); proto.defineGetter(name, function(){ return this[target][name]; }); return this;};proto[name]能设置target[name]Delegator.prototype.setter = function(name){ var proto = this.proto; var target = this.target; this.setters.push(name); proto.defineSetter(name, function(val){ return this[target][name] = val; }); return this;};可读可写Delegator.prototype.access = function(name){ return this.getter(name).setter(name);};request对象一句话,request对象主要做了拿到请求的信息我们看几个例子// 返回this.req.headers信息get header() { return this.req.headers;},get(field) { const req = this.req; switch (field = field.toLowerCase()) { case ‘referer’: case ‘referrer’: return req.headers.referrer || req.headers.referer || ‘’; default: return req.headers[field] || ‘’;}},// 获取请求头的内容铲毒get length() { const len = this.get(‘Content-Length’); if (len == ‘’) return; return ~~len;},//获得query信息get querystring() { if (!this.req) return ‘’; return parse(this.req).query || ‘’;}response对象response对象主要做了相应头的的信息set(field, val) { if (2 == arguments.length) { if (Array.isArray(val)) val = val.map(String); else val = String(val); this.res.setHeader(field, val); } else { for (const key in field) { this.set(key, field[key]); } }},set lastModified(val) { if (‘string’ == typeof val) val = new Date(val); this.set(‘Last-Modified’, val.toUTCString()); }, set etag(val) { if (!/^(W/)?"/.test(val)) val = "${val}"; this.set(‘ETag’, val);}redirect(url, alt) { // location if (‘back’ == url) url = this.ctx.get(‘Referrer’) || alt || ‘/’; this.set(‘Location’, url); // status if (!statuses.redirect[this.status]) this.status = 302; // html if (this.ctx.accepts(‘html’)) { url = escape(url); this.type = ’text/html; charset=utf-8’; this.body = Redirecting to &lt;a href="${url}"&gt;${url}&lt;/a&gt;.; return; } // text this.type = ’text/plain; charset=utf-8’; this.body = Redirecting to ${url}.;}结合官网看更好以上,????谢谢你的阅读,你的点赞是我写文章的动力。git博客地址 ...

March 18, 2019 · 4 min · jiezi

koa-rapid-router超越koa-router性能的100多倍

对比如果使用nodejs来搭建Service服务,那么我们首选express或者koa,而fastify告诉我们一个数据:FrameworkVersionRouter?Requests/sechapi18.1.0✓29,998Express4.16.4✓38,510Restify8.0.0✓39,331Koa2.7.0✗50,933Fastify2.0.0✓76,835- http.Server10.15.2✗71,768从数据中可以看出,Koa的性能远大于express。当然,它的测试基于简单的单路由测试。不过由此我们可以看到fastify的性能远大于Koa。相信使用过fastify的小伙伴都会对它的性能速度感到惊讶。其实原理很简单,就是请求的URL快速匹配Callback。如何做到,理论上也很简单,就是找寻它的最短路径来匹配。所以一般能够快速匹配的,都是通过空间换时间的方式来达到效果。这里,我还想告诉大家一点,fastify并不是最快的。主角今天的主角就是koa-rapid-router。为什么我们会以KOA打头呢?因为这篇文章目的其实是与koa-router的比较,而不是fastify。而此路由架构,也是为了在使用KOA的时候能够接近fastify的性能(经过测试,没有超过fastify,KOA本身的性能也有问题)。接下来,我们会抛出一系列的测试数据来告诉大家Koa-router的性能是何其糟糕。我们分别使用这样的原则来测试向每个架构注入10000个静态路由,测试最末尾的那个。使用相同的测试命令 autocannon -c 100 -d 40 -p 10 <url>对比静态路由和动态路由性能上的差距测试代码全部在这里静态路由对比我们写入如下的代码for (let i = 0; i < 10000; i++) { router.get(’/uuid/’ + (i + 1), async (ctx) => ctx.body = ‘ok’); vrouter.get(’/uuid/’ + (i + 1), (res) => res.end(‘ok’)); route_2.get(’/interface/api/uuid/’ + (i + 1), async (ctx) => ctx.body = ‘ok’); fastify.get(’/interface/api/uuid/’ + (i + 1), (request, reply) => reply.send(‘ok’));}接着进行测试 npm run test,得到数据:Preview:ResultscommandarchitectureLatencyReq/SecBytes/Sectest:koakoa + koa-router245.07 ms394.2556 kBtest:fastfastify1.96 ms493247 MBtest:rapidkoa + koa-rapid-router2.17 ms44828.86.37 MBtest:httphttp + koa-rapid-router1.64 ms58911.25.95 MB从数据上得出结论,koa-router在有10000个路由的时候,它的性能超级低下,只能达到平均的394.25,意思就是每秒只能处理394.25个请求,多来就不行。而koa + koa-rapid-router则处理到了44828.8个。同样是使用KOA模型,差距很明显。我做了分析,主要是koa-router内部循环比较多导致的。在10000个请求循环过程中,效率非常低下。而我们如何做到达到44828.8的性能,主要我们在内存中维护了一份静态路由列表,能让程序以最快的速度找到我们需要的callback。对比fastify,可以看出,KOA本身性能的问题很大。大家一定会问,对比静态路由Koa-router肯定没有优势,那么我们来对比动态路由。动态路由对比我们写入如下代码router.get(’/zzz/{a:number}’, async (ctx) => ctx.body = ‘ok’);vrouter.get(’/zzz/{a:number}’, (res) => res.end(‘ok’));route_2.get(’/interface/api/zzz/:a(\d+)’, async (ctx) => ctx.body = ‘ok’);fastify.get(’/interface/api/zzz/:a’, (request, reply) => reply.send(‘ok’));我们将这段代码加入到10000个静态路由代码的后面,修正测试的路径,我们得到如下数据:ResultscommandarchitectureLatencyReq/SecBytes/Sectest:koakoa + koa-router220.29 ms441.7562.7 kBtest:fastfastify1.9 ms50988.657.24 MBtest:rapidkoa + koa-rapid-router2.32 ms41961.65.96 MBtest:httphttp + koa-rapid-router1.82 ms53160.85.37 MB动态路由的对比从一定程度上可以看出koa-router的糟糕之处,不论是静态路由还是动态路由,它都基本稳定在400左右的qps。而koa + koa-rapid-router稍有下降,fastify一如既往的稳定。但是从http + koa-rapid-router模型上看,rapid完全超越fastify。koa + koa-rapid-router与koa + koa-router对比,性能大概是100倍的样子。如果我们可以这样认定,如果我们需要高并发,但是还是使用koa的生态的话,koa + koa-rapid-router是最佳选择。如果我们完全追求性能,不考虑生态的话,那么fastify首选。有人会问,那么为什么http + koa-rapid-router不使用,它可是比fastify更快的路由?那是因为,http + koa-rapid-router需要单独建立生态,暂时无法做到大规模使用,也许到最后,我们可以用上新的基于koa-rapid-router的企业级服务架构。这也是我正在思考的。结尾我们所造的轮子的性能是不可能超越http模块的性能,我们只能无限接近它。这就像光速的道理一样,只能接近,无法等于。高性能架构主要还是在于理念模型,跟数学息息相关。项目开源在 https://github.com/cevio/koa-rapid-router 有兴趣的小伙伴关注下,谢谢。 ...

March 16, 2019 · 1 min · jiezi

Express与Koa中间件机制分析(一)

提到 Node.js 开发,不得不提目前炙手可热的2大框架 Express 和 Koa。 Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。目前使用人数众多。Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。相信对这两大框架有一些了解的人都或多或少的会了解其中间件机制,Express 为线型模型,而 Koa 则为洋葱型模型。这个系列的博客主要讲解 Express 和 Koa 的中间件机制,本篇将主要讲解 Express 的中间件机制。Express 中间件connect 曾经是 express 3.x 之前的核心,而 express 4.x 已经把 connect 移除,在 express 中自己实现了 connect 的接口,所以我们本篇中的源码解释将直接使用 connect 源码。示例下面将使用 Express 实现一个简单的 demo 来进行中间件机制的讲解var express = require(’express’);var app = express();app.use(function (req, res, next) { console.log(‘第一个中间件start’); setTimeout(() => { next(); }, 1000) console.log(‘第一个中间件end’);});app.use(function (req, res, next) { console.log(‘第二个中间件start’); setTimeout(() => { next(); }, 1000) console.log(‘第二个中间件end’);});app.use(’/foo’, function (req, res, next) { console.log(‘接口逻辑start’); next(); console.log(‘接口逻辑end’);});app.listen(4000);此时的输出比较符合我们对 Express 线性的理解,其输出为第一个中间件start第一个中间件end第二个中间件start第二个中间件end接口逻辑start接口逻辑end但是,如果我们取消掉中间件内部的异步处理直接调用 next()var express = require(’express’);var app = express();app.use(function (req, res, next) { console.log(‘第一个中间件start’); next() console.log(‘第一个中间件end’);});app.use(function (req, res, next) { console.log(‘第二个中间件start’); next() console.log(‘第二个中间件end’);});app.use(’/foo’, function (req, res, next) { console.log(‘接口逻辑start’); next(); console.log(‘接口逻辑end’);});app.listen(4000);输出结果为第一个中间件start第二个中间件start接口逻辑start接口逻辑end第二个中间件end第一个中间件end这种结果不是和 Koa 的输出很相似吗?是的,但是它和剥洋葱模型还是不一样的,其实这种输出的结果是由于代码的同步运行导致的,并不是说 Express 不是线性的模型。当我们的中间件内没有进行异步操作时,其实我们的代码最后是以下面这种方式运行的app.use(function middleware1(req, res, next) { console.log(‘第一个中间件start’) // next() (function (req, res, next) { console.log(‘第二个中间件start’) // next() (function (req, res, next) { console.log(‘接口逻辑start’) // next() (function handler(req, res, next) { // do something })() console.log(‘接口逻辑end’) })() console.log(‘第二个中间件end’) })() console.log(‘第一个中间件end’)})导致这种运行方式的其实就是 connect 的实现方式,接下来我们进行其源码的解析connect 源码解析connect的源码仅有200多行,但是这里讲解只选择其中部分核心代码,并非完整代码,查看全部代码请移步 github中间件的挂载主要依赖 proto.use 和 proto.handle,这里我们删掉部分 if 判断以使我们更专注于其内部原理的实现proto.use = function use(route, fn) { var handle = fn; var path = route; // 这里是对直接填入回调函数的进行容错处理 // default route to ‘/’ if (typeof route !== ‘string’) { handle = route; path = ‘/’; } . . . this.stack.push({ route: path, handle: handle }); return this;};proto.use 主要将我们需要挂载的中间件存储在其自身 stack 属性上,同时进行部分兼容处理,这一块比较容易理解。其中间件机制的核心为 proto.handle 内部 next 方法的实现。proto.handle = function handle(req, res, out) { var index = 0; var stack = this.stack; function next(err) { // next callback var layer = stack[index++]; // all done if (!layer) { defer(done, err); return; } // route data var path = parseUrl(req).pathname || ‘/’; var route = layer.route; // skip this layer if the route doesn’t match if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) { return next(err); } // call the layer handle call(layer.handle, route, err, req, res, next); } next();};在删除到部分非核心代码后,可以清晰的看到,proto.handle 的核心就是 next 方法的实现和递归调用,对存在于 stack 中的中间件取出、执行。这里便可以解释上文中异步和非异步过程中所输出的结果的差异了。当有异步代码时,将会直接跳过继续执行,此时的 next 方法并未执行,需要等待当前队列中的事件全部执行完毕,所以此时我们输出的数据是线性的。当 next 方法直接执行时,本质上所有的代码都已经为同步,所以层层嵌套,最外层的肯定会在最后,输出了类似剥洋葱模型的结果。总结connect 的实现其基本原理是维护一个 stack 数组,将所需要挂载的中间件处理后全部 push 到数组内,之后在数组内循环执行 next 方法,直至所有中间件挂载完毕,当然这过程中会做一些异常、兼容等的处理。 ...

March 14, 2019 · 2 min · jiezi

使用 Nuxt.js 快速搭建服务端渲染(SSR) 应用

安装 nuxt.jsNuxt.js 官方提功了两种方法来进行项目的初始化,一种是使用Nuxt.js团队的脚手架工具 create-nuxt-app ,一种是根据自己的需求自由配置 使用脚手架适合新手,对 nodejs 后台框架有所了解;按照自己需求自由配置,需要对如何配置 webpack 以及 nodejs 后台框架有所了解。 两种方式比较下就是原生和插件的区别。使用脚手架安装需要有 nodejs 或者 yarn 环境,推荐使用 vscode 自带的控制台输入命令行命令进行操作在有了环境之后直接输入以下命令就可以直接创建一个项目(npx 在npm 5.2.0默认安装,使用最新稳定nodejs环境不用考虑有没有)npx create-nuxt-app <项目名>#或者用yarnyarn create nuxt-app <项目名>之后他会提示你进行一些选择 1.项目名 在这里可以设置项目名,亦可以之后在 package.js 中设置 name 属性,一般是在输入上面命令时的项目名,不需要修改直接回车就好2.项目描述这里是关于项目的描述,比如是做什么的,也可以之后在 package.js 中设置 description 属性3.选择服务器端框架 看自己习惯使用什么了,一般 Express Koa 居多4.扩展插件选择 axios EsLint Prettieraxios 发送HTTP请求EsLint 在保存时代码规范和错误检查自己的代码。Prettier 在保存时格式化/美化自己的代码。5.选择 UI 框架 UI 框架方便快速开发,提供了很多现成的样式,近几年听到最多的就是 Element UI 6.选择测试框架测试框架是用来检测程序有没有到达预期的目的,有没有出错,这里暂时用不到,所以选择 none 就好7.选择渲染模式这里分单页应用(spa)以及普遍的方式(Universal),单页应用有很多路由但是页面只有一个,所有能看到的页面都是 js 即时生成的 dom,第二种是在服务器生成 html ,有多少路由就有多少页面。 使用 nuxt 就是为了解决 SEO 的问题,使其实现所有网站路径完全被收录8.作者这个也可以之后在 package.js 中设置 author 属性 9.选择包管理工具这里选择那个都可以,看自己习惯用哪个10.选择完成开始安装11.安装完成提示信息项目目录关于如何根据自己的需求自由配置,这里不讲,有需要自由配置的一般都不是新手了,推荐看看官方文档添加其他常用功能除了 nuxt 脚手架自带的,我们还需要其他配置,ES6的编译 ,CSS的预处理,其他的用到了再添加安装 babelyarn add babel-cli babel-preset-env配置文件.babelrc{ “presets”: [“env”]}安装 scssyarn add node-sass yarn add sass-loader之后只需要在 vue 文件的 style 标签加一条属性声明下就好<style lang=“sass”></style># or<style lang=“scss”></style> ...

March 13, 2019 · 1 min · jiezi

你不知道的CORS跨域资源共享

了解下同源策略源(origin):就是协议、域名和端口号;同源: 就是源相同,即协议、域名和端口完全相同;同源策略:同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源;同源策略的分类:DOM 同源策略:即针对于DOM,禁止对不同源页面的DOM进行操作;如不同域名的 iframe 是限制互相访问。XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。不受同源策略限制:页面中的链接,重定向以及表单提交(因为表单提交,数据提交到action域后,本身页面就和其没有关系了,不会管请求结果,后面操作都交给了action里面的域)是不会受到同源策略限制的。资源的引入不受限制,但是js不能读写加载的内容:如嵌入到页面中的<script src="…"></script>,<img>,<link>,<iframe>等为什么要跨域限制如果没有 DOM 同源策略:那么就没有啥xss的研究了,因为你的网站将不是你的网站,而是大家的,谁都可以写个代码操作你的网站界面如果没有XMLHttpRequest 同源策略,那么就可以很轻易的进行CSRF(跨站请求伪造)攻击:用户登录了自己的网站页面 a.com,cookie中添加了用户标识。用户浏览了恶意页面 b.com,执行了页面中的恶意 AJAX 请求代码。b.com 向 a.com发起 AJAX HTTP 请求,请求会默认把 a.com对应cookie也同时发送过去。a.com从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据;数据就泄露了。而且由于Ajax在后台执行,这一过程用户是无法感知的。(附)有了XMLHttpRequest 同源策略就可以限制CSRF攻击?别忘了还有不受同源策略的:表单提交和资源引入,(安全问题下期在研究)跨域决解方案JSONP 跨域:借鉴于 script 标签不受浏览器同源策略的影响,允许跨域引用资源;因此可以通过动态创建 script 标签,然后利用 src 属性进行跨域;缺点:所有网站都可以拿到数据,存在安全性问题,需要网站双方商议基础token的身份验证。只能是GET,不能POST。可能被注入恶意代码,篡改页面内容,可以采用字符串过滤来规避此问题。服务器代理:浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。document.domain、window.name 、location.hash:借助于iframe决解DOM同源策略postMessage:决解DOM同源策略,新方案CORS(跨域资源共享):这里讲的重点CORS(跨域资源共享)HTML5 提供的标准跨域解决方案,是一个由浏览器共同遵循的一套控制策略,通过HTTP的Header来进行交互;主要通过后端来设置CORS配置项。CORS简单使用之前说得CORS跨域,嗯嗯,后端设置Access-Control-Allow-Origin:|[或具体的域名]就好了;第一次尝试:app.use(async(ctx,next) => { ctx.set({ “Access-Control-Allow-Origin”: “http://localhost:8088”})发现有些请求可以成功,但是有些还是会报错:请求被同源策略阻止,预请求的响应没有通过检查:http返回的不是ok?并且发现发送的是OPTIONS请求:发现:CORS规范将请求分为两种类型,一种是简单请求,另外一种是带预检的非简单请求简单请求和非简单请求浏览器发送跨域请求判断方式:浏览器在发送跨域请求的时候,会先判断下是简单请求还是非简单请求,如果是简单请求,就先执行服务端程序,然后浏览器才会判断是否跨域;而对于非简单请求,浏览器会在发送实际请求之前先发送一个OPTIONS的HTTP请求来判断服务器是否能接受该跨域请求;如果不能接受的话,浏览器会直接阻止接下来实际请求的发生。什么是简单请求请求方法是如下之一:GETHEADPOST所有的Header都只包含如下列表中(没有自定义header):Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma除此之外都是非简单请求CORS非简单请求配置须知正如上图报错显示,对于非简单请求,浏览器会先发送options预检,预检通过后才会发送真是的请求;发送options预检请求将关于接下来的真实请求的信息给服务器:Origin:请求的源域信息Access-Control-Request-Method:接下来的请求类型,如POST、GET等Access-Control-Request-Headers:接下来的请求中包含的用户显式设置的Header列表服务器端收到请求之后,会根据附带的信息来判断是否允许该跨域请求,通过Header返回信息:Access-Control-Allow-Origin:允许跨域的Origin列表Access-Control-Allow-Methods:允许跨域的方法列表Access-Control-Allow-Headers:允许跨域的Header列表,防止遗漏Header,因此建议没有特殊需求的情况下设置为Access-Control-Expose-Headers:允许暴露给JavaScript代码的Header列表Access-Control-Max-Age:最大的浏览器预检请求缓存时间,单位为sCORS完整配置koa配置CORS跨域资源共享中间件:const cors = (origin) => { return async (ctx, next) => { ctx.set({ “Access-Control-Allow-Origin”: origin, //允许的源 }) // 预检请求 if (ctx.request.method == “OPTIONS”) { ctx.set({ ‘Access-Control-Allow-Methods’: ‘OPTIONS,HEAD,DELETE,GET,PUT,POST’, //支持跨域的方法 ‘Access-Control-Allow-Headers’: ‘’, //允许的头 ‘Access-Control-Max-Age’:10000, // 预检请求缓存时间 // 如果服务器设置Access-Control-Allow-Credentials为true,那么就不能再设置Access-Control-Allow-Origin为*,必须用具体的域名 ‘Access-Control-Allow-Credentials’:true // 跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息) }); ctx.send(null, ‘预检请求’) } else { // 真实请求 await next() } }}export default cors现在不管是简单请求还是非简单请求都可以跨域访问啦~跨域时如何处理cookiecookie:我们知道http时无状态的,所以在维持用户状态时,我们一般会使用cookie;cookie每次同源请求都会携带;但是跨域时cookie是不会进行携带发送的;问题:由于cookie对于不同源是不能进行操作的;这就导致,服务器无法进行cookie设置,浏览器也没法携带给服务器(场景:用户登录进行登录操作后,发现响应中有set-cookie但是,浏览器cookie并没有相应的cookie)决解:浏览器请求设置withCredentials为true即可让该跨域请求携带 Cookie;使用axios配置axios.defaults.withCredentials = true服务器设置Access-Control-Allow-Credentials=true允许跨域请求携带 Cookie“积跬步、行千里”—— 持续更新中~,喜欢的话留下个赞和关注哦!往期经典好文:服务器(CentOS)安装配置mongodbKoa日志中间件封装开发(log4js)团队合作必备的Git操作使用pm2部署node生产环境 ...

March 11, 2019 · 1 min · jiezi

使用pm2部署node生产环境

一、PM2是什么是可以用于生产环境的Nodejs的进程管理工具,并且它内置一个负载均衡。它不仅可以保证服务不会中断一直在线,并且提供0秒reload功能,还有其他一系列进程管理、监控功能。并且使用起来非常简单。嗯嗯,最好的用处就是监控我们的生产环境下的node程序运行状态,让它给我们日以继日的处于工作状态。pm2官方文档二、为森么要使用pm2原始社会的我们开发node服务端程序一般过程:编写好node程序app.js,运行node app.js;或者写入script使用npm运行;打开浏览器访问;好像需要修改内容,浏览器对修改的内容没有显示出来?->node app.js->再次运行;浏览器忽然访问不到服务,好像出错啦?重启下->node app.js->再次运行;哎呀开了好多控制台窗口,一不小心关闭了,服务又访问不到了,继续打开控制台->node app.js->再次运行;好崩溃!好像有个工具nodemon;安装使用nodemon app.js;哇,可以自动监听文件修改变化自动重启,但是关闭控制台服务还是会被摧毁。通过这个很常用的场景,我们了解到要避免这些麻烦一个服务器至少需要有:后台运行和自动重启,这两个能力。再来看看使用pm2可拥有的能力:日志管理;两种日志,pm2系统日志与管理的进程日志,默认会把进程的控制台输出记录到日志中;负载均衡:PM2可以通过创建共享同一服务器端口的多个子进程来扩展您的应用程序。这样做还允许以零秒停机时间重新启动应用程序。终端监控:可以在终端中监控应用程序并检查应用程序运行状况(CPU使用率,使用的内存,请求/分钟等)。SSH部署:自动部署,避免逐个在所有服务器中进行ssh。静态服务:支持静态服务器功能支持开发调试模式,非后台运行,pm2-dev start <appName>;。。。。。太过强大!pm2常用命令启动服务pm2 start <script_file|config_file> [options] 启动指定应用pm2 start app.js //启动app.js应用pm2 start app.js –name app //启动应用并设置namepm2 start app.sh //脚本启动pm2 start app.js –watch //监听模式启动,当文件发生变化,自动重启//max 表示PM2将自动检测可用CPU的数量并运行尽可能多的进程//max可以自定义,如果是4核CPU,设置为2则占用2个pm2 start app.js -i max //启用群集模式(自动负载均衡)pm2-dev start … // 开发模式启动,即不启用后台运行查看启动列表pm2 list显示应用程序详细信息pm2 show <appName> [options] 显示指定应用详情pm2 show [Name] //根据name查看pm2 show [ID] //根据id查看停止指定应用pm2 stop <appName> [options] 停止指定应用pm2 stop all //停止所有应用pm2 stop [AppName] //根据应用名停止指定应用pm2 stop [ID] //根据应用id停止指定应用重启应用pm2 reload|restart <appName> [options] 重启指定应用pm2 restart app.js //同时杀死并重启所有进程,短时间内服务不可用,生成环境慎用pm2 reload app.js //重新启动所有进程,0秒重启,始终保持至少一个进程在运行pm2 gracefulReload all //以群集模式重新加载所有应用程序启动静态服务器pm2 serve ./dist 8080将目录dist作为静态服务器根目录,端口为8080删除应用pm2 delete <appName> [options] 删除指定应用;如果修改了应用配置行为,需要先删除应用,重新启动后方才会生效,如修改脚本入口文件;pm2 delete all //关闭并删除应用pm2 delete [AppName] //根据应用名关闭并删除应用pm2 delete [ID] //根据应用ID关闭并删除应用pm2 kill 杀掉pm2管理的所有进程;pm2 logs <appName> 查看指定应用的日志,即标准输出和标准错误pm2 logs //查看所有应用日志pm2 logs [Name] //根据指定应用名查看应用日志pm2 logs [ID] //根据指定应用ID查看应用日志pm2 monit 监控各个应用进程cpu和memory使用情况;PM2配置方式命令生产默认示例配置文件pm2 ecosystem或pm2 init,运行默认会生成ecosystem.config.js配置文件module.exports = { apps: [ { name: ‘back-Api’, //应用名 script: ‘./server/start.js’, //应用文件位置 env: { PM2_SERVE_PATH: “./apidoc”, //静态服务路径 PM2_SERVE_PORT: 8080, //静态服务器访问端口 NODE_ENV: ‘development’ //启动默认模式 }, env_production : { NODE_ENV: ‘production’ //使用production模式 pm2 start ecosystem.config.js –env production }, instances:“max”, //将应用程序分布在所有CPU核心上,可以是整数或负数 instance_var: “INSTANCE_ID”, exec_mode: “cluster”, watch:[ “server”, ], //监听模式,不能单纯的设置为true,易导致无限重启,因为日志文件在变化,需要排除对其的监听 merge_logs: true, //集群情况下,可以合并日志 } ], deploy: { production : { user: ’node’, //ssh 用户 host: ‘212.83.163.1’, //ssh 地址 ref: ‘origin/master’, //GIT远程/分支 repo: ‘git@github.com:repo.git’, //git地址 path: ‘/var/www/production’, //服务器文件路径 “post-deploy”: ’npm install && pm2 reload ecosystem.config.js –env production’ //部署后的动作 } }}; 自定义json配置文件如:processes.json;启动pm2 start processes.json { “apps”: [{ “name”: “app”, //名称 “script”: “./”, //程序入口 “cwd”: “./”, //根目录 “watch”:[ “views” ],//需要监控的目录 “error_file”:"./logs/err.log",//错误输出日志 “out_file”:"./logs/out.log", //日志 “log_date_format”:“YYYY-MM-DD HH:mm Z” //日期格式 }] }pm2常用配置项解析1. apps:json结构,apps是一个数组,每一个数组成员就是对应一个pm2中运行的应用2. name:应用程序名称"app"3. cwd:应用程序所在的目录"./“4. script:应用程序的脚本路径”./“5. log_date_format: 日志文件名输出日期格式"YYYY-MM-DD HH:mm Z"6. error_file:自定义应用程序的错误日志文件”./logs/app-err.log",7. out_file:自定义应用程序日志文件"./logs/app-out.log"8. instances: 应用启动实例个数,仅在cluster模式有效 默认为fork;或者 max9. min_uptime:最小运行时间,这里设置的是60s即如果应用程序在60s内退出,pm2会认为程序异常退出,此时触发重启max_restarts设置数量10. max_restarts:设置应用程序异常退出重启的次数,默认15次(从0开始计数)11. cron_restart:定时启动,解决重启能解决的问题12. watch:是否启用监控模式,默认是false。如果设置成true,当应用程序变动时,pm2会自动重载。这里也可以设置你要监控的文件。13. “ignore_watch”: [ // 不用监听的文件 “node_modules”, “logs” ],13. merge_logs:// 设置追加日志而不是新建日志14. exec_interpreter:应用程序的脚本类型,这里使用的shell,默认是nodejs15. exec_mode:应用程序启动模式,这里设置的是cluster_mode(集群),默认是fork16. autorestart:启用/禁用应用程序崩溃或退出时自动重启,默认为true, 发生异常的情况下自动重启17. vizion:启用/禁用vizion特性(版本控制)18. “args”: “”, // 传递给脚本的参数19. env: { PM2_SERVE_PATH: “./apidoc”, //静态服务路径 PM2_SERVE_PORT: 8080, //静态服务器访问端口 NODE_ENV: ‘development’ //启动默认模式 },20. env_production : { NODE_ENV: ‘production’ //使用production模式 pm2 start ecosystem.config.js –env production },pm2配合log4js处理日志pm2启动时通常会发现log4js记录不到日志信息;决解方案,安装pm2的pm2-intercom进程间通信模块在log4js的配置文件logger.js里添加如下命令:pm2: true, pm2InstanceVar: ‘INSTANCE_ID’ pm2配置文件中添加"instance_var": “INSTANCE_ID”, // 添加这一行 字段发现如果没有设置群集模式"exec_mode": “cluster”,也会记录不到;其他log4js日志配置使用详情Koa日志中间件封装开发(log4js)“积跬步、行千里”—— 持续更新中~,喜欢的话留下个赞和关注哦!往期经典好文:团队合作必备的Git操作谈谈Js前端模块化规范 ...

March 8, 2019 · 2 min · jiezi

Koa日志中间件封装开发

Koa日志中间件开发封装对于一个服务器应用来说,日志的记录是必不可少的,我们需要使用其记录项目程序每天都做了什么,什么时候发生过错误,发生过什么错误等等,便于日后回顾、实时掌握服务器的运行状态,还原问题场景。日志的作用记录服务器程序运行状态;帮助开发者快速捕获错误,定位以及决解故障。日志中间件开发工具log4js在node当中没有自带的日志模块,所以需要使用第三方模块使用模块:log4js安装: npm i log4js -Slogsjs官方文档日志分类:访问日志: 记录客户端对项目的访问,主要是 http 请求。用于帮助改进和提升网站的性能和用户体验;应用日志: 项目标记和记录位置打印的日志,包括出现异常情况,方便查询项目的运行状态和定位bug(包含了debug、info、warn 和 error等级别)。日志等级如果配置了日志等级,则其只能记录日志等级比设置的更高级别的日志信息日志等级图如配置level: ’error’,则只能输出error,fatar,mark级别的日志信息日志中间件开发设置需要日志需要记录的信息段(log_info.js)export default (ctx, message, commonInfo) => { const { method, // 请求方法 url, // 请求链接 host, // 发送请求的客户端的host headers // 请求中的headers } = ctx.request; const client = { method, url, host, message, referer: headers[‘referer’], // 请求的源地址 userAgent: headers[‘user-agent’] // 客户端信息 设备及浏览器信息 } return JSON.stringify(Object.assign(commonInfo, client));}设置通用获取配置后的log4js对象(logger.js)const getLog = ({env, appLogLevel, dir}, name) => { //log4js基本说明配置项,可自定义设置键名,用于categories.appenders自定义选取 let appenders = { // 自定义配置项1 cheese: { type: ‘dateFile’, //输出日志类型 filename: ${dir}/task, //输出日志路径 pattern: ‘-yyyy-MM-dd.log’, //日志文件后缀名(task-2019-03-08.log) alwaysIncludePattern: true } } // 如果为开发环境配置在控制台上打印信息 if (env === “dev” || env === “local” || env === “development”) { // 自定义配置项2 appenders.out = { type: “stdout” } } // log4js配置 let config = { appenders, //作为getLogger方法获取log对象的键名,default为默认使用 categories: { default: { appenders: Object.keys(appenders), // 取appenders中的说有配置项 level: appLogLevel } } } log4js.configure(config) //使用配置项 return log4js.getLogger(name)// 这个cheese参数值先会在categories中找,找不到就会默认使用default对应的appenders,信息会输出到yyyyMMdd-out.log}log日志中间件开发(logger.js)export default (options) => { const contextLogger = {}; //后期赋值给ctx.log const { env, appLogLevel, dir, serverIp, projectName } = Object.assign({}, baseInfo, options || {}); // 取出通用配置(项目名,服务器请求IP) const commonInfo = { projectName, serverIp }; const logger = getLog({env, appLogLevel, dir},‘cheese’); return async (ctx, next) => { const start = Date.now(); //日志记录开始时间 // 将日志类型赋值ctx.log,后期中间件特殊位置需要记录日志,可直接使用ctx.log.error(err)记录不同类型日志 methods.forEach((method, i) => { contextLogger[method] = (message) => { logger[method](logInfo(ctx, message, commonInfo)) } }) ctx.log = contextLogger; // 执行中间件 await next() // 结束时间 const responseTime = Date.now() - start; // 将执行时间记录logger.info logger.info(logInfo(ctx, { responseTime: 响应时间为${responseTime/1000}s }, commonInfo) ) }}中间件使用(app.js)import Log from ‘../log/logger’;…app.use(Log({ env: app.env, // koa 提供的环境变量 projectName: ‘back-API’, appLogLevel: ‘debug’, dir: ’logs’, serverIp: ip.address() }))其他特殊位置需要日志记录使用ctx.log.error(err.stack); //记录错误日志ctx.log.info(err.stack); // 记录信息日志ctx.log.warn(err.stack); // 记录警告日志…运行截图log4js使用基本配置和流程解析设置配置项,// 配置项形式{ appenders:{ [自定义key]:{} }, categories:{ }}// 配置config: { appenders:{ // 每一个属性可以看作为一个配置模块 out: { type: ‘dateFile’, //输出日志类型 filename: log/task, //输出日志路径 pattern: ‘-yyyy-MM-dd.log’, //日志文件后缀名(task-2019-03-08.log) …//具体配置看官网 }, error: { type: ‘dateFile’, filename: ’log/error’, pattern: ‘-yyyy-MM-dd.log’’, “alwaysIncludePattern”: true }, stdout: { type: ‘stdout’ }, //在控制台上打印信息 }, // 通过categories来取出给log4js按需配置,返回配置后的log4js对象,每个属性配置相当于一个不同的log4js配置对象入口;default为默认入口(getLogger()找不到入口时默认使用default) categories:{ // 配置默认入口,使用appenders中的’stdout’,‘out’配置模块,记录trace以上等级日志 default: { appenders: [‘stdout’,‘out’], level: ’trace’ }, // 配置error门入口,使用appenders中的’stdout’,’err’配置模块,记录error以上等级日志 error : {appenders: [’err’], level: ’error’} }}使用let logger_out = log4js.getLogger(‘app’);log4js.getLogger(‘app’)查找特定log4js对象流程:先根据app参数值在categories中找,发现没有app,然后就会默认使用default对应的appenders进行配置,即信息会输出到log/task-yyyy-mm-dd.log文件中,并且会输出到控制台使用let logger_out = log4js.getLogger(’error’);根据error参数值在categories中找,发现没有拥有error配置,然后就会使用error对应的appenders进行配置,即信息会输出到log/error-yyyy-mm-dd.log文件中,因为error的配置项appenders中没有使用stdout模块,所以信息不会输出到控制台后期考虑是否需要对日志进行数据库存储,进行日志持久化;考虑到不可能对日志记录后一直保存,对于一个月或者一周以前的日志可能没有必要在进行存储了,需要开发设置定时自动删除过期日志文件(获数据库日志记录)参考:log4js配置Node.js 之 log4js 完全讲解学无止境,积累点滴;把小简单变成大简单。如果这篇文章对你有所收获,请留在你的小心心!往期文章推荐:React你知多少Git常用操作 ...

March 8, 2019 · 2 min · jiezi

近期前端发展计划

February 24, 2019 · 0 min · jiezi

nuxt+koa+ant-design-vue+mongoose开发的微信公众号文章管理

github欢迎star https://github.com/aoping/nux…关键词服务端渲染 微信公众号开发 nuxt koa ant-design mongoose功能介绍后台登录 注册 公众号增删改查 文章增删改查公众号自动回复 网页授权 自定义分享 设置菜单等界面展示首页登录注册页公众号管理页文章管理页移动端

February 22, 2019 · 1 min · jiezi

react项目运用BrowserRouter上线后在非根路由情况下刷新出现404问题的解决方法 -- Koa

问题先上react App代码class App extends React.Component { render() { return ( <BrowserRouter> <div> <Link to="/lottery">to lottery</Link> <Route path="/lottery" component={Lottery} exact /> <Route path="/a" render={() => <div>in a</div>} exact /> </div> </BrowserRouter> ) }}点击后可以正常跳转至lottery路由,忽略丑陋的界面…这时刷新就404找不到页面了原因react的BrowserRouter用的是Html5提供的HistoryApi方法,Link组件实际上是调用了History.pushState(),然后通过监听history状态去展示或者隐藏组件。所以当刷新时,也就是向服务器发送了这个路径的请求,而服务器上实际是没有对这个路径的请求做任何处理的,故返回的是404。解决方法 – 用的是koa搭建服务器app.use(views(path.resolve(__dirname, ‘../www/dist’), {extension: ‘html’}))app.use(async (ctx, next) => { console.log(ctx.path) await next()})app.use(router.routes())router.all(/.js/i, static(path.resolve(__dirname, ‘../www/dist’)))router.all(’*’, async ctx => { await ctx.render(‘index’)})这里需要注意的是koa-views提供的render方法是异步的,所以要用await,否则也是返回404,相当于对路由没有进行任何处理。只要不是以.js结束的路由请求都返回index.html,js类型的就从项目打包出来的静态资源里找,相当于把路由的控制权交给了前端。前端react App只需要对匹配不到的路由做下处理就ok了。<BrowserRouter> <div> <Switch> <Route path="/lottery" component={Lottery} exact /> <Route path="/a" render={() => <div>in a</div>} exact /> <Route render={() => <div>404页面</div>}></Route> </Switch> </div></BrowserRouter> ...

February 20, 2019 · 1 min · jiezi

基于Koa(nodejs框架)对json文件进行增删改查

想使用nodejs(koa)搭建一个完整的前后端,完成数据的增删改查,又不想使用数据库,那使用json文件吧。本文介绍了基于koa的json文件的增、删、改、查。代码准备const Koa = require(‘koa’)const bodyParser = require(‘koa-bodyparser’)const Router = require(‘koa-router’)const fs = require(‘fs’)const path = require(‘path’)const app = new Koa()const router = new Router()app.use(bodyParser())// 路由const deploy = new Router()// 增删改查接口,可添加在下面// 装载所有子路由router.use(’/deploy’, deploy.routes(), deploy.allowedMethods())app.use(router.routes()).use(router.allowedMethods())app.listen(3000);json示例[ {“id”: 1, “name”: “唐僧”}, {“id”: 2, “name”: “孙悟空”}, {“id”: 3, “name”: “猪八戒”}, {“id”: 4, “name”: “沙和尚”}]1.新增和修改新增和修改可以分开,但是为了省代码就合并在一起了。deploy.post(’/add-modify’, async (ctx) => {// 这里使用的bodyParser来解析post请求传来的数据,id是用来查找之前有的数据并进行修改,新增数据的在前台应该将id设置为空 let id = ctx.request.body.id let params = ctx.request.body.params let writeJson = () => { return new Promise((resolve,reject)=>{ // fs模块读取json文件 对fs、path模块不熟悉的可以去查下官方文档 fs.readFile(path.join(__dirname, ‘/data/project.json’),function(err,data){ if(err){ // 报错返回 resolve({code: -1, msg: ‘新增失败’ + err}) return console.error(err); } let jsonData = data.toString();//将二进制的数据转换为字符串 jsonData = JSON.parse(jsonData);//将字符串转换为json对象 // 有id值=>修改 无id值=>新增 if (id) { jsonData.splice(jsonData.findIndex(item => item.id === id), 1, params) } else { // 有重复 => 返回-1 无重复 => 将params加到json数组末尾 let hasRepeat = jsonData.filter((item) => item.id === params.id); hasRepeat ? resolve({code: -1, msg: ‘新增失败,有重复项目id’}) : jsonData.push(params); } //因为nodejs的写入文件只认识字符串或者二进制数,所以把json对象转换成字符串重新写入json文件中 let str = JSON.stringify(jsonData); fs.writeFile(path.join(__dirname, ‘/data/project.json’),str,function(err){ if(err){ resolve({code: -1, msg: ‘新增失败’ + err}) } resolve({code: 0, msg: ‘新增成功’}) }) }) }) } // 返回给前端 ctx.body = await writeJson()})2.删除删除,这里使用的get方法deploy.get(’/delete’, async (ctx) => { let id = ctx.request.query.id let deleteJson = () => { return new Promise((resolve,reject)=>{ fs.readFile(path.join(__dirname, ‘/data/project.json’),function(err,data){ if(err){ resolve({code: -1, msg: ‘删除失败’ + err}) return console.error(err); } let jsonData = data.toString();//将二进制的数据转换为字符串 jsonData = JSON.parse(jsonData);//将字符串转换为json对象 // 过滤出所存item的id和前端传来id不等的 item ,下面提供了两种方法filter和splice jsonData = jsonData.filter((item) => item.id !== id); // jsonData.splice(jsonData.findIndex(item => item.id === id), 1) let str = JSON.stringify(jsonData); fs.writeFile(path.join(__dirname, ‘/data/project.json’),str,function(err){ if(err){ resolve({code: -1, msg: ‘删除失败’ + err}) } resolve({code: 0, msg: ‘删除成功’}) }) }) }) } ctx.body = await deleteJson()})3.查询deploy.get(’/find’, async (ctx) => {// 两种查询方式 1.id为空 => 查询全部 2.id有值 => 查询单个 let id = ctx.request.query.id let findJson = () => { return new Promise((resolve,reject)=>{ fs.readFile(path.join(__dirname, ‘/data/project.json’),function(err,data){ if(err){ resolve({code: -1, msg: ‘查询失败’ + err}) return console.error(err); } let jsonData = data.toString();//将二进制的数据转换为字符串 jsonData = JSON.parse(jsonData);//将字符串转换为json对象 // 有id值=>单个 无id值=>全部 if (id) { jsonData = jsonData.filter((item) => item.id === id); resolve({code: 0, data: jsonData}) } else { resolve({code: 0, data: jsonData}) } }) }) } ctx.body = await findJson()})当然,上面提供的还没有支持分页,想要实现分页,需求改变json格式,如下:{ “data”: [{“id”: 1, “name”: “唐僧”}, {“id”: 2, “name”: “孙悟空”}, {“id”: 3, “name”: “猪八戒”}, {“id”: 4, “name”: “沙和尚”}], “currentPage”: 1, “pageSize”: 4, “pageNum”: 1, “total”: 4}新增page一些查询参数,并在使用传入的参数取对应数据。 ...

February 1, 2019 · 2 min · jiezi

GraphQL 在 koa 框架中的实践

在之前翻译的一篇文章为什么我们在 API 中使用 GraphQL 中介绍 GraphQL 的作用,之后笔者在 Koa 框架中实现相关接口及调用方式并整理相关实现过程,希望对如笔者一样想要使用 GraphQL 的入门者一些帮助。由于笔者日常工作开发中使用的Node 后台框架是 Koa,因此就以此框架为基础实现 /graphql 接口,接下来会分步介绍实现的步骤与方法。路由配置建立一个路由配置,笔者使用的框架对路由进行封装,只要在指定文件下新建graphql.ts文件夹即可,以下是使用 koa-router 的写法:router.get(’/graphql’, async (ctx, next) => {// do something…})数据定义引入 js 实现 GraphQL 相关模块 graphql,其定义一套数据类型,用于描述我们能从服务查询到的数据,比如 graphql 定义 GraphQLObjectType 、 GraphQLString, GraphQLID, GraphQLList 等相关类型,本次接口需要返回设备基本信息列表,以下是本次接口查询定义的对象import { GraphQLObjectType, GraphQLString, GraphQLID, GraphQLList, GraphQLNonNull } from ‘graphql’;/device.js///id、 baseSn 、 baseWifi 均为数据库中字段let DeviceType = new GraphQLObjectType({ name: ‘Device’, fields: { id: { type: GraphQLID }, baseSn: { type: GraphQLString }, baseWifi: { type: GraphQLString } }})export const devices = { type: new GraphQLList(DeviceType)}实现 GraphQL 查询入口1. 定义查询参数和数据来源前面定义数据的结构和类型,接下来就看一下如何定义数据来源和数据类型:export const devices = { type: new GraphQLList(DeviceType), args: { baseSn: { type: new GraphQLNonNull(GraphQLString) } }, resolve(root, params, options) { return getList({params}).then((data) => { return data[0] }) //the result of th getList function: / * [total, devices] * [1, [ { baseSn, basWifi, id …} ] */ }}在device.js devices 对象中添加 args 属性,定义 baseSn 为查询条件,在 resolve 中调用数据查询函数作为 devices 的返回结果。2. 定义查询入口GraphQL 服务定义大部分都是普通对象类型,但是一个 schema 内定义两个特殊类型:schema { query: Query mutation: Mutation}每个 GraphQL 都会定义一个 query 类型作为查询入口,也会定义 mutation 定义更改操作的接口。// schema.jsimport { GraphQLSchema, GraphQLObjectType } from ‘graphql’;import { devices } from ‘./device.js’export default new GraphQLSchema({ query: new GraphQLObjectType({ name: ‘Query’, fields: { devices // you can define other data here } }) //mutation: …})3.在 Koa 中实现//router.js import { graphqlKoa} from ‘graphql-server-koa’import schema from ‘./schema.js’router.get(’/graphal’, async (ctx, next) => { await graphqlKoa({ schema: schema })(ctx, next) //… })前端接口调用前端使用 jQuery.ajax 进行调用,GraphQL 查询请求与返回的数据结构类似,在查询语句中指定需要查询的字段,比如下列查询中指定 baseSn, baseWifi 两个字段,则会在结果中返回这两个字段而没有 id 字段。$.ajax({ url: ‘/graphql’, data: { query: query { devices (baseSn: "**"){ baseSn, baseWifi } } }/*results"data": { “devices”: [ { “baseSn”: “************”, “baseWifi”: “” } ]} */参考文献[1] GraphQL 官网 [2] https://github.com/graphql/gr… ...

January 27, 2019 · 2 min · jiezi

手把手教你用koa+mongoodb实现自己的接口

实际前端开发中我们常常需要模拟数据,市场上有许多工具提供使用但是基本都是提供数据展示,如果我们想要一个具备增删改查功能的接口怎么办呢?当然是强大自己啦!!!首先我们需要新建一个目录mkdir koa-project安装koakoa 依赖node V7.6.0及以上版本, 首先确认node版本在7.6.0以上,版本低的请自行搞定cd koa-projectnpm initnpm install koa –save写个demo试一试mkdir koa-apicd koa-apitouch index.jsconst Koa = require(‘koa’)const app = new Koa()app.use(async(ctx)=>{ ctx.body = ‘Hello World’})app.listen(3000,()=>{ console.log(‘服务已经启动’)})这个时候打开浏览器输入localhost://3000你会发现界面已经出现了程序员标配的“Hello World”,当然这个时候你可以在ctx.body后面放上json数据这样就已经达到了大部分接口模拟工具实现的功能,但是我们能满足于此嘛?不,我们要让这个接口实现增删改查的功能,这个时候我们就需要一个数据库了,所以接下来我们要白活一个数据库了安装mongoodb去官网下载MongoDB,https://www.mongodb.com/ 然后傻瓜式安装即可(这里推荐一个安装教程http://www.runoob.com/mongodb…)运行mongoodb记住这里我默认你已经配置好环境变量了,如果安装出现问题请自行谷歌,没有的话直接输入mongod既可启动mongoodb服务器安装mongoosenpm install mongoose –save万事俱备,起锅烧油,用mongoose连接数据库mkdir databasecd databasetouch init.js今日有事,有空继续更新,会尽快。。。

January 24, 2019 · 1 min · jiezi