前言
- 采用上一篇vue+Nodejs+Koa搭建前后端系统(一)–简易版创建的项目目录的基础上,创建新的后端服务项目server2
- 使用koa-generator脚手架创建后端项目
- 计算机系统为Windows 10 专业版
小说中,终成眷属一般就结局了,但现实是生活一直在继续,我们一直在调试生活的方式来满足我们有恃无恐的安全感。
上一篇只是简单地搭建了后端服务器。这回我们要利用koa的脚手架搭建后端项目框架。
搭建koa项目框架
创建koa应用
koa-generator,koa的脚手架。就像vue与vue-cll(或vite)关系一样,koa-generato会帮助生成基础的应用项目。
安装koa-generato:
npm install koa-generator -g
在项目根目录下(/hello-node/)创建koa应用
koa server2
server2是项目的名字。
生成的项目目录如图:
然后按照终端的提示分别输入以下指令
1.进入server2目录
cd server2
2.安装依赖
npm install
3.启动服务器
npm start
4.在浏览器地址栏输入127.0.0.0:3000即可打开应用
koa指令报错
当安装完koa-generato,用koa指令创建应用时,可能会在终端报错:
koa : 无法加载文件 C:\Users\Administrator\AppData\Roaming\npm\koa.ps1,因为在此系统上禁止运行脚本。
这应该是由于计算机在启动 Windows PowerShell 时,执行策略很可能是 Restricted(受限制的),也就是默认设置。
Restricted这个执行策略不允许任何脚本运行。
可以打开PowerShell 输入 get-executionpolicy来查看,计算机目前的执行策略。如出现:Restricted,则说明执行策略受限,不允许运行未签名的脚本。
解决:
以管理员身份打开PowerShell 输入 set-executionpolicy remotesigned(设置执行策略远程签名)
————————————————————————————————
*版权声明:本文为CSDN博主「shelleyHLX」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_27009517/article/details/115293568*
1.以管理员身份运行PowerShell
- 设置执行策略远程签名
- 输入
set-executionpolicy remotesigned
- 输入
A
- 输入
koa -v
(如果要用koa2指令生成koa2应用,则输入koa2 -v
)
这是在vscode终端输入koa --version
,会显示koa的版本号。若还是报错,重启一下 vscode,重启vscode还报错,那就重启系统(并点上三根香保佑一下)。
分析并修改koa项目代码
koa-generato生成的项目目录如下:
项目文件组成
-
package.json 配置文件
其中
script.start运行node bin/www
指令,启动应用,但不监视文件变化。(适用于生产环境)。script.dev运行
./node_modules/.bin/nodemon bin/www
指令(其实就是nodemon指令),启动应用,并监视文件变化。(适用于开发环境)。script.prd运行
pm2 start bin/www
指令,启动应用(怎么启动还没研究)。PM2 是一个守护进程管理工具。开机自启动也可以用它写。 -
/bin/www.js 入口文件
可以在里面修改应用进程的端口号,默认是3000
-
/app.js 应用文件
用于整合 koa中间件。
app.use(function):将给定的中间件方法(即function)添加到此应用程序。如果你使用过Vue,可以把看成是VUE.use()。 -
/routes/ 路由目录
koa-generato脚手架默认会在 /routes/目录下创建index.js和user.js,这两个都是用来匹配路由的。
设置ctx.body
表示响应的数据。
以index.js为例:const router = require("koa-router")();//引入路由中间件koa-router /**路由匹配规则 Start*/ router.get("/", async (ctx, next) => { await ctx.render("index", { title: "Hello Koa 2!", }); next(); }); router.get("/string", async (ctx, next) => { ctx.body = "koa2 string"; }); router.get("/json", async (ctx, next) => { ctx.body = { title: "koa2 json", }; }); /**路由匹配规则 End*/ module.exports = router;//导出路由模块
首先引入koa-router中间件,然后编写路由匹配规则,最后将路由模块导出,以备app.js中引入并安装该中间件。
app.js
const Koa = require("koa"); const app = new Koa(); /***引入路由中间件*/ const index = require("./routes/index"); const users = require("./routes/users"); /***引入路由中间件*/ ....... /***安装路由中间件*/ app.use(index.routes(), index.allowedMethods()); app.use(users.routes(), users.allowedMethods()); /***安装路由中间件*/ module.exports = app;
-
/view/ 视图目录
视图层,类似于vue的作用,这里用的Pug模板(我们是前后端分离,这里可忽略)。 -
/public/ 公共资源目录
一般用于存放静态文件,比如图片等。
项目流程
1./bin/www.js为项目入口,运行该文件,将创建一个http服务器var server = http.createServer(app.callback());
,并监听端口
app.callback()返回适用于 http.createServer() 方法的回调函数来处理请求。你也可以使用此回调函数将 koa 应用程序挂载到 Connect/Express 应用程序中。
2.进入app.js,添加需要的Koa中间件。
3.app.js中的路由中间件具体匹配规则被代码分离到 /routes/ 目录下的各个文件中。
4.当请求发生时,各个中间件按序触发,对请求、响应进行处理。直到完成此次握手。
中间件原理
通俗的讲:中间件就是匹配路由之前或者匹配路由完成后做的一系列的操作,我们就可以把它叫做中间件。
Koa的中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res))。在 Koa中处理请求、响应循环流程的变量一般被命名为 next 变量。
如果我们的get、post回调函数中,没有next参数,那么匹配到这个路由就不会继续往下匹配了。如果想往下匹配的话,那么需要写next() 。
中间件函数有两个参数:ctx和next。
ctx是一个对象,其中包含了请求对象、响应对象等。next是一个函数,调用该函数Koa会进入下一个中间件函数,直到当前中间件函数执行完毕(遇到return)会返回到上一个中间件函数的next处继续向后执行。
Koa把这种中间件执行方式叫做洋葱圈模型。
如
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx, next) => {
console.log("111");
await next();
console.log("666");
});
app.use(async (ctx, next) => {
console.log("222");
await next();
console.log("555");
});
app.use(async (ctx, next) => {
console.log("333");
await next();
console.log("444");
});
服务器启动后输出:
111
222
333
444
555
666
这个执行顺序有点像递归函数,next()前面为前递归,后面为后递归(实际上应该使用generator函数写的)。而next就像一个指针,指向下一个中间件。
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx, next) => {
console.log("111");
await next();
console.log("666");
});
app.use(async (ctx, next) => {
console.log("222");
//await next();
console.log("555");
});
app.use(async (ctx, next) => {
console.log("333");
await next();
console.log("444");
});
服务器启动后输出:
111
222
555
666
由于第二个中间件没有调用next(),所以到第二个中间件的执行链就断了,不会执行下一个中间件了。
路由中间件也是一样的道理
const Koa = require("koa");
const app = new Koa();
const router = require("koa-router")();
/**路由匹配规则*/
router.get("/", async (ctx, next) => {
await ctx.render("index", {
title: "Hello Koa 2!",
});
next();
});
router.get("/string", async (ctx, next) => {
ctx.body = "koa2 string";
});
/**路由匹配规则*/
app.use(async (ctx, next) => {
console.log("111");
await next();
console.log("666");
});
app.use(async (ctx, next) => {
console.log("222");
await next();
console.log("555");
});
/**路由中间件*/
app.use(router.routes(), router.allowedMethods());
/**路由中间价*/
app.use(async (ctx, next) => {
console.log("333");
await next();
console.log("444");
});
几个常用中间件
1.koa-bodyparse:解析请求和响应中的body。
- 解析POST请求中的参数(处理成对象),存储在ctx.request.body中。
- 响应时,可以设置ctx.body为对象(GET、POST都适用)。
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyParser());
app.use(async ctx => {
// the parsed body will store in ctx.request.body
// if nothing was parsed, body will be an empty object {}
ctx.body = ctx.request.body;
});
2.koa-router:路由处理
const router = require("koa-router")();
//Get:不带参数
router.post("/hello", async (ctx, next) => {
ctx.body = `Hello Node! I Post`;
});
//Get:带参数
router.get("/hello/:name", async (ctx, next) => {
var name = ctx.params.name;
ctx.body = {title:'node',name:name};
});
//Post
router.post("/goodbye", async (ctx, next) => {
const name= ctx.request.body.name;
ctx.body = {name:name,type:'node';
});
router.prefix(s)
设置当前路由的前缀,比如:
const router = require("koa-router")();
router.prefix("/index");
/**请求地址为/index匹配该路由*/
router.get("/", async (ctx, next) => {
await ctx.render("index", {
title: "Hello Koa 2!",
});
next();
});
/**请求地址为/index/string匹配该路由*/
router.get("/string", async (ctx, next) => {
ctx.body = "koa2 string";
});
3.koa-logger:日志打印
适用于开发环境,打印请求和响应信息
const logger = require('koa-logger')
const Koa = require('koa')
const app = new Koa()
app.use(logger())
自定义打印信息
const logger = require('koa-logger')
const Koa = require('koa')
const app = new Koa()
app.use(logger((str, args) => {
// redirect koa logger to other output pipe
// default is process.stdout(by console.log function)
}))
或
app.use(logger({
transporter: (str, args) => {
// ...
}
}))
str 是一个字符串类型,在发生请求时 str 包含 请求类型、请求路径信息,在发生响应时 str 包含
响应状态码、响应时长、响应文件大小信息。
args是一个数组类型,在发生请求时会将请求类型、请求路径放在该数组中,在发生响应时会将响应状态码、响应时长、响应文件大小信息放入该数组中。
4.koa-json 格式化响应文本
虽然可以设置ctx.body为JSON对象(响应数据),但其实响应的数据都是文本(字符串)类型的。因为使用了 koa-bodyparse 中间件,它会把设置的JSON对象处理成字符串,然后再发出响应。
做个试验,我们把koa-bodyparse 中间件去掉,再去设置ctx.body为JSON对象,那时http的响应为“无响应”。除非你用JSON.stringify()把对象处理成字符串。
既然响应是字符串,那么我们以何种格式设置它,它就会以同样的格式给请求。这样很难保证可读性,这时koa-json就起到作用了,它可以让JSON字符串显示得更像JSON的格式,增加可读性。
配置默认:
app.use(json());
自定义配置:
const json = require("koa-json");
app.use(json({pretty: false, param: 'pretty',spaces:4}));
- pretty:是否格式化响应的JSON字符串,默认true
- param:可选查询字符串,默认为空。当pretty为false,请求中查询字符串参数有该字段设置的字符串时,格式化响应的JSON字符串。
- spaces:每组key-value前空格的数量,默认为2。
比如,在/routes/index.js中添加路由配置
router.get("/json", async (ctx, next) => {
ctx.body = {
title: "koa2 json",
sub: "hah",
tree: {
leaf: "234",
},
};
});
app.js使用koa-json默认配置
app.use(json());
浏览器地址中输入localhost:3000/json,显示为:
修改koa-json配置
app.use(json({ pretty: false, param: "pretty", spaces: 8 }));
浏览器地址中输入localhost:3000/json,显示为:
浏览器地址中输入localhost:3000/json?pretty,显示为:
也可以在Edge浏览器的DevTools中看到实际响应的数据:
koa-json一般都是用于开发环境调试时用的。而前端一般会将响应的JSON格式字符串转化为JSON对象,所以不太会在意JSON格式字符串的格式。
5.koa-static 静态资源管理
如果不用koa-static中间件,以该项目目前的代码是访问不到静态资源的。
可以做个测试:在/public/images/目录中放一张图片
你可以在浏览器地址栏尝试输入:localhost://3000/public/1.jpeg ,结果是页面没有显示图片,而是 404 not found。
为甚?因为node是一种服务,localhost://3000/上的所有请求都是服务,而/public/1.jpeg这个请求在该node服务中没有被路由注册,所以就会返回404。
koa-static加载说明:
app.use(require('koa-static')(root, opts));
- root:String,静态资源的根目录,请求该目录下的文件不会被当成服务
- opts:Object,配置参数
- maxage:浏览器缓存max-age数值,单位ms,默认0。
- hidden:是否允许传输隐藏的文件,默认false。
- index:默认文件名,默认为"index.html"。
- defer:如果为true,则在返回next()后服务,这将允许任何下游中间件首先响应。默认false。
- gzip:当客户端支持gzip并且请求的扩展名为.gz的文件存在时,尝试自动提供文件的gzip版本。默认为true。
- br:当客户端支持brotli并且请求的扩展名为.br的文件存在时,尝试自动提供文件的brotli版本(注意,brotli仅通过https接受)。默认为true。
- setHeaders:Function(res, path, stats),自定义设置响应头。
- extensions:当URL中没有足够的扩展名时,尝试从传递的数组中匹配扩展名以搜索文件。先找到的先匹配。默认为false。
基础用法(配置项全部默认)
app.use(require("koa-static")(__dirname + "/public"));
__dirname是node的常量,存储当前目录路径的字符串。
在浏览器地址栏输入:http://localhost:3000/images/1.jpeg,图片就显示出来了。
注意!因为设置koa-static的root为/public/,所以在请求静态资源时不需要加/public/(可以把koa-static看成是静态资源服务器,其设置的root,即是该服务器的根目录)。
设置静态资源缓存
app.use(require("koa-static")(__dirname + "/public",{maxage:60000}));
可以在DevTools看到,响应头中的Cache-Control:max-age=60;
设置默认文件名
app.use(require("koa-static")(__dirname + "/public",{index:"index.html"}));
在/public/目录下新建一个index.html
在浏览器地址栏输入:http://localhost:3000/,就会显示/public/index.html页面。
这就相当于koa-router设置默认。
如果koa-router也设置了默认:
const router = require("koa-router")();
router.get("/", async (ctx, next) => {
await ctx.render("index", {
title: "Hello Koa 2!",
});
next();
});
那么谁有效就要看中间件的加载顺序了:谁先加载谁有效!另外,要是koa-static设置的index静态资源没找到,那么他就不会起作用。
比如,把index设置index2.html,因为 /public/ 目录下没有index2.html,所以即使koa-static的加载顺序要靠在koa-router前面也不会有效:他会去匹配后面的koa-router默认。
defer设置
app.use(require("koa-static")(__dirname + "/public",{defer: true}));
上面讲到koa-router和koa-static同时设置了默认路由 谁先加载谁有效 ,其实不准确,那得是有一个前提,就是defer这个配置项为false。一旦defer设置为true,那么不管加载顺序怎样,都是koa-router的默认路由有效。因为defer=true相当于滞后生效。
自定义设置响应头
app.use(require("koa-static")(__dirname + "/public",{
setHeaders: (res, path, stats) => {
res.setHeader("name", "xy");
},
}));
在浏览器地址栏输入:http://localhost:3000/images/1.jpeg,可以看到DevTools中的响应头:
setHeaders配置项是一个Function,其有三个参数res, path, stats:
- res:相当于ctx.res,是Node 的 response 对象.(ctx为Koa上下文),其setHeader()方法可以设置响应头。
- path:请求的文件地址字符串。
- stats:是Node 的 stats对象,存储着文件信息。
extensions配置
app.use(
require("koa-static")(__dirname + "/public", {
extensions: [".png", ".jpeg"],
})
);
在浏览器地址栏输入:http://localhost:3000/images/1 (不加文件后缀),图片依然可以显示。这是因为设置了extensions,其中有 .jpeg 的扩展匹配。
可以设置多个静态资源管理器:
const app = new Koa();
app.use(
require("koa-static")(__dirname + "/public")
);
app.use(
require("koa-static")(__dirname + "/static")
);
番外:koa-static-match-path是静态资源映射器。它可以将原资源目录映射为指定目录名称:
const app = new Koa();
const handleStatic = require('koa-static-path')
app.use(handleStatic(__dirname+'./public/','static'))
浏览器地址栏输入http://localhost:3000/static/images/1.jpeg
6.koa-views视图模板渲染
前后端分离,这个项目目前用不到,搁置-----主要是写不动了。
参考资料
- koa中文文档
- NodeJs中文文档
- CSDN博客:koa错误
- 阿里云开发者社区:Koa.js 中的日志管理
- 博客园:koa-logger