第一步:如何写一个基于node的hello world
①创建新的文件夹nodejsdemo
②在cmd命令行中进入新建的文件夹nodejsdemo 运行 npm init -y (生成package.json文件)
③在cmd命令行中进入新建的文件夹nodejsdemo 运行 npm i (生成package-lock.json文件)
此时就搭建好了能够运行一个基础node的环境了
第二步:
①创建一个js文件 (server.js)有了server.js文件 我们就可以开始写基于nodejs的代码了
②写一个helloworld的demo
分析一下上面的代码是什么意思呢 ?
var http = require('http');
//引入nodejs自带的http模块(那这个http模块是干嘛用的呢?你可以把它理解成对象 这个对象里有很多别人写好的方法 你只需要调用 人家就帮你做了 你应该做的事情 )
//http模块功能 可以把他理解成一个创建小型的web服务器,主要的作用就是 让你在浏览器中输入
//http://127.0.0.1:8888/能看到东西
所以你会看到下面的代码
http.createServer(function (request, response) { response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World\n'); }).listen(8888);
引入http模块调用 createServer方法 就是启动了一个web服务 后面的listen 只是给这个web服务指定一个端口
request/response
①request 请求 (广义上来说 当后端需要获取前端传来的参数时 浏览器会把参数放在哪个地方呢 就是封装在request里面)比如nodejs里面 就会有对应的request对象
②response 响应 (广义上来说 当后端需要向前端返回数据时,浏览器会把返回的参数放在哪个地方呢 就是封装在response对象里)
此时再看如下的代码
response.writeHead(200, {'Content-Type': 'text/plain'});//指定响应头 response.end('Hello World\n');//输出hello world
让我们运行起来看一看效果:
方式一:node .\server.js
方式二:
让我们看看效果
此时一个基于nodejs程序的demo就写好了
第三步:
此时会发现 这个demo里仅仅输出了一个helloworld 一点实际的作用都没有
所以我们要对它进行改造 改的更向一个实际的项目的样子
那么实际的项目应该长什么样子呢 ,实际的项目会有很多的url 而上面的例子很显然还不足以满足我们需求
http://127.0.0.1:8888/ http://127.0.0.1:8888/demo http://127.0.0.1:8888/data
那我们能不能在上面的demo稍微改造 让他满足我们的需求呢
仔细分析后我们会发现 唯一不同的是什么 就是url: / /demo /data
那么我们怎么改造呢 举例 我们希望访问路径是/ 时返回helloworld 访问路径时/data 时返回hellodata
var http = require('http'); http.createServer(function (request, response) { if(request.url=='/'){ response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World\n'); } if(request.url=='/data'){ response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello Data\n'); } }).listen(8888); console.log('Server running at http://127.0.0.1:8888/');
看代码 中 我们通过request获取到url然后稍加判断就能实现根据不同的url返回不同东西的效果
但是聪明的你们会发现 上面的代码太过繁琐 如果一个项目需要这么写 那后端就得累死了
那么有没有什么好的方式呢 ,于是基于以上方式封装好的框架就出来 比如express 和 即将使用的 koa2
全文以下皆以koa2 为示例:(科普:如果你们使用vite构建一个vue项目时 当你们npm run server启动服务时 就是打开了一个koa写好的web服务)
第四步:
那koa这个玩意怎么用呢?
首先我们得安装koa 在你的pakeage.json中添加 以下内容 然后npm i
"koa": "^2.14.1", "koa-bodyparser": "^4.4.0", "koa-router": "^12.0.0", "koa-session": "^6.4.0", "koa-views": "^8.0.0", "koa2-request": "^1.0.4"
此时我们就安装好了koa框架 那么怎么用呢 ?我们不如在新建一个文件 koaserver.js
接着我们写一份简单的demo (基于koa)
const Koa = require('koa'); const app = new Koa(); //引用koa const Router = require('koa-router'); const router = new Router(); //引用路由 const views = require('koa-views') const path = require('path') const session = require('koa-session'); const bodyParser=require('koa-bodyparser'); const request = require("request"); const https = require('https'); const querystring = require('querystring'); const koa2Req = require('koa2-request') //以上引入koa相关的模块 就像我们曾经引入http模块一样 //这里定义了一个访问路径/ 也就是说当访问路径为/时 会自动执行下面的函数 router.get('/', async (ctx) => { ctx.body='hello /'; //返回体 也就是返回的内容 ctx.status=200 //返回的状态码 比如404 500 200 401 }) //把我们引用的koa 和 koa-route 绑定到一起 这样koa才知道有你写的这个路由 app.use(router.routes()); app.listen(8888); //监听一个端口 就是http模块的listen 一模一样
我们稍加修改:
const Koa = require('koa'); const app = new Koa(); const Router = require('koa-router'); const router = new Router(); const views = require('koa-views') const path = require('path') const session = require('koa-session'); const bodyParser=require('koa-bodyparser'); const request = require("request"); const https = require('https'); const querystring = require('querystring'); const koa2Req = require('koa2-request') router.get('/', async (ctx) => { ctx.body='hello /'; ctx.status=200 }) router.get('/data', async (ctx) => { ctx.body='hello data'; ctx.status=200 }) app.use(router.routes()); app.listen(8888);
我们看看效果
此时我们就会发现 我们再也不用向http模块那样 写一大推if去判断了 也能实现我们需要的功能 ,这就是框架的魅力。
如上 一个简单的基于koa的demo就此完成了。
第五步:
上面的代码都是返回一个字符串 ,似乎显得太过单调 离项目的样子似乎还差了点有趣的东西
那什么是有趣的呢 ,我们希望返回给前端的东西是具有可维护性的 可修改的,所以这个世界就出现了一个叫数据库的东西 下文我们以postgresql为例 别问我为什么 因为你们会用上它!!!
那怎么连接数据库呢 (篇幅有限 怎么安装数据库 自行blibli解决)
毋庸置疑 写nodejs自身肯定是没有实现数据库功能的,因为nodejs的开发者谁知道你会用哪种数据库呢 ,
(此时抛出一个疑问 如果让你自己去写一个基于nodejs 连接数据库的东西 你该怎么写?)
你应该写一个模块,就像http模块那样 让人家require一下 就可以用是最好的, 在这个美好的时代,大佬帮你写了不用写了
我们想要连接postgresql数据库 请在你的pakeage.json 文件里引入:然后npm i
"pg": "^8.11.0", "pg-hstore": "^2.3.4", "request": "^2.88.2", "sequelize": "^6.31.1"
此时啊 我们就正式踏上了nodejs操作数据库的征途
①nodejs 是 nodejs ,数据库是数据库 ,两者结合在一起 是不是需要一个渠道,在代码领域 我们把这个渠道叫做数据库连接
那么连接 长什么样子呢 ,该怎么写呢:
看上面代码 :我们先引用一个叫做pg的模块然后配置连接
(***注意 任何一种关系型数据的连接都分为 host:服务器ip地址 port:端口号 user:用户名 password:密码 database :数据库的名称)
上面的代码 就是创建了一个连接的桥梁 (渠道)
我们有了这个渠道那怎么用呢 ?不妨我们在写一个url 用来查询数据库的一些东西
router.get('/sqldemo', async (ctx) => { client.query('select * from demo',(err,res)=>{ console.log(res.rows); }) ctx.body=''; ctx.status=200 })
让我们看看效果如何:
你会看到 console.log打印出了数据库里的内容,但是页面是空白的 ,此时正常人的思想 把打印的东西赋值给ctx.body不就OK了吗 ?不妨我们试试看
router.get('/sqldemo', async (ctx) => { client.query('select * from demo',(err,res)=>{ // console.log(res.rows); ctx.body=res.rows; }) // ctx.body=''; ctx.status=200 })
看效果:
koa非常善意的给了我们一个ok
玄学登场了吗?我们并没有写ok,ok是鬼写的 。
这个问题的诞生 是因为异步,严格来说 js里的玄学 80% 都可能是因为异步,那怎么处理呢 ?
我们对它进行分装一下 :(我们把执行sql的代码啊 放在一个promise里面)
let select=async (accesstoken,slot)=>{ return new Promise((resolve,reject)=>{ client.query('select * from demo',(err,res)=>{ resolve(res.rows); }) }) } router.get('/sqldemo', async (ctx) => { var data=await select() ctx.body={list:data} ctx.status=200 })
这样我们在看看返回的效果:
非常好,总算是看到了我们想要的东西。
到此一个大学生式的demo我们也算是写完了!!!
第六步:
******我们可以做它,但是不够优雅******
想象一下上面的东西有哪写问题啊?
①我们所有的访问路径都写在了一个叫koaserver.js的文件里,这就很扯淡,遐想一下,一个项目100个人搞,这个文件将成为什么鸟样
②我们执行查询数据库的语句时 因为异步的问题,我们啊单独写了一个函数专门查询,遐想一下,一个项目100000个sql语句 是不是得特么写200000个函数 ,这是不能容忍的 ,这是不可维护的
为了让项目维护性提高,为了出了bug的时候 我们能更快的甩锅 ,务必是要解决上面两个问题的
很显然 项目是不能向上面的demo那样开发的
为了更好的保留现场 我们在新建一个app.js文件
那怎么处理①中的问题呢?我们要对上面的写的代码进行封装
比如:
const {Client} = require('pg') const client = new Client({user: 'postgres',host: '192.168.222.18',database: 'postgres',password: 'senqi1010',port: 5432, }) client.connect();
这个连接啊应该是所有需要操作数据库的js文件都需要用的,我们新建一个文件夹叫做dbconfig 里面新建一个dbconnect.js存放我们的连接 并导出
我们在新的app.js里面
const Koa = require('koa'); const app = new Koa(); const Router = require('koa-router'); const router = new Router(); const views = require('koa-views') const path = require('path') const session = require('koa-session'); const bodyParser=require('koa-bodyparser'); const request = require("request"); const https = require('https'); const querystring = require('querystring'); const koa2Req = require('koa2-request') /* 上面引入所需模块 */ //引入自己写好的js文件 const liutianming = require('./routes/liutianming/route.js') //让route模块加载我们写好的js文件 router.use('/liutianming',liutianming) //让koa和route绑定到一起 app.use(router.routes()); app.listen(8888);
让我们看看我们如何写一个自己的js文件吧 (项目根目录新建routes目录 里面新建自己名字的文件夹 新增一个route.js文件 ** 文件夹和文件的名称随便 要和const liutianming = require('./routes/liutianming/route.js')
路径对的上就行),在看看自己的js里面写啥呢
const router = require('koa-router')() const request = require("request"); const https = require('https'); const koa2Req = require('koa2-request') /* 引用模块 */ const client =require("../../dbconfig/dbconnect.js");//获取到连接 //promise函数 返回查询数据库结果 let select=async (accesstoken,slot)=>{ return new Promise((resolve,reject)=>{ client.query('select * from demo',(err,res)=>{ resolve(res.rows); }) }) } /* 查询的url 那么怎么访问到这个呢 还记得我们在app.js里面配置的 router.use('/liutianming',liutianming) 那么访问路径就变成了 http://127.0.0.1:8888/liutianming/select */ router.get('/select', async (ctx) => { var data=await select() ctx.body={list:data} ctx.status=200 }) //会看到 add 往数据库新增数据时 删除数据时 修改数据时没有写promise //因为这里没考虑返回给前端内容如果有还是的异步 router.get('/add', async (ctx) => { var addSql = 'INSERT INTO demo(name) VALUES($1)'; var addSqlParams = ['nodejs']; client.query(addSql,addSqlParams,function (err, result) { }); ctx.body={list:'ok'} ctx.status=200 }) router.get('/del', async (ctx) => { var addSql = 'DELETE FROM demo WHERE id=1' client.query(addSql,function (err, result) {}) ctx.body={list:'ok'} ctx.status=200 }) router.get('/update', async (ctx) => { var addSql = 'UPDATE demo SET name=\'AA\' where id=2' client.query(addSql,function (err, result) {}) ctx.body={list:'ok'} ctx.status=200 }) module.exports = router.routes()
此时我们已经解决的了所有的url都写在一个文件里的尴尬 ,还有一个尴尬 ,还是那句话 一个项目一万句查询语句要写二万个函数 ,好累,太特么扯淡了 ,怎么办
不需要 紧张,到了这里 你们都应该知道了,没啥 肯定曾有个大佬又帮我们封装好了一个模块,没错就是这样,大佬正在用一个巨大的笼子把你们限定在增删改查的笼子里,你不需要知道他们做了啥,只需要知道你要去干啥
这个模块叫做 sequelize 一种基于nodejs orm连接数据库的方式 ,问题来了 什么orm 完全就不知所云
不妨我们百度一下 :
简单来说 ,就是创建一个类 类里的变量名和类型与数据库中表的字段一一对应上
比如说 :
数据库里有个表叫做demo ,那么我们可以创建一个类名字叫做demo ,表里有一个字段叫做name 那么我们demo类里有一个属性叫做name,晕了没有 看图
具体怎么用呢?
①之前我们创建连接的方式不行了 既然要用sequelize库就得按sequelize的规矩
1.创建一个数据库的配置文件(根目录下创建文件夹dbconfigorm 里面创建dbconfig.js)
module.exports = { HOST: "192.168.222.18", USER: "postgres", PASSWORD: "senqi1010", DB: "postgres", dialect: "postgres", pool: { max: 5, min: 0, acquire: 30000, idle: 10000 } };
2.创建连接数据库的渠道 也就是连接 桥梁(根目录下创建文件夹dbconnectorm 里面创建dbconnect.js)
const dbConfig =require("../dbconfigorm/dbconfig.js");//获取到配置 const Sequelize = require("sequelize"); const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, { host: dbConfig.HOST, dialect: dbConfig.dialect, pool: { max: dbConfig.pool.max, min: dbConfig.pool.min, acquire: dbConfig.pool.acquire, idle: dbConfig.pool.idle } }); module.exports = sequelize;
②接着我们就可以使用它了(为了做到环境隔离 我们在routes下新建一个dataormop.js文件用来使用sequelize操作数据库)
const router = require('koa-router')() const request = require("request"); const https = require('https'); const koa2Req = require('koa2-request') const sequelize=require("../dbconnectorm/dbconnect.js"); router.get('/orisql', async (ctx) => { const demo = await sequelize.query("SELECT * FROM demo"); ctx.body={list:demo} ctx.status=200 }) router.get('/orisql2', async (ctx) => { var id=3 const res = await sequelize.query(`SELECT * FROM demo where id=${id}` ,{ model: demo, mapToModel: true }); ctx.body={list:res} ctx.status=200 }) router.get('/orisql3', async (ctx) => { const res = await sequelize.query(`SELECT * FROM demo where id=?` ,{ replacements: [3], model: demo, mapToModel: true }); ctx.body={list:res} ctx.status=200 }) router.get('/orisql4', async (ctx) => { const res = await sequelize.query('SELECT count(*) as countnum FROM demo'); ctx.body={list:res} ctx.status=200 }) module.exports = router.routes()
到此为止:对于数据库的操作也算是接近尾声。(我的demo里会提供基于orm的方式操作数据,但是需要对数据库非常清晰,请自行要就学习)
第七步:
现在我们可以做到前端访问后端,后端查询后数据库把数据返回给前端 ,这样看已经很像一个项目了,但似乎又少了一点什么
少了啥了 ,就是访问权限(记得和后面的认证权限区分开)
什么叫访问权限 就是 带token访问
什么叫认证权限 就是 这个人有没有资格访问我这个url
(两者的区别 访问权限 你有没有资格进去 认证权限 你进去了有没有资格动屋里 的东西)
这里介绍一下 常用的jwt 访问权限:
举一个流程例子:
***当用户只有登录成功的时候才能进去首页 那么 系统里除了登录/注册这些功能外 其余的功能是不是都需要一个权限 怎么实现了 就是当前端提交用户名和密码到后端后,后端判断是否正确如果正确那么返回给前端一个token(也就是很长的字符串),前端拿到后放入到以后请求的header里面,每当一个请求来时都要先判断一个这个token有没有效(下面我们仅仅用代码去实现jwt 需要packege.json中添加jsonwebtoken)
①我们要回到app.js 这是我们的主文件
const Koa = require('koa'); const app = new Koa(); const Router = require('koa-router'); const router = new Router(); const views = require('koa-views') const path = require('path') const session = require('koa-session'); const bodyParser=require('koa-bodyparser'); const request = require("request"); const https = require('https'); const querystring = require('querystring'); const koa2Req = require('koa2-request') const liutianming = require('./routes/liutianming/route.js') const orms = require('./routes/dataormop.js') router.use('/liutianming',liutianming) router.use('/orm',orms) app.use(router.routes()); app.listen(8888);
看上面代码 我们新增了两个路由一个liutianming 指向一个一个js(用client操作数据库) 一个 orm(用sequelize操作数据库)
那我们现在就将给这个url添加权限 怎么弄呢
第一种:
我们想下 如果我们要给所有的url添加权限 最简单的判断 每个url对应的函数里去验证一下token(此方法坏处:要写好多代码 ,好烦好扯淡)
第二种:
koa提供了一个钩子函数:就是在执行所有的url前都要执行一个函数 这个就避免了我们写那么多(直接上代码)
const Koa = require('koa'); const app = new Koa(); const Router = require('koa-router'); const router = new Router(); const views = require('koa-views') const path = require('path') const session = require('koa-session'); const bodyParser=require('koa-bodyparser'); const request = require("request"); const https = require('https'); const querystring = require('querystring'); const koa2Req = require('koa2-request') const jwt = require('jsonwebtoken'); const liutianming = require('./routes/liutianming/route.js') const whiteList = ['/login','/reg'] const orms = require('./routes/dataormop.js') function verifyToken(token) { return new Promise((resolve, reject) => { jwt.verify(token, 'key', {ignoreNotBefore: true}, (error, result) => { if(error){ resolve({code:401,msg:'token expired'}) } else { resolve(result) } }) }) } app.use(async (ctx,next) => { if(!whiteList.includes(ctx.request.url)) { //在线获取新的token(php版本) const resurl = await koa2Req({ url: 'http://101.35.96.100/juyan/index/login?username=admini&password=a123456' }); let body = JSON.parse(resurl.body); token=body.token //这是一个过期的token //token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleUlkIn0.eyJpc3MiOiJteSIsImF1ZCI6InB1YmxpYyIsImlhdCI6MTY4MzM1MjcwNiwibmJmIjoxNjgzMzUyNzA2LCJleHAiOjE2ODM5NTc1MDYsImRhdGEiOnsiaWQiOjEsInVzZXJuYW1lIjoiYWRtaW5pIiwicGFzc3dvcmQiOiJhMTIzNDU2IiwiZW1haWwiOiIxMjNAMTYzLmNvbSIsInJlYWxuYW1lIjoiMTIzIiwicGhvbmUiOiIxNTIzNzE1NjU3MyIsImltZyI6Imh0dHBzOi8vbmV3c2Fhcy53dWhhbnpodWFuZ3hpdTAxLmNuL2NyLTE4MmMxY2VmYjlmZDllNDk4MDY5YWM2NTEyMzMyZTI1MjAyMzAyMTEuanBnIiwicmVnX2lwIjoiMTI3LjAuMC4xIiwibG9naW5fdGltZSI6MTY4MDU3Mjk2NywibG9naW5faXAiOiIzOS4xNDkuMTIuMTg0IiwidXBkYXRlX3RpbWUiOjE2ODA1NzI5NjcsImlzX2VuYWJsZWQiOjEsImdyb3VwX2lkIjoxLCJjcmVhdGVfdGltZSI6MTU0MDk3NTIxMywiZGVsZXRlX3RpbWUiOjB9fQ.rB7rUIw6N5xOhgR036yytWE1wnw_z8PcYMEO4NErk0Y' //生成jwt token(nodejs版本) // const token = jwt.sign({id:1}, 'key', { expiresIn:60*60}) const res=await verifyToken(token) // console.log(res) if(res.code==401){ ctx.status = 401; ctx.body = res; }else{ await next() } } else { await next() } }); // router.get('/', async (ctx) => { // ctx.body='hello /'; // ctx.status=200 // }) // router.get('/', async (ctx) => { // ctx.body='hello /'; // ctx.status=200 // }) router.use('/liutianming',liutianming) router.use('/orm',orms) app.use(router.routes()); app.use(router.allowedMethods({ })); app.listen(8888);
附注资料:
①如何处理跨域问题
方法一:引入koa2-cors包
const cors = require('koa2-cors'); /* 若添加配置项 可以手动设置允许哪些域名访问能跨域 比如 来自www.baidu.com的域名的请求允许跨域*/ app.use( cors({ origin: function(ctx) { //设置允许来自指定域名请求 return 'http://www.baidu.com'; //只允许http://www.baidu.com这个域名的请求 }, maxAge: 5, credentials: true, //是否允许发送Cookie allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法 allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //设置服务器支持的所有头信息字段 exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段 }) );
此时就可以跨域了
方法二:
还记得之前前面讲的验证token的时候,我们讲了一个
app.use(async (ctx,next) => { if(!whiteList.includes(ctx.request.url)) { //在线获取新的token(php版本) const resurl = await koa2Req({ url: 'http://101.35.96.100/juyan/index/login?username=admini&password=a123456' }); let body = JSON.parse(resurl.body); token=body.token //这是一个过期的token //token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleUlkIn0.eyJpc3MiOiJteSIsImF1ZCI6InB1YmxpYyIsImlhdCI6MTY4MzM1MjcwNiwibmJmIjoxNjgzMzUyNzA2LCJleHAiOjE2ODM5NTc1MDYsImRhdGEiOnsiaWQiOjEsInVzZXJuYW1lIjoiYWRtaW5pIiwicGFzc3dvcmQiOiJhMTIzNDU2IiwiZW1haWwiOiIxMjNAMTYzLmNvbSIsInJlYWxuYW1lIjoiMTIzIiwicGhvbmUiOiIxNTIzNzE1NjU3MyIsImltZyI6Imh0dHBzOi8vbmV3c2Fhcy53dWhhbnpodWFuZ3hpdTAxLmNuL2NyLTE4MmMxY2VmYjlmZDllNDk4MDY5YWM2NTEyMzMyZTI1MjAyMzAyMTEuanBnIiwicmVnX2lwIjoiMTI3LjAuMC4xIiwibG9naW5fdGltZSI6MTY4MDU3Mjk2NywibG9naW5faXAiOiIzOS4xNDkuMTIuMTg0IiwidXBkYXRlX3RpbWUiOjE2ODA1NzI5NjcsImlzX2VuYWJsZWQiOjEsImdyb3VwX2lkIjoxLCJjcmVhdGVfdGltZSI6MTU0MDk3NTIxMywiZGVsZXRlX3RpbWUiOjB9fQ.rB7rUIw6N5xOhgR036yytWE1wnw_z8PcYMEO4NErk0Y' //生成jwt token(nodejs版本) // const token = jwt.sign({id:1}, 'key', { expiresIn:60*60}) const res=await verifyToken(token) console.log(res) if(res.code==401){ ctx.status = 401; ctx.body = res; }else{ await next() } } else { await next() } });
其实处理跨域的问题的原理就是在服务端给浏览器响应的时候都在请求头上添加一些东西,这些东西的作用就是让浏览器任何这个会话的行为不存在不符合同源策略的情况,所以我们改造一下我们的代码:
app.use(async (ctx,next) => { ctx.set('Access-Control-Allow-Origin', '*'); //允许来自所有域名请求(不携带cookie请求可以用*,如果有携带cookie请求必须指定域名) // ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); // 只允许指定域名http://localhost:8080的请求 ctx.set('Access-Control-Allow-Methods', 'OPTIONS, GET, PUT, POST, DELETE'); // 设置所允许的HTTP请求方法 ctx.set('Access-Control-Allow-Headers', 'x-requested-with, accept, origin, content-type'); // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段. // 服务器收到请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。 ctx.set('Content-Type', 'application/json;charset=utf-8'); // Content-Type表示具体请求中的媒体类型信息 ctx.set('Access-Control-Allow-Credentials', true); // 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。 // 当设置成允许请求携带cookie时,需要保证"Access-Control-Allow-Origin"是服务器有的域名,而不能是"*"; ctx.set('Access-Control-Max-Age', 300); // 该字段可选,用来指定本次预检请求的有效期,单位为秒。 // 当请求方法是PUT或DELETE等特殊方法或者Content-Type字段的类型是application/json时,服务器会提前发送一次请求进行验证 // 下面的的设置只本次验证的有效时间,即在该时间段内服务端可以不用进行验证 ctx.set('Access-Control-Expose-Headers', 'myData'); // 需要获取其他字段时,使用Access-Control-Expose-Headers, if(!whiteList.includes(ctx.request.url)) { //在线获取新的token(php版本) const resurl = await koa2Req({ url: 'http://101.35.96.100/juyan/index/login?username=admini&password=a123456' }); let body = JSON.parse(resurl.body); token=body.token //这是一个过期的token //token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImtleUlkIn0.eyJpc3MiOiJteSIsImF1ZCI6InB1YmxpYyIsImlhdCI6MTY4MzM1MjcwNiwibmJmIjoxNjgzMzUyNzA2LCJleHAiOjE2ODM5NTc1MDYsImRhdGEiOnsiaWQiOjEsInVzZXJuYW1lIjoiYWRtaW5pIiwicGFzc3dvcmQiOiJhMTIzNDU2IiwiZW1haWwiOiIxMjNAMTYzLmNvbSIsInJlYWxuYW1lIjoiMTIzIiwicGhvbmUiOiIxNTIzNzE1NjU3MyIsImltZyI6Imh0dHBzOi8vbmV3c2Fhcy53dWhhbnpodWFuZ3hpdTAxLmNuL2NyLTE4MmMxY2VmYjlmZDllNDk4MDY5YWM2NTEyMzMyZTI1MjAyMzAyMTEuanBnIiwicmVnX2lwIjoiMTI3LjAuMC4xIiwibG9naW5fdGltZSI6MTY4MDU3Mjk2NywibG9naW5faXAiOiIzOS4xNDkuMTIuMTg0IiwidXBkYXRlX3RpbWUiOjE2ODA1NzI5NjcsImlzX2VuYWJsZWQiOjEsImdyb3VwX2lkIjoxLCJjcmVhdGVfdGltZSI6MTU0MDk3NTIxMywiZGVsZXRlX3RpbWUiOjB9fQ.rB7rUIw6N5xOhgR036yytWE1wnw_z8PcYMEO4NErk0Y' //生成jwt token(nodejs版本) // const token = jwt.sign({id:1}, 'key', { expiresIn:60*60}) const res=await verifyToken(token) console.log(res) if(res.code==401){ ctx.status = 401; ctx.body = res; }else{ await next() } } else { await next() } });
②路由前缀:
比如我们写好了接口
http://127.0.0.1:8888/orm/select
我们需要给项目所有的接口都添加一个前缀比如
http://127.0.0.1:8888/node/orm/select
http://127.0.0.1:8888/node/orm/add
http://127.0.0.1:8888/node/orm/del
则需要配置
router.prefix('/node')
③文件上传
方法一:安装 npm i koa-multer
添加代码
const Multer=require("koa-multer") let storage=Multer.diskStorage({ destination:function(req,file,cb){ cb(null,"upload") }, filename:function(req,file,cb){ var fileFormat = (file.originalname).split("."); cb(null,Date.now() + "." + fileFormat[fileFormat.length - 1]); } }) let upload=Multer({storage:storage})
router.post("/upload",upload.single("file"),async ctx=>{ console.log("ctx.req:",ctx.req) ctx.body={ msg:"上传成功" } })
方法二:
安装koa-body npm i koa-body
const { koaBody }= require('koa-body'); //解析上传文件的插件 app.use(koaBody({ multipart: true, formidable: { uploadDir:path.join(__dirname,'upload/'), maxFileSize: 2000 * 1024 * 1024 // 设置上传文件大小最大限制,默认2M } }))
router.post("/upload",async (ctx)=>{ const file = ctx.request.files.file //得到文件对象 /* 创建一个读流 (输入流):怎么理解呢 ?在内存或者磁盘开辟一个临时的存储空间, 把文件对象的东西读出来放到这个临时的存储空间*/ const reader = fs.createReadStream(file.filepath); //创建一个存放文件的路径 比如 upload文件夹下1.png文件 // upload/1.png let filePath = 'upload/' + `/${file.originalFilename}`; /* 创建一个写流(输出流):可以理解成一个指向文件的指针*/ const upStream = fs.createWriteStream(filePath); /* 创建一个写流 :怎么理解呢 ?把刚才在内存或者磁盘开辟一个临时的存储空间 的内容通过刚才新建的写流 写入到上面filepath中 这样就会在upload文件夹下生成1.png这张图片*/ reader.pipe(upStream); //可以把文件对象返回前端 看看都是写什么东东 ctx.body = JSON.stringify(ctx.request.files); })
④提交get/post请求 之 https版
安装koa2-request/request 运行npm i koa2-request
const koa2Req = require('koa2-request') const https = require('https'); //举例向微信提交get请求 获取到小程序accesstoken router.get('/general_accesstoken', async (ctx) => { const res = await koa2Req({ url: 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='+ctx.query.appid+'&secret='+ctx.query.secret }); let body = JSON.parse(res.body); ctx.body=body.access_token ctx.status=200 })
//提交https post请求 const request = require("request"); const https = require('https'); let publisher_adpos_general=async (accesstoken)=>{ return new Promise((resolve,reject)=>{ request({ timeout:5000, // 设置超时 method:'GET', //请求方式 url:'https://api.weixin.qq.com/publisher/stat?action=publisher_adpos_general&page=0&page_size=0&start_date=2023-05-21&end_date=2023-05-21', //url qs:{ //参数,注意get和post的参数设置不一样 access_token:accesstoken, } },function (error, response, body) { if (!error && response.statusCode == 200) { resolve(body) }else{ reject(error) } }); }) } router.get('/publisher_adpos_general', async (ctx) => { var data=null; let res =await publisher_adpos_general(ctx.query.accesstoken) ctx.body=res; ctx.status=200 })
⑤pm2管理器
由于js是单线程应用 一旦报错 整个进程就会再也无法继续进行下去,于是一种以守护进程的方法启动项目的方式就此出现 这个叫做pm2
1.安装pm2 npm install -g pm2
2.使用 pm2 start 文件名(比如:app.js server.js koaserver.js)
我们会看到 并不像 node ./app.js 那样卡在了控制台 可以理解 后台默默的启动了
3.我们启动多个nodejs的项目
4.如何关闭pm2
首先我们能在pm2里面看到我们启动了两个项目 app 和 server
关闭命令 pm2 delete app / pm2 delete server
番外版:
①七牛云文件上传
以前我们讲的都是前端上传文件到服务器上 ,在市场上还有一种叫做第三方对象存储 ,七牛云就是其中的一种
1.安装qiniu 运行npm i qiniu
2.现在我们对之前上传到服务器的代码进行改装
router.post("/upload",async (ctx)=>{ const file = ctx.request.files.file // const reader = fs.createReadStream(file.filepath); // let filePath = 'upload' + `\\${file.originalFilename}`; // const upStream = fs.createWriteStream(filePath); // reader.pipe(upStream); const data=await uploadFile(file.filepath, file.originalFilename) ; ctx.body = JSON.stringify(data); }) function uploadFile(reader, key) { return new Promise((resolve, reject) => { /* 七牛后台配置 */ var ACCESS_KEY = ''; var SECRET_KEY = ''; var bucket = ''; const domain = ''; const mac = new qiniu.auth.digest.Mac(ACCESS_KEY, SECRET_KEY); const config = new qiniu.conf.Config(); config.zone = qiniu.zone.Zone_z0; const formUploader = new qiniu.form_up.FormUploader(config); const putExtra = new qiniu.form_up.PutExtra(); // 生成上传凭证 const options = { scope: bucket + ':' + key, }; const putPolicy = new qiniu.rs.PutPolicy(options); const uploadToken = putPolicy.uploadToken(mac); // 上传文件 formUploader.putFile(uploadToken, key, reader, putExtra, function (err, body, info) { if (err) { reject(err); } else { resolve(domain + '/' + key); } }); }); }
②nodejs 使用websocket 网络通信
1.安装nodejs-websocket 运行npm i nodejs-websocket
2.新建ws.js文件
nodejs代码:
const ws = require('nodejs-websocket'); const TYPE_ENTER = 0 const TYPE_LEAVE = 1 const TYPE_MSG = 2 //记录当前连接上的用户登录 let count = 0 const server = ws.createServer(conn =>{ console.log('新的连接') count++ conn.userName = `用户${count}` broadcast({ type: TYPE_ENTER, msg: `${conn.userName}进入了聊天室`, time: new Date().toLocaleDateString() }) conn.on('text',data=>{ //2. 当接收到某个用户信息是,发给所有人 broadcast({ type: TYPE_MSG, msg: data, time: new Date().toLocaleTimeString() }) }) conn.on('close',data=>{ //3.有人退出 告诉所有人 console.log('关闭连接') count-- broadcast({ type: TYPE_LEAVE, msg:`${conn.userName}离开了聊天室`, time: new Date().toLocaleTimeString() }) }) conn.on('error',data=>{ console.log('发生异常') }) }) // 广播 给所有人发消息 function broadcast(msg) { //server.connections : 表示所有的用户 server.connections.forEach(item=>{ item.send(JSON.stringify(msg)) }) } server.listen(2349,()=>{ console.log('监听端口2349') })
原生html代码:
<html>
<head>
<title>WebSocket测试</title>
<script>
var ws = null;
function send() {
var message = document.getElementById("text").value;
ws.send(message);
}
function connect() {
if(ws == null)
{
ws = new WebSocket("ws://127.0.0.1:2349/");
ws.onopen = function ()
{
alert("连接成功");
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
document.getElementById("showMes").value += evt.data + "\n";
};
ws.onclose = function ()
{
alert("断开了连接");
};
}
}
function closeSocket()
{
ws.close();
ws = null;
}
window.onload = function(){
}
</script>
</head>
<body>
<input type="button" οnclick="connect()" value="连接" />
<input type="button" οnclick="closeSocket()" value="关闭" />
<input type="button" οnclick="send()" value="发送" />
<input type="text" id="text" />
<br/>
<textarea rows="3" cols="30" id="showMes" style="width:100%;height:500px;"></textarea>
</body>
</html>
运行如下:
vue代码(新增一个测试组件 ChatDemo.vue)
<template> <div> <input type="button" @click="connect()" value="连接" /> <input type="button" @click="closeSocket()" value="关闭" /> <input type="button" @click="send()" value="发送" /> <input type="text" id="text" v-model="msg"/> <br/> <textarea rows="3" cols="30" ref="showMes" style="width:100%;height:500px;"></textarea> </div> </template> <script> export default { data() { return { wsUrl: 'ws://127.0.0.1:2349/', websock: null, //ws实例 msg:'' }; }, mounted() { //初始化websocket,此页面建立了2个长链接 // this.initWebSocket(); }, destroyed() { //离开路由之后断开websocket连接 this.websock.close(); }, methods: { connect(){ this.initWebSocket(); }, closeSocket(){ this.websocketclose() }, send(){ this.websock.send(this.msg); }, //初始化Websocket--sys_info initWebSocket() { if (typeof WebSocket === "undefined") return console.log("您的浏览器不支持websocket"); this.websock = new WebSocket(this.wsUrl); this.websock.onmessage = this.websocketonmessage; this.websock.onopen = this.websocketonopen; this.websock.onerror = this.websocketonerror; this.websock.onclose = this.websocketclose; }, websocketonopen() { // let action = { message: "sys_info" }; // this.websocketsend(JSON.stringify(action)); }, websocketonerror() { //链接建立失败重连 this.initWebSocket(); }, websocketonmessage(e) { //数据接收 const redata = JSON.parse(e.data); console.log(redata) this.$refs.showMes.value += JSON.stringify(redata) + "\n"; }, websocketsend(Data) { //数据发送 // console.log("数据发送", Data); this.websock.send(Data); }, websocketclose(e) { //关闭 // console.log("断开链接", e); this.websock.close(); }, }, }; </script> <style> </style>
效果图:(先运行html然后运行vue)