vue+Nodejs+Koa搭建前后端系统(二)--koa-generator创建项目及分析

news2024/9/29 15:30:07

前言

  • 采用上一篇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

在这里插入图片描述

  1. 设置执行策略远程签名
  • 输入 set-executionpolicy remotesigned
  • 输入A
  • 输入koa -v(如果要用koa2指令生成koa2应用,则输入koa2 -v

在这里插入图片描述
这是在vscode终端输入koa --version,会显示koa的版本号。若还是报错,重启一下 vscode,重启vscode还报错,那就重启系统(并点上三根香保佑一下)。

分析并修改koa项目代码

koa-generato生成的项目目录如下:
在这里插入图片描述

项目文件组成

  1. package.json 配置文件

    在这里插入图片描述
    其中
    script.start运行node bin/www指令,启动应用,但不监视文件变化。(适用于生产环境)。

    script.dev运行./node_modules/.bin/nodemon bin/www指令(其实就是nodemon指令),启动应用,并监视文件变化。(适用于开发环境)。

    script.prd运行pm2 start bin/www指令,启动应用(怎么启动还没研究)。PM2 是一个守护进程管理工具。开机自启动也可以用它写。

  2. /bin/www.js 入口文件

    可以在里面修改应用进程的端口号,默认是3000
    在这里插入图片描述

  3. /app.js 应用文件

    用于整合 koa中间件。
    在这里插入图片描述app.use(function):将给定的中间件方法(即function)添加到此应用程序。如果你使用过Vue,可以把看成是VUE.use()。

  4. /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;
    
    
  5. /view/ 视图目录
    视图层,类似于vue的作用,这里用的Pug模板(我们是前后端分离,这里可忽略)。

  6. /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() 。

中间件函数有两个参数:ctxnext
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/429474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32在ESP-IDF框架下为LVGL(v8.3)配置SD卡文件系统

踩坑记录 1、如果SD卡曾经做过系统盘(比如说:作为树莓派的系统盘),那么要把系统盘的分区合并成一个(这个网上有很多教程),并重新格式化,否则实验会失败。 2、并不是买回来的新的SD卡…

基于Vant组件库二次封装组件(TS+Vue3.x环境)

1. 今天的需求是封装一个 Navigation Bar 导航栏组件,目的是给到App几乎所有的页面复用: 2. 因为之前的项目里使用过Vant组件库,笔者第一时间想到了Vant组件库中的 NavBar 组件,和当前App的需求匹配度很高。Vant组件库的 NavBar 组…

压箱底教程分享,手把手教会你如何注册target账号和下单

喜欢套利的朋友肯定都认识target这个平台吧,它是美国热门的综合性海淘网站之一。东哥近日收到私信有朋友向我请教在注册target账号时遇到的一些问题,所以今天东哥想跟大家分享的就是就是target账号注册教程和下单流程,让也想注册target账号的…

软考第五章 无线通信网

无线通信网 无线通信网包括面向语音通信的移动电话系统以及面向数据传输的无线局域网和无线广域网。 WiFI底层是如何传输数据的呢 1.移动通信 1.1 蜂窝通信系统 1980年中期,欧洲和日本都建立了第一代蜂窝移动电话系统。蜂窝网络把一个地理区域划分成若干个称为…

Vue2-黑马(五)

目录: (1)vue2-组件重用 (2)vue2-element ui安装 (3)vue2-ElementUI-table (4)Element-ui-分页pagination (1)vue2-组件重用 页面上有很多的…

PyTorch中的符号索引和函数索引用法

Pytorch中很多函数都采用的是函数式索引的思路,而且使用函数式索引对代码可读性会有很大提升。 张量的符号索引 张量也是有序序列,我们可以根据每个元素在系统内的顺序位置,来找出特定的元素,也就是索引。 一维张量的索引 一维…

离线安装JumpServer

官网操作手册: https://docs.jumpserver.org/zh/v3/installation/setup_linux_standalone/offline_install/ 环境要求:(内存最小需要4G) 架构图 安装部署 1、下载 JumpServer官网下载: https://community.fit2cloud…

定点数的二进制表示形式

定点数的二进制表示形式 文章目录定点数的二进制表示形式什么是定点数表示格式数值范围与分辨率转换python 转换定点数C 双精度浮点数转换为8位和16位定点数C 将定点数转回浮点数测试什么是定点数 在嵌入式系统中,为了降低运算复杂度,通常还会使用定点数…

有趣的数学之回文数

“回文”是指正读反读都能读通的句子,它是古今中外都有的一种修辞方式和文字游戏,如“我为人人,人人为我”等,最有名的莫过于“上海自来水来自海上,人过大佛寺佛大过人 ”。你们知道吗,在数学中也有这样一类…

30多份软件测试报告模板,如何写一份优秀测试报告模板流程

相信很多做软件测试的小伙伴在软件测试后期,都为软件测试报告总结花费了很多的精力,那么如何做好软件测试报告呢?一份优秀的测试报告又包含哪些内容呢? 测试报告的核心要素 一、测试结论 从测试工程师的专业角度分析&#xff0…

pytorch进阶学习(四):使用不同分类模型进行数据训练(alexnet、resnet、vgg等)

课程资源:5、帮各位写好了十多个分类模型,直接运行即可【小学生都会的Pytorch】_哔哩哔哩_bilibili 目录 一、项目介绍 1. 数据集准备 2. 运行CreateDataset.py 3. 运行TrainModal.py 4. 如何切换显卡型号 二、代码 1. CreateDataset.py 2.Train…

如何基于ChatGPT+Avatar搭建24小时无人直播间

0 前言 最近朋友圈以及身边很多朋友都在研究GPT开发,做了各种各样的小工具小Demo,AI工具用起来是真的香!在他们的影响下,我也继续捣鼓GPT Demo,希望更多的开发者加入一起多多交流。 上一篇结合即时通 IM SDK捣鼓了一个…

因为这三个面试题,我与字节offer失之交臂

我一个朋友挑战3个月入职字节,一路过关斩将直到终面,着实把我惊了一把,可惜的是,他倒在了最后三个面试题上。 我很讶异,前面不是打得很好吗?怎么会在最后几题上犯错误呢? 朋友说:别…

电瓶隔离器工作原理与发展简史

电瓶隔离器(Battery Isolators)工作原理与发展简史 电池隔离器(英文:Battery Isolators),又叫双电池隔离器、双电瓶隔离器、双电瓶保护器,还有叫双电池分离器的。 电瓶隔离器其实并没有真正的隔离,负极是始终连在一起的。房车、…

拓展系统命令

文章目录拓展系统命令使用方式拓展系统命令快速运行方法命令 - ZFASTRUN安全运行方法命令 - ZFASTSAFERUN快速运行Query方法命令 - ZFASTQUERY安全运行Query方法 命令 - ZSAFEQUARY防止调试时误将数据提交命令 - ZTRN在Terminal执行SQL语句命令 - ZSQL安全Global命令 - ZSAFEKI…

动态内存管理【上篇】

文章目录⚙️1.为什么存在动态内存分配⚙️2.动态内存函数的介绍📬2.1. malloc函数📬2.2. free函数📬2.3. calloc函数📬2.4. realloc函数⚙️3.常见的动态内存错误🔒3.1.对NULL指针的解引用操作🔒3.2.对动态…

二叉树(OJ)

单值二叉树(力扣) ---------------------------------------------------哆啦A梦的任意门------------------------------------------------------- 我们来看一下题目的具体要求: 既然我们都学了二叉树了,我们就应该学会如何去…

笔记:Java关于轻量级锁与重量级锁之间的问答

问题:如果在轻量级锁状态下出现锁竞争,不一定会直接升级为重量级锁,而是会先尝试自旋获取锁,那么有a b两个线程竞争锁,a成功获取锁了,b就一定失败,那么轻量级锁就一定升级为重量级锁&#xff0c…

基于Bazel + SQLFluff实现SQL lint

背景SQL进行版本化控制后,我们希望为SQL加入lint步骤。这样做的好处是我们可以在真正执行SQL前发现问题。本文中,我们通过Bazel执行SQLFluff[1]以实现SQL的lint。SQLFluff是一款使用Python语言使用的,支持SQL多方言的SQL lint工具。它的特点是…

设计模式-创建型模式之单例模式

6.单例模式6.1. 模式动机对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时…