建设银行e路护航官方网站登陆,网游推广,营销方式和渠道,北京印刷厂Nodejs是一个js运行环境#xff0c;可以让js开发后端程序#xff0c;实现几乎其他后端语言实现的所有功能#xff0c;能够让js与其他后端语言平起平坐。
nodejs是基于v8引擎#xff0c;v8是Google发布的开源js引擎#xff0c;本身就是用于chrome浏览器的js解释部分#…Nodejs是一个js运行环境可以让js开发后端程序实现几乎其他后端语言实现的所有功能能够让js与其他后端语言平起平坐。
nodejs是基于v8引擎v8是Google发布的开源js引擎本身就是用于chrome浏览器的js解释部分现在把v8转移到服务器上用于做服务器端的软件。
Nodejs超强的高并发能力能够实现高性能服务器
node环境
只有v8引擎解析js没有dom和bom对象。
浏览器环境
Blink起到排版作用对html/css进行解析确定每个元素位置
V8解析js
CommonJS规范
我们可以把公共功能抽离成一个单独的js文件作为一个模块默认情况下这个里面的方法或者属性外面是没法访问的如果要外部可以访问模块里的方法或者属性就必须在模块里面通过exports 或者 module.exports 暴露属性或者方法。
module.exports {test,a}module.exports test//可以一个一个的多个导出
exports.test test
exports.a aconst a require(./test.js)
npm npm init npm install/i -g npm install/i --save -dev npm list -g列举目录下的安装包 npm info 包名 npm i/install md51指定安装版本 npm outdated检查包是否已过时 --save添加在dependencies
-dev/-D添加在devDependencies
-g全局安装
依赖版本前的符号
^2.1.0 ^表示会安装 2.*.* 的最新版本
~2.1.0 ^表示会安装 2.1.* 的最新版本
* ^表示会安装最新版本
nrm
nrm是npm的镜像源管理工具国外资源太慢可以使用这个在npm源间切换手动切换 npm config set registry https://registry.npm.taobao.org 全局安装 nrm npm i -g nrm 使用nrm
执行命令nrm ls 查看可选源 其中带有 * 的是当前使用源上面的输出表明是官方源。
切换nrm
切到taobao源 npm use taobao 测试速度
测试相应源的响应时间 nrm test 查看当前的仓库 npm config get registry 中国npm镜像
这是一个完整的npmjs.org镜像你可以用此代替官方版本只读同步频率目前为10分钟一次与官方服务同步之后可以使用 cnpm 下载。 npm i -g cnpm --registryhttps://registry.npmmirror.com yarn npm install -g yarn 比npm快yarn 缓存每个下载过的包所以再次使用无须重复下载同时利用并行下载以最大化资源利用率因此安装速度更快。
安全性执行代码前yarn 会通过算法检验每个安装包的完整性。 开始新项目yarn init 添加依赖yarn add 包名 | yarn add 包名版本 | yarn add 包名 --dev 升级依赖包yarn upgrade 包名版本 移除依赖包yarn remove 包名 安装全部依赖yarn install ES模块化写法
在package.json中加入type选项即可使用es的模块化写法。 npm init {name: aaa,version: 1.0.0,description: ,main: index.js,type: module,scripts: {test: echo \Error: no test specified\ exit 1},author: jerry,license: ISC
}//暴露出 引入
export default obj import obj from ./1.jsexport const demo hello import { demo } from ./2/js 内置模块
http模块
创建服务器node app.js 启动服务器
全局安装 nodemon 自动重启node-dev也可以 npm i -g nodemon / npm i -g node-dev 启动服务器 nodemon app.js const http require(http)
console.log(http);const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8 });//通过访问不同的url发送不同的数据res.end(JSON.stringify(render(req.url)))
})server.listen(8000, () {console.log(start);
})
function render(url) {switch (url) {case /home:return { data: home }case /about:return {data: about}default:return index}
}
JsonP
解决跨域的办法之一后端直接返回一个函数并且执行前提是在html文件有已经定义好的函数前后端保持一致。
const http require(http)
const url require(url)const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8 });//通过访问不同的url发送不同的数据const {pathname,query} url.parse(req.url,true) //http://localhost:8000/home?callbacktest2res.end(${query.callback}(${JSON.stringify(render(pathname))}))
})server.listen(8000, () {console.log(start);
})function render(url) {switch (url) {case /home:return { data: home }case /about:return {data: about}default:return index}
}
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/headbody/body
scriptfunction test(param){console.log(param);}function test2(param){console.log(param);}
/script
script srchttp://localhost:8000/home?callbacktest2/script
/html cors
添加cors头解决跨域
const http require(http)
const url require(url)const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8, access-control-allow-origin: * });//通过访问不同的url发送不同的数据const { pathname, query } url.parse(req.url, true) //http://localhost:8000/homeconsole.log(query);res.end(JSON.stringify(render(pathname)))
})server.listen(8000, () {console.log(start);
})function render(url) {switch (url) {case /home:return { data: home }case /about:return {data: about}default:return index}
}
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/headbody/body
scriptfetch(http://localhost:8000/home).then(resres.json()).then(res{console.log(res);})
/script
/html get
http既可以做服务端也可以做客户端可以解决跨域成为代理服务
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/headbody/body
scriptfetch(http://localhost:8000/home).then(resres.json()).then(res{console.log(res);})
/script
/html
const http require(http)
const https require(https)
const url require(url)const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8, access-control-allow-origin: * });//通过访问不同的url发送不同的数据const { pathname, query } url.parse(req.url, true) //http://localhost:8000/homeswitch (pathname) {case /home:httpget((data){res.end(data)})case /about:return {data: about}default:return index}
})server.listen(8000, () {console.log(start);
})function httpget(callback){let data //https://api.douban.com/v2/movie/in_theatershttps.get(https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json,res{res.on(data,chunk{datachunk})res.on(end,(){callback(data)})})
} post
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/headbody/body
scriptfetch(http://localhost:8000/home).then(resres.json()).then(res{console.log(res);})
/script
/html
const http require(http)
const https require(https)
const url require(url)const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8, access-control-allow-origin: * });//通过访问不同的url发送不同的数据const { pathname, query } url.parse(req.url, true) //http://localhost:8000/homeswitch (pathname) {case /home:httppost((data){res.end(data)})case /about:return {data: about}default:return index}
})server.listen(8000, () {console.log(start);
})function httppost(callback){let data //https://m.xiaomiyoupin.com/mtop/market/search/placeHolder//请求的信息let options{hostname:m.xiaomiyoupin.com,port:443,path:/mtop/market/search/placeHolder,method: POST,headers:{Content-Type:application/json//Content-Type:x-www-form-urlencoded 这种对应传参req.write(namezhangsanage6)}}let req https.request(options,res{res.on(data,chunk{datachunk})res.on(end,(){callback(data)})})req.write(JSON.stringify([{},{baseParam:{ypClient:1}}]))req.end()
}
爬虫
可以直接爬出网页调用接口可以拿到网页的html结构数据。然后通过过滤出有效的信息返回给前端。需要工具 cheerio用法类似于jQuery npm i --save cheerio const http require(http)
const https require(https)
const url require(url)
const cheerio require(cheerio)const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8, access-control-allow-origin: * });//通过访问不同的url发送不同的数据const { pathname, query } url.parse(req.url, true) //http://localhost:8000/homeswitch (pathname) {case /home:httpget((data) {res.end(spider(data))})case /about:return {data: about}default:return index}
})server.listen(8000, () {console.log(start);
})function httpget(callback) {let data //https://api.douban.com/v2/movie/in_theatershttps.get(https://i.maoyan.com/?requestCode32779c7926dc56716d208cc297ee8da1z5u0y, res {res.on(data, chunk {data chunk})res.on(end, () {callback(data)})})
}function spider(data) {let $ cheerio.load(data)//通过选择器来定位到元素let $movielist $(.column.content)let movies []$movielist.each((index, value) {movies.push({ title: $(value).find(.title).text(), grade: $(value).find(.grade).text(),actor: $(value).find(.actor).text()})})console.log($movielist);console.log(movies);return JSON.stringify(data)
}
url模块
parse和format旧版
parse在使用查询字符串的方式请求时可以用来解析带有查询字符串的请求。两个参数url传入的url第二个是否将参数解析为对象格式默认是falsepathname是对应的urlquery是请求的请求参数对象。
format与parse正好相对立将url.parse解析的对象转为对应的url地址
const http require(http)
const url require(url)const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8 });//通过访问不同的url发送不同的数据// {// protocol: null,// slashes: null,// auth: null,// host: null,// port: null,// hostname: null,// hash: null,// search: ?a1b2,// query: [Object: null prototype] { a: 1, b: 2 },// pathname: /home,// path: /home?a1b2,// href: /home?a1b2// }const {pathname,query} url.parse(req.url,true) //http://localhost:8000/home?a1b2console.log(url.format(url.parse(req.url,true))); //格式化为url地址res.end(JSON.stringify(render(pathname)))
})server.listen(8000, () {console.log(start);
})function render(url) {switch (url) {case /home:return { data: home }case /about:return {data: about}default:return index}
}
resolve 旧版
用于拼接url
const url require(url)
let a url.resolve(1/2/3/,4) // 1/2/3/4
let b url.resolve(1/2/3,4) // 1/2/4let c url.resolve(http://baidu.com/,/4) // http://baidu.com/4域名后的所有都被替换
let d url.resolve(http://baidu.com/1,/4) //http://baidu.com/4
URL对象新
也是用于获取请求的url地址以及参数
const http require(http)
const url require(url)const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8 });//通过访问不同的url发送不同的数据const myURL new URL(req.url,http://127.0.0.1:3000) //第二个参数必填合法地址这里取本地因为这里请求的是本地console.log(myURL);// {// href: http://127.0.0.1:3000/home?a1b2,// origin: http://127.0.0.1:3000,// protocol: http:,// username: ,// password: ,// host: 127.0.0.1:3000,// hostname: 127.0.0.1,// port: 3000,// pathname: /home,// search: ?a1b2,// searchParams: URLSearchParams { a 1, b 2 },// hash: // }const pathname myURL.pathnameres.end(JSON.stringify(render(pathname)))
})server.listen(8000, () {console.log(start);
})function render(url) {switch (url) {case /home:return { data: home }case /about:return {data: about}default:return index}
}
URLSearchParams类
提供对 URL 查询的读写访问在全局对象上也可用WHATWG URLSearchParams 接口和querystring 模块有相似的用途但querystring模块用途更广因为它允许自定义的分隔符 和 此api纯粹是为网址查询字符串设计的。 const myURL new URL(https://example.org/?abc123)console.log(myURL.searchParams.get(abc)); //123myURL.searchParams.append(www,xyz)console.log(myURL.href); //https://example.org/?abc123wwwxyzmyURL.searchParams.delete(abc)myURL.searchParams.set(a,b)console.log(myURL.href) //https://example.org/?abconst newSearchParams new URLSearchParams(myURL.searchParams)console.log(newSearchParams); // { www xyz, a b }newSearchParams.append(a,c)console.log(myURL.href); //https://example.org/?abconsole.log(newSearchParams.toString());//wwwxyzabac
拼接作用 let b new URL(one,https://example.org/?abc123)console.log(b.href); //https://example.org/one
详情参考URL | Node.js v19 API
format新 参数 urlWHATWG 网址对象 optionsauthboolean 如果序列化的网址字符串包含用户名和密码则为true否则为false默认值true。 fragmentboolean如果序列化的网址字符串包含片段则为true否则为false默认值true。 searchboolean如果序列化的网址字符串包含搜索查询则为true否则为false默认值true。 unicodeboolean如果序列化的网址字符串的主机组件中的unicode字符应该被直接编码而不是Punycode编码默认值false。 返回值string 返回 WHATWG 网址 对象的网址 String 表示的可自定义的序列化。
网址对象具有 toString方法和href属性用于返回网址的字符串序列化。但是这些都不能以任何方式自定义。url.format(url,[options])方法允许对输出进行基本的自定义。 const myURL new URL(https://a:b测试?abc#foo)console.log(url.format(myURL));//https://a:bxn--0zwm56d/?abc#foo 编译了unicode码console.log(url.format(myURL,{unicode:true}));//https://a:b测试/?abc#foo 保持unicode码console.log(url.format(myURL,{unicode:true,auth:false}));//https://测试/?abc#foo 去除了a:bconsole.log(url.format(myURL,{unicode:true,fragment:false}));//https://a:b测试/?abc 去除了#后面的内容console.log(url.format(myURL,{unicode:true,search:false}));//https://a:b测试/#foo 去除了?后面的内容到#结束
url.fileURLToPath(url) 和 url.pathToFileURL(path) url.fileURLToPath(url) 新增于10.12 参数urlURL | string 要转换为路径的文件网址字符串或网址对象必须绝对路径 返回string 完全解析的特定于平台的node.js文件路径。 此函数可确保正确解码宝粉笔编码字符并确保跨平台有效的绝对路径字符串。 new URL的方式对于文件路径是有问题的所以采用fileURLToPath。 console.log(url.fileURLToPath(file:///C:/path/)) // /C:/path/console.log(new URL(file:///C:/path/).pathname) // /C:/path/console.log(url.fileURLToPath(file://nas/foo.txt)) // //nas//foo.txt(Windows)console.log(new URL(file://nas/foo.txt).pathname) // /foo.txtconsole.log(url.fileURLToPath(file:///你好.txt)) // /你好.txt(POSIX)console.log(new URL(file:///你好.txt).pathname) // /%E4%BD%A0%E5%A5%BD.txtconsole.log(url.fileURLToPath(file:///hello world)) // /hello world (POSIX)console.log(new URL(file:///hello world).pathname) // /hello%20worldconst _ url.pathToFileURL(path) 新增于10.12 参数pathstring 要转为文件网址的路径 返回URL 文件网址对象 该函数确保path被绝对解析并且在转换为文件网址时正确编码网址控制字符。 import {pathToFileURL} from urlnew URL(/foo#1,file) //错误file:///foo#1
pathToFileURL(/foo#1) //正确file:///foo%231(POSIX)new URL(/some/path%.c,file) //错误file:///some/path%.c
pathToFileURL(/some/path%.c) //正确file:///some/path%25.c(POSIX) url.urlToHttpOptions(url) 参数urlurl 要转换为选项对象的 WHATWG对象。 返回Object选项对象 protocal string 使用的协议 hostname string 向其发出请求的服务器域名或ip地址 hash string 网址的片段部分 search string 网址的序列化的查询部分 pathname string 网址的路径部分 path string 请求的绝对路径。应包括查询字符串如果有。当请求路径包含非法字符时抛出异常。目前只有空格被拒绝。 href string 序列化的网址 port number 远程服务器的端口 auth string 基本身份验证 该实用函数按照http.request() 和 https.request() API的预期将网址对象转换为普通选项对象。 const url require(url)const myURL new URL(https://a:b测试?abc#foo)console.log(url.urlToHttpOptions(myURL));// {// protocol: https:,// hostname: xn--0zwm56d,// hash: #foo,// search: ?abc,// pathname: /,// path: /?abc,// href: https://a:bxn--0zwm56d/?abc#foo,// auth: a:b// }
querystring模块
parse解析
const querystring require(querystring)let str namezhangsanage12
let obj querystring.parse(str)
console.log(obj); //{ name: zhangsan, age: 12 }
stringify编码
const querystring require(querystring)let obj { name: zhangsan, age: 12 }
let str querystring.stringify(obj)
console.log(str); //namezhangsanage12
escape 转译特殊字符
const querystring require(querystring)let str namezhangsanage8urlhttps://baidu.com
let escape querystring.escape(str)
console.log(escape); //name%3Dzhangsan%26age%3D8%26url%3Dhttps%3A%2F%2Fbaidu.com
unescape 解码特殊字符
const querystring require(querystring)let str name%3Dzhangsan%26age%3D8%26url%3Dhttps%3A%2F%2Fbaidu.com
let unescape querystring.unescape(str)
console.log(unescape); //namezhangsanage8urlhttps://baidu.com
event 模块
类似于发布订阅使用event模块将之前的get方法做一下优化需要引入EventEmitter对象对event添加监听的方法在数据处理好后抛出并发送个给前端。
const http require(http)
const https require(https)
const { EventEmitter } require(events)
const url require(url)
let event null
const server http.createServer((req, res) {res.writeHead(200, { content-type: application/json;charsetutf-8, access-control-allow-origin: * });//通过访问不同的url发送不同的数据const { pathname, query } url.parse(req.url, true) //http://localhost:8000/homeswitch (pathname) {case /home:event new EventEmitter()event.on(play,(data){res.end(data)})httpget()case /about:return {data: about}default:return index}
})server.listen(8000, () {console.log(start);
})function httpget(){let data //https://api.douban.com/v2/movie/in_theatershttps.get(https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json,res{res.on(data,chunk{datachunk})res.on(end,(){event.emit(play,data)})})
}
fs文件操作模块
包括同步和异步的方法同步会阻塞进程需要搭配 try catch 语句来进行捕获错误。
同步方法在异步方法后面添加Sync即可由于node环境执行的js代码是服务器代码所以绝大部分需要在服务器运行期反复执行业务逻辑的代码必须使用异步否则同步代码在执行时期服务器停止响应js只有一个执行线程。
服务器启动时如果需要读取配置文件或者结束时需要写入到状态文件时可以使用同步代码因为这些代码只在启动和结束时执行一次不影响服务器正常运行时的异步操作。
但是在异步方法中容易产生回调地狱可以使用promise的写法
const fs require(fs).promisesfs.mkdir(./avater).then(res{console.log(res);
}).catch(err{console.log(err);
})
创建目录
const fs require(fs)
//创建文件夹
fs.mkdir(./avater,err{if(err err.code EEXIST){console.log(文件夹已存在);}
})//同步写法
try {fs.mkdirSync(./avater2)
} catch (error) {console.log(error);
}
修改目录名称
const fs require(fs)
//修改文件夹名称
fs.rename(./avater,./avater2,err{if(err err.code ENOENT){console.log(当前文件夹不存在);}
})
创建新的文件写入内容是覆盖之前的内容
const fs require(fs)
//创建文件
fs.writeFile(./avater2/a.txt,hello world,err{console.log(err);
}) 删除文件夹
//删除文件夹
fs.rmdir(./avater2,err{console.log(err)
})
追加文件内容
// 追加内容
fs.appendFile(./avater2/a.txt,\n你好,err{console.log(err);
})
读取文件内容
//读取文件
fs.readFile(./avater2/a.txt,utf-8,(err,data){if(!err){console.log(data);}
})
删除文件
//删除文件
fs.unlink(./avater2/a.txt,(err){console.log(err);
})
读取文件夹下的文件
//读取文件夹下的文件
fs.readdir(./avater2, (err, data) {if (!err) {console.log(data);//[ a.txt, b.txt, c.js ]}
})
查看文件或文件夹信息
主要用里面的判断文件或文件夹的方法。
//查看文件或文件夹信息
fs.stat(./avater2,(err,data){console.log(data.isDirectory()); //判断是否是文件目录console.log(data.isFile()); //判断是否是文件
})// {
// dev: 16777231,
// mode: 16877,
// nlink: 5,
// uid: 501,
// gid: 20,
// rdev: 0,
// blksize: 4096,
// ino: 12893475,
// size: 160,
// blocks: 0,
// atimeMs: 1692457394626.1108,
// mtimeMs: 1692457394548.2456,
// ctimeMs: 1692457394548.2456,
// birthtimeMs: 1692453342095.4685,
// atime: 2023-08-19T15:03:14.626Z,
// mtime: 2023-08-19T15:03:14.548Z,
// ctime: 2023-08-19T15:03:14.548Z,
// birthtime: 2023-08-19T13:55:42.095Z
// }
stream流模块
stream是node.js提供的又一个仅在服务端可用的模块目的是支持流这种数据结构。
创建可读流
const fs require(fs)const rs fs.createReadStream(./1.txt,utf-8)rs.on(data,chunk{console.log(chunk);
})rs.on(end,(){console.log(end);
})rs.on(error,(err){console.log(err);
})
创建可写流
const ws fs.createWriteStream(./2.txt,utf-8)ws.write(111)
ws.write(222)ws.end()
把读取的数据写入新的文件
const fs require(fs)
const rs fs.createReadStream(./1.txt)
const ws fs.createWriteStream(./2.txt)
rs.pipe(ws)
zlib
压缩资源传输
const http require(http)
const fs require(fs)
const zlib require(zlib)
const gzip zlib.createGzip()const server http.createServer((req, res) {const rs fs.createReadStream(./1.txt)res.writeHead(200, { content-type: application/x-javascript;charsetutf-8, access-control-allow-origin: *,Content-Encoding:gzip }); //告诉浏览器根据什么方式解压缩如不添加就是流rs.pipe(gzip).pipe(res) // 压缩
})server.listen(8000, () {console.log(start);
})
crypto
crypto模块的额目的是为了提供通用的加密和哈希算法。用纯js实现起来比较麻烦并且速度会很慢所以nodejs通过c/c实现后通过crypto这个模块暴露出来方便快捷。
MD5是一个常用的哈希算法用于给任意数据一个签名通常以十六进制的字符串表示也可以用 base64 的形式展示。
const crypto require(crypto)const hash crypto.createHash(md5)hash.update(hello world) //update方法默认字符串编码为utf-8也可以传入bufferconsole.log(hash.digest(hex)); //十六进制的形式展示
//console.log(hash.digest(base64)); //base64的形式展示update方法默认字符串编码为utf-8也可以传入buffer。
如果要计算SHA1只需要将 md5 改为 sha1就可以得到结果md5sha1sha256
const crypto require(crypto)const hash crypto.createHash(sha1)hash.update(hello world) //update方法默认字符串编码为utf-8也可以传入bufferconsole.log(hash.digest(hex)); //十六进制的形式展示
// console.log(hash.digest(base64)); //base64的形式展示Hmac算法
也是一种hash算法他可以利用md5或者SHA1等hash算法。不同的是Hmac还需要一个秘钥。只要秘钥发生变化生成的签名也就不同。
const crypto require(crypto)const hash crypto.createHmac(sha256,secret)hash.update(hello world) //update方法默认字符串编码为utf-8也可以传入bufferconsole.log(hash.digest(hex)); //十六进制的形式展示
// console.log(hash.digest(base64)); //base64的形式展示AES是一种常用的对称加密算法加解密都用同一个秘钥。crypto模块提供了AES支持但是需要自己封装好函数便于使用。
const crypto require(crypto)function encrypt(key,iv,data){ //iv是秘钥let dep crypto.createCipheriv(aes-128-cbc,key,iv)return dep.update(data,binary,hex)dep.final(hex) //update参数原始数据输入格式为 binary以十六进制展示
}function decrypt(key,iv,data){let crypted Buffer.from(data,hex).toString(binary)//十六进制的对象转为buffer,再转为二进制。let dep crypto.createDecipheriv(aes-128-cbc,key,iv)return dep.update(crypted,binary,utf8)dep.final(utf8)
}
//16 * 8 128 必须是8的倍数 key和iv都要遵循
let key abcdef1234567890
let iv 1234567890abcdeflet data jerryconsole.log(encrypt(key,iv,data));//895f852b13da04523548c2cfebf25eff
console.log(decrypt(key,iv,encrypt(key,iv,data)));//jerry路由
可以返回前端html页面也可以实现api接口
//server.jsconst http require(http)let Router {}
function use(obj){Router {...Router,...obj}
}function start() {const server http.createServer((req, res) {let myurl new URL(req.url, http://127.0.0.1)try {Router[myurl.pathname](req,res)} catch (error) {Router[/404](req,res)}})server.listen(3000, () {console.log(启动服务器);})
}exports.start start
exports.use use
const server require(./server)
const route require(./route)
const api require(./api)server.use({...route,...api}) //合并接口
server.start()
//route.jsconst routes {/home:(req,res){res.writeHead(200,{Content-Type:text/html;charsetutf-8})res.write(fs.readFileSync(./home.html),utf-8)res.end()},/index:(req,res){res.writeHead(200,{Content-Type:text/html;charsetutf-8})res.write(fs.readFileSync(./index.html),utf-8)res.end()},/favicon.ico:(req,res){// res.writeHead(200,{Content-Type:image/x-icon;charsetutf8})// res.write(fs.readFileSync(./favicon.ico))res.end()},/404:(req,res){res.write(404,utf-8)res.end()}
}
module.exports routes
//api.jsconst api {/api/findStr:(req,res){res.writeHead(200,{Content-Type:application/json;charsetutf-8})res.write({a:str})res.end()},
}
module.exports api
路由获取请求参数
根据服务端的地址访问 localhost:3000/index
!DOCTYPE html
html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
button idlogin登录-get/button
button idlogin2登录-post/buttonbody/body
script//get请求login.onclick function () {let username zhangsanlet passsword 123456fetch(/api/findStr?username${username}password${passsword}).then(res res.json()).then(res {console.log(res)})}//post请求login2.onclick function () {fetch(/api/findStrpost,{method:post,body:JSON.stringify({username:zhangsan,passsword:123456}),headers:{Content-Type:application/json}}).then(res res.json()).then(res {console.log(res)})}/script/html
参数获取上述的URL模块即可
const api {//get请求/api/findStr:(req,res){const myUrl new URL(req.url,http://127.0.0.1)let username myUrl.searchParams.get(username)let password myUrl.searchParams.get(password)console.log(username,password);res.writeHead(200,{Content-Type:application/json;charsetutf-8})res.write({a:str})res.end()},//post请求/api/findStrpost:(req,res){let post req.on(data,chunk{ //收集数据postchunk})req.on(end,(){console.log(post);res.writeHead(200,{Content-Type:application/json;charsetutf-8})res.write({a:str})res.end()})},
}
module.exports api
路由获取静态资源
这里需要一个插件 mime可以能够获取到对应的文件应当的content-type npm i mime const mime require(mime) mime.getType(txt) // text/plain mime.getExtension(text/plain) // txt const fs require(fs)
const path require(path)
const mime require(mime)const routes {/home:(req,res){res.writeHead(200,{Content-Type:text/html;charsetutf-8})res.write(fs.readFileSync(./home.html),utf-8)res.end()},/index:(req,res){res.writeHead(200,{Content-Type:text/html;charsetutf-8})res.write(fs.readFileSync(./index.html),utf-8)res.end()},/favicon.ico:(req,res){// res.writeHead(200,{Content-Type:image/x-icon;charsetutf8})// res.write(fs.readFileSync(./favicon.ico))res.end()},/404:(req,res){if(readStaticFile(req,res)){ //判断是否存在文件return}res.write(404,utf-8)res.end()}
}
function readStaticFile(req,res){const myURL new URL(req.url,http://127.0.0.1:3000)const pathname path.join(__dirname,myURL.pathname); //绝对路径const type mime.getType(myURL.pathname.split(.)[1]) //获取文件后缀if(myURL.pathname/) return falseif(fs.existsSync(pathname)){res.writeHead(200,{Content-Type:${type};charsetutf8}) //设置响应头res.write(fs.readFileSync(pathname),utf-8)res.end()return true}else{return false}
}
module.exports routes
!DOCTYPE html
html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
link relstylesheet href./static/css/index.cssbodydivbutton idlogin登录-get/buttonbutton idlogin2登录-post/button/div
/body
script//get请求login.onclick function () {let username zhangsanlet passsword 123456fetch(/api/findStr?username${username}password${passsword}).then(res res.json()).then(res {console.log(res)})}//post请求login2.onclick function () {fetch(/api/findStrpost, {method: post,body: JSON.stringify({username: zhangsan,passsword: 123456}),headers: {Content-Type: application/json}}).then(res res.json()).then(res {console.log(res)})}/script/html
Express
基于nodejs平台快速开放极简的web开发框架 npm i express --save 路由路径和请求方法一起定义了请求的端点可以是字符串字符串模式或者是正则表达式。
基本路由
const express require(express)const app express() //创建服务器
app.get(/,(req,res){res.send(123) //res.send可以发送任意格式的内容
})app.listen(3000,(){console.log(启动了);
}) 以下情况访问 http://localhost:3000/ac 或者 http://localhost:3000/abc 均可请求 app.get(/ab?c,(req,res){ res.send(123) //res.send可以发送任意格式的内容 }) 占位符能够匹配参数请求http://localhost:3000/abc/1 格式即可
app.get(/abc/:id,(req,res){console.log(req.params.id)res.send(456) //res.send可以发送任意格式的内容
})
可匹配 abcd abbcd abbbcd 等b可以一次或多次。
app.get(/abcd,(req,res){res.send(abcd)
})
可以在之间写任意内容
app.get(/ab*cd,(req,res){res.send(ab*cd)
}) 匹配正则表达式
app.get(/q/,(req,res){ //路径中包含 q 即可res.send(q)
}) 在接口的参数中除了对应的访问路径以外还可以写多个回调函数来操作处理然后返回给前端数据。其中next函数非常重要是进入下一个回调函数的开关一旦使用了res.send之后的 代码都不再执行。
app.get(/home,(req,res,next){//验证tokenconsole.log(验证成功)next()
},(req,res){res.send(home) //res.send可以发送任意格式的内容
}) 多个回调函数可以写成数组的形式并且能够传参数使用res传递。
const a function (req,res,next){//验证tokenconsole.log(验证)res.namezhangsannext()
}
const b function (req,res,next){console.log(res.name)res.send(home) //res.send可以发送任意格式的内容
}
app.get(/home,[a,b])
res.send() 支持发送片段以及json
res.json() 只能支持发送json
res.render() 支持发送模版
中间件
express是一个资深功能极简完全是由路由和中间件构成的一个web开发框架从本质上说一个express应用就是在调用各种中间件。
中间件是一个函数可以访问请求对象响应对象以及web应用中处于响应循环流程中的中间件一般被命名为next的变量。
中间件的功能包括
执行任何代码 修改请求和响应对象 终结请求-响应循环 调用堆栈中的下一个中间件。
如果当前中间件始终没有 终结请求-响应循环则必须调用next 方法将控制权交给下一个中间件否则就会挂起。
Express应用可以使用如下几种中间件
应用级中间件
应用级中间件绑定到app对象使用 app.use() 和 app.method() 包括app.get app.post等其中method是需要处理http请求的方法例如getputpost等。在请求接口前先验证token是否有效。
const express require(express)const app express()
const a function (req,res,next){//验证tokenconsole.log(验证)res.name 123next()
}
app.use(a)
const b function (req,res,next){console.log(res.name);res.send(home) //res.send可以发送任意格式的内容
}
app.get(/home,[b])
app.get(/ab?c,(req,res){res.send(123) //res.send可以发送任意格式的内容
})
app.get(/abc/:id,(req,res){res.send(456) //res.send可以发送任意格式的内容
})
app.get(/q/,(req,res){res.send(q) //res.send可以发送任意格式的内容
})
app.listen(3000,(){console.log(启动了);
})
路由级中间件
类似于接口模块化可以给对应的模块加上前缀使用express.Router() 来创建
下面的访问为http://localhost:3000/index/home 等。
//indexRouter.js
const express require(express)const router express.Router()router.get(/,(req,res){res.send(/)
})
router.get(/home,(req,res){res.send(home)
})router.get(/ab?c,(req,res){res.send(123)
})
router.get(/abc/:id,(req,res){res.send(456)
})
router.get(/q/,(req,res){res.send(q)
})module.exports router
const express require(express)
const IndexRouter require(./router/IndexRouter)const app express()
const a function (req,res,next){//验证tokenconsole.log(验证)res.name 123next()
}
app.use(a)
app.use(/index,IndexRouter)
//app.use(/login,LoginRouter)
//app.use(/home,HomeRouter)app.listen(3000,(){console.log(启动了);
})
错误处理中间件
在匹配不到接口时报错放在app.use() 应用中间件的最后。没有任何路径匹配万能中间件任何数据都可以返回。使用4个参数errreqresnexterr是参数状态码。
app.use((req,res){res.status(404).send(丢了)
})
内置中间件
express.static是express唯一内置的中间件。它基于serve-static负责在express应用中替托管静态资源。每个应用可有多个静态目录。
第三方中间件
安装所需功能的node模块并在应用中加载可以在应用级加载也可以在路由级加载。
路由获取前端参数 http://localhost:3000/index/home?a1b2 get请求通过req.query来获取查询字符串
router.get(/home,(req,res){console.log(req.query);res.send(home)
})
post请求通过req.body来获取但是要提前配置解析post请求的中间件旧版需要下载body-parser现在已经内置需要引入配置。
const express require(express)
const IndexRouter require(./router/IndexRouter)const app express()
const a function (req,res,next){//验证tokenconsole.log(验证)res.name 123next()
}
//配置post请求的中间件
//form 表单的形式a1b2
app.use(express.urlencoded({extended:false}))
//Json对象形式{a:1,b:2}
app.use(express.json()) app.use(a)
app.use(/index,IndexRouter)app.use((req,res){res.status(404).send(丢了)})
app.listen(3000,(){console.log(启动了);
})
router.post(/homePost,(req,res){console.log(req.body); //必须配置中间件res.send(homePost)
})router.post(/homePostJson,(req,res){console.log(req.body); //必须配置中间件res.send(homePost)
})
!DOCTYPE html
html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/headbodydivbutton idlogin登录-post/buttonbutton idlogin2登录-postJson/button/div
/body
scriptlogin.onclick function () {let username zhangsanlet passsword 123456fetch(/index/homePost, {method: post,body:username${username}password${passsword},headers: {Content-Type: applicatin/x-www-form-urlencoded}}).then(res res.text()).then(res {console.log(res)})}login2.onclick function () {fetch(/index/homePostJson, {method: POST,body: JSON.stringify({username: zhangsan,passsword: 123456}),headers: {Content-Type: application/json}}).then(res res.text()).then(res {console.log(res)})}/script/html 利用express托管静态文件
通过express内置的express.static可以方便地托管静态文件例如图片cssjs文件等。
将静态资源文件所在的目录作为参数传递给express.static中间件就可以提供静态资源文件的访问了。 app.use(express.static(public)) //可以是任意文件夹。 然后就可以访问文件了 http://localhost:3000/index/images/kitten.jpg http://localhost:3000/index/js/app.js http://localhost:3000/index/css/style.css http://localhost:3000/index/hello.html 所有文件的路径都是相对于存放目录的因此存放静态文件的目录不会出现在URL中 只需访问 http://localhost:3000/index.html
同时可以设置多个静态文件
const express require(express)
const IndexRouter require(./router/IndexRouter)const app express()
app.use(express.urlencoded({extended:false})) //配置post请求的中间件 a1b2
app.use(express.json()) //配置post请求的中间件
app.use(express.static(public)) //配置静态资源
app.use(express.static(static))const a function (req,res,next){//验证tokenconsole.log(验证)res.name 123next()
}app.use(a)
app.use(/index,IndexRouter)app.use((req,res){res.status(404).send(丢了)})
app.listen(3000,(){console.log(启动了);
})
服务端渲染模版引擎
客户端渲染前后端分离BSR前段中组装页面做好静态页面动态效果json模拟ajax动态创建页面真实接口数据前后联调。
服务端渲染后端镶嵌模版后端渲染模版SSRServer-side-render 后端把页面组装做好静态页面动态效果把前端代码提供给后端后端把静态html以及html里的假数据给删掉通过模版进行动态生成html的内容。
需要ejs 模版引擎工具在应用中设置让express渲染模版文件 views放模版文件的目录比如app.set(views,./views) view engine模版引擎比如app.set(view engine,ejs) img src%E7%AC%94%E8.assets/image-2021.png altnot-found stylezoom:50%/ % % 流程控制标签写入js代码 % % 输出标签原文输出HTML标签html片段 %- % 输出标签 HTML会被浏览器解析 %# % 注释标签 %- include(user/show,{user.user}) % 导入公模版内容 npm i ejs
//服务端
const express require(express)
const IndexRouter require(./router/IndexRouter)
const HomeRouter require(./router/HomeRouter)const app express()//配置模版引擎
app.set(views,./views) //配置文件夹views下的ejs文件为渲染路由页面
app.set(view engine,ejs)app.use(express.urlencoded({extended:false})) //配置post请求的中间件 a1b2
app.use(express.json()) //配置post请求的中间件
app.use(express.static(public)) //配置静态资源const a function (req,res,next){//验证tokenconsole.log(验证)res.name 123next()
}app.use(a)
app.use(/index,IndexRouter)
app.use(/home,HomeRouter)app.use((req,res){res.status(404).send(丢了)})
app.listen(3000,(){console.log(启动了);
})
配置路由接口
//IndexRouter.js
const express require(express)const router express.Router()router.get(/,(req,res){res.render(index,{title:hello world})//渲染模版后自动返回给前端找到views下的index.ejs
})
router.post(/validate,(req,res){console.log(req.body);res.redirect(/home) //重定向到home页面
})module.exports router
//HomeRouter.js
const express require(express)
const router express.Router()router.get(/,((req,res){res.render(home,{list:[1,2,3],content:h1我是片段/h1})
}))module.exportsrouter
在views文件下创建ejs文件
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
bodyindex 页面div标题%title%/divform action/index/validate methodpostdiv用户名input typetext nameusername/divdiv密码input typepassword namepassword/divdivinput typesubmit value登录/div/form
/body
/html
!DOCTYPE html
html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/headbodyhome 页面ul% for(let i0;ilist.length;i){ %li% list[i]%/li%}%/uldiv最新消息%- content %/div%# 注释的写法 不会在页面资源中显示,只存在开发环境中 % div%-include(./about.ejs,{showItem:true}) %/div
/body
script
/script/html
有时候我们不需要ejs文件只需要html文件那么应该怎么做 //配置模版引擎
app.set(views,./views)
app.set(view engine,html)
app.engine(html,require(ejs).renderFile) //直接支持html渲染
配置好后在views下写好对应的html文件即可。
Express脚手架
通过express-generator可以快速创建一个应用骨架 npm i -g express-generator 创建项目并下载依赖启动项目
express myapp --viewejsnpm inpm start
此时启动后不能随时重编译做一下修改package.json完成脚手架的配置。
scripts: {start: nodemon ./bin/www},
//一些模块
res.locals.message err.message; //res.locals类似于上下文在ejs中这种可以直接获取到里面的内容
app.use(logger(dev)); //记录请求的生成器控制台可以输出错误
MongoDB
关系型与非关系型数据库
关系型数据库sql语句增删改查操作保持事务的一致性事务机制回滚包括mysqlsqlserverdb2oracle。
非关系型数据库no sql:not only sql轻量高效自由包括mongodbHbaseredis。
mongoDB的优势
由于独特的数据处理方式可以将热点数据加载到内存故而对查询来讲会非常快当然也消耗内存同时由于采用了BSON的方式存储数据对JSON格式数据具有非常好的支持性以及友好的表结构修改性文档式的存储方式数据有好可见数据库的分片集群负载具有非常好的扩展性以及非常不错的自动 鼓掌转移。
sqlMongoDB说明databasedatabase数据库tablecollection表/集合rowdocument表行/文档columnfield表列字段/域indexindex索引table joins不支持表连接primary keyprimary key主键
安装数据库
Install MongoDB Community Edition — MongoDB Manual
可自行选择系统匹配的下载
这里使用mac版本先下载homebrew然后安装mongodb
//终端安装homebrew
/bin/bash -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)
//根据提示继续执行
eval $(/opt/homebrew/bin/brew shellenv)
brew install mongodb 如果报错No available formula with the name mongodb. Did you mean mongosh or monetdb?
首先tap一个仓库
brew tap mongodb/brew
安装社区版
brew install mongodb-community
安装好mongodb以后启动
mongosh
在命令行中操作数据库
操作库
查看命令提示 help db.help() db.test.help() db.test.find().help() 创建/切换数据库use testdb查询数据库show dbs查看当前使用的数据库db/db.getName()显示当前DB状态db.stats()查看当前DB版本db.version产看当前DB的连接机器地址db.getMongo()删除数据库db.dropDatabase()
操作集合
创建一个集合db.createCollection(cillName,{size:222,capped:true,max:5000});最大存储空间为5M最多5000个文档的集合。得到指定名称的聚集集合db.getCollection(account)得到当前db的所有聚集集合db.getCollectionNames()显示当前db所有聚集的状态db.printCollectionStats()删除集合db.collection.drop();
操作集合数据
添加集合数据 db.users.insert({name:zhangsan,age:25,sex:1}) db.users.insert([{name:zhangsan,age:25,sex:1},{name:lisi,age:25,sex:1}]) 在这里插入的数据数据结构可以完全不一样也能够插入。 修改 db.users.update(age:25,$set{name:wanger,false,true}) 相当于sqlupdate users set name wanger where age 25 db.users.update({name:zhangsan},{$inc:{age:50}},false,true) 相当于sqlupdate users set age age 50 where name zhangsan 减的话$inc:{age:-50}即可 db.users.update({name:zhangsan},{$inc:{age:50},$set{name:lisi}},false,true) 相当于sqlupdate users set age age 50, name zhangssan where name lisi 删除 db.users.remove({age:32}); db.users.remove({}); 删除所有 db.users.deleteOne({age:32}); db.users.deleteMany({age:32});
查询所有数据 db.userInfo.find() 相当于sqlselect * from userInfo 查询某字段去重后的数据 db.userInfo.distinct(name) 相当于sqlselect distinct name from userInfo 查询age22的数据 db.userInfo.find({age:22}) 相当于sqlselect * from userInfo where age 22 查询age22的数据 db.userInfo.find({age:{$gt:22}}) 相当于sqlselect * from userInfo where age 22 查询age22的数据 db.userInfo.find({age:{$lt:22}}) 相当于sqlselect * from userInfo where age 22 查询age22的数据 db.userInfo.find({age:{$gte:22}}) 相当于sqlselect * from userInfo where age 22 查询age22的数据 db.userInfo.find({age:{$lte:22}}) 相当于sqlselect * from userInfo where age 22 查询age22 age26的数据 db.userInfo.find({age:{$gte:26,$lte:22}}) 相当于sqlselect * from userInfo where age 22 and age 26 查询name包含 mongo的数据 db.userInfo.find({name:/mongo/})写入正则表达式 相当于sqlselect * from userInfo where name like mongo% 查询指定列 nameage数据想显示哪列就将字段设置为1不想显示的就设置为0 db.userInfo.find({},{name:1,age:1}) 相当于sqlselect name,age from userInfo 查询指定列 nameage数据 age25 db.userInfo.find({age:{$gt:25}},{name:1,age:1}); 相当于sqlselect name,age from userInfo where age 25 按照年龄排序数组就是多列查询 升序.db.userInfo.find().sort({age:1}) 降序.db.userInfo.find().sort({age:-1}) 查询 name:zhangsa,age:22的数据 db.userInfo.find({name:zhangsan,age:22}) sqlselect * from userInfo where namezhangsan and age22 查询前5条数据 db.userInfo.find().limit(5) sqlselect top 5 * from userInfo 查询10条以后的数据 db.userInfo.find().skip(10) sqlselect * from userInfo where id not in ( select top 10 * from userInfo ) 查询5-10之间的数据db.userInfo.find().skip(5).limit(5)or 与 查询 db.userInfo.find({$or:[{age:22},{age:25}]}) sqlselect * from userInfo whrer age 22 or age 25 查询第一条数据 db.userInfo.findOne() / db.userInfo.find().limit(1) sqlselect 1 * from userInfo 查询某个结果集的记录条数 db.userInfo.find({age:{$gte:25}}).count() sqlselect count(*) from userInfo where age 20
可视化工具进行增删改查 Robomogo Robo3T adminMongo 可自行下载使用 使用nodejs操作数据库
这里使用生成的express ejs的脚手架数据库连接新建数据库文件连接在启动文件下引入可以在app.js或者bin下面的www文件这里在express脚手架中www文件引入 npm i mongoose const mongoose require(mongoose)mongoose.connect(mongodb://127.0.0.1:27017/express_test)
//插入集合数据数据库会自动创建
#!/usr/bin/env node/*** Module dependencies.*/var app require(../app);
var debug require(debug)(myapp:server);
var http require(http);
require(../config/db.config)
/*** Get port from environment and store in Express.*/var port normalizePort(process.env.PORT || 3000);
app.set(port, port);/*** Create HTTP server.*/var server http.createServer(app);/*** Listen on provided port, on all network interfaces.*/server.listen(port);
server.on(error, onError);
server.on(listening, onListening);/*** Normalize a port into a number, string, or false.*/function normalizePort(val) {var port parseInt(val, 10);if (isNaN(port)) {// named pipereturn val;}if (port 0) {// port numberreturn port;}return false;
}/*** Event listener for HTTP server error event.*/function onError(error) {if (error.syscall ! listen) {throw error;}var bind typeof port string? Pipe port: Port port;// handle specific listen errors with friendly messagesswitch (error.code) {case EACCES:console.error(bind requires elevated privileges);process.exit(1);break;case EADDRINUSE:console.error(bind is already in use);process.exit(1);break;default:throw error;}
}/*** Event listener for HTTP server listening event.*/function onListening() {var addr server.address();var bind typeof addr string? pipe addr: port addr.port;debug(Listening on bind);
}创建数据库模型由于mongodb存储数据比较自由所以需要添加这样的模型来限制传入的字段以及字段类型新建限制模型
const mongoose require(mongoose)// 限制模型 类似于接口interface限制参数
const type {username:String,password:String
}
// 模型user将会对应users集合
const UserModel mongoose.model(user,new mongoose.Schema(type))module.exportsUserModel
在接口文件中引入限制模型并操作数据库。
var express require(express);
var router express.Router();
const UserModel require(../dbModel/UserModel)
/* GET home page. */
router.get(/, function (req, res, next) { //服务端渲染//创建数据库模型要限制field类型一一对应数据库集合let page 1let limit 3UserModel.find({},[username]).sort({_id:-1}).skip((page-1)*limit).limit(3).then(result { //数据查询res.render(index, {list:result});})
});
//注册
router.post(/register, function (req, res, next) {const { username, password } req.body//创建数据库模型要限制field类型一一对应数据库集合UserModel.create({ username, password }).then(result {res.send(register success);})
});
//修改
router.post(/update/:id, function (req, res, next) {const { username, password } req.bodyconst { id } req.params//创建数据库模型要限制field类型一一对应数据库集合UserModel.updateOne({ id, username, password }).then(result { res.send(update success);})
});
// 删除
router.get(/delete, function (req, res, next) {const { id } req.query//创建数据库模型要限制field类型一一对应数据库集合UserModel.deleteOne({ _id:id }).then(result { //插入数据res.send(delete success)})
});
module.exports router;页面请求index.ejs页面
!DOCTYPE html
htmlheadlink relstylesheet href/stylesheets/style.css //headbodydiv用户名input idusername typetext/divdiv密码input idpassword typetext/divbutton idreg注册/buttonbutton idupdate修改/buttonbutton iddeleteItem删除/buttonbrdivul% for (let index 0; index list.length; index) { %li%list[index].username %-/li% } %/ul/div/bodyscriptreg.onclick(){fetch(/register,{method:post,headers:{Content-Type:application/json},body:JSON.stringify({username:username.value,password:password.value})}).then(resres.text()).then(res{console.log(res);})}update.onclick(){fetch(/update/64eb4d5df24d574cfca6e518,{method:post,headers:{Content-Type:application/json},body:JSON.stringify({username:修改,password:修改})}).then(resres.text()).then(res{console.log(res);})}deleteItem.onclick(){fetch(/delete?id64eb4df708eff7b8d60cadbf).then(resres.text()).then(res{console.log(res);})}/script
/htmlfetch接口 //postfetch(/register,{method:post,headers:{Content-Type:application/json},body:JSON.stringify({username:username.value,password:password.value})}).then(resres.text()).then(res{console.log(res);})//get 也可以 /delete/id 的占位符写法fetch(/delete?id64eb4df708eff7b8d60cadbf).then(resres.text()).then(res{console.log(res);})接口规范与业务分层
接口规范
restful架构
服务器上每一种资源比如一个文件一张图片一部电影都有对应的url地址如果我们的额客户端要对服务器的资源进行操作就要通过http协议执行形影的动作来操作比如获取更新删除。
简单来说就是url地址中只包含名词表示资源使用http动词表示动作进行操作资源下面对比右边为规范的写法
Get /blog/getArticles --- Get /blog/Articles //获取Get /blog/addArticles --- Post /blog/Articles //新增Get /blog/editArticles --- Put /blog/Articles //编辑Get /rest/api/deleteArticles --- Delete /blog/Articles/1 //删除
使用方式
GET http://www.test.com/api/user //获取列表POST http://www.test.com/api/user //创建用户PUT http://www.test.com/api/user/{id} //修改用户信息DELETE http://www.test.com/api/user/{id} //删除用户信息
fetch(/api/user,{method:POST,headers:{Content-Type:application/json},body:JSON.stringify({username:username.value,password:password.value})}).then(resres.text()).then(res{console.log(res);})fetch(/api/user/id,{method:PUT,headers:{Content-Type:application/json},body:JSON.stringify({username:修改,password:修改})}).then(resres.text()).then(res{console.log(res);})fetch(/api/user/id,{method:DELETE,headers:{Content-Type:application/json},body:JSON.stringify({username:修改,password:修改})}).then(resres.text()).then(res{console.log(res);})
fetch(/api/user}).then(resres.text()).then(res{console.log(res);})
router.get(/user, function (req, res, next) { //服务端渲染//创建数据库模型要限制field类型一一对应数据库集合let page 1let limit 3UserModel.find({},[username]).sort({_id:-1}).skip((page-1)*limit).limit(3).then(result { //数据查询res.render(index, {list:result});})
});//注册
router.post(/user, function (req, res, next) {const { username, password } req.body//创建数据库模型要限制field类型一一对应数据库集合UserModel.create({ username, password }).then(result {res.send(register success);})
});
//修改
router.put(/user/:id, function (req, res, next) {const { username, password } req.bodyconst { id } req.params//创建数据库模型要限制field类型一一对应数据库集合UserModel.updateOne({ id, username, password }).then(result { res.send(update success);})
});
// 删除
router.delete(/user/:id, function (req, res, next) {const { id } req.query//创建数据库模型要限制field类型一一对应数据库集合UserModel.deleteOne({ _id:id }).then(result { //插入数据res.send(delete success)})
});
业务分层
router.js负责将请求分发给c端
controller.jsc层负责处理业务逻辑v与m之间的沟通
viewsv层 负责展示页面
modelm层 负责处理数据增删改查
创建controllers service
//indexRouter.js
const UserModel require(../dbModel/UserModel)
var express require(express);
var router express.Router();
const IndexController require(../controllers/indexController)
/* GET home page. */
router.get(/, IndexController.queryUser);
//注册
router.post(/register,IndexController.addUser);
//修改
router.post(/update/:id,IndexController.updateUser );
// 删除
router.get(/delete, IndexController.deleteUser);
module.exports router;//indexControllers.js
const UserService require(../service/UserService)
const IndexController {addUser: async (req, res, next) {const { username, password } req.body//创建数据库模型要限制field类型一一对应数据库集合await UserService.addUser(username, password)res.send(register success);},updateUser: async (req, res, next) {const { username, password } req.bodyconst { id } req.params//创建数据库模型要限制field类型一一对应数据库集合await UserService.updateUser(id, username, password)res.send(update success);},queryUser: async (req, res, next) { //服务端渲染//创建数据库模型要限制field类型一一对应数据库集合let page 1let limit 3let result await UserService.queryUser(page, limit)res.render(index, { list: result });},deleteUser:async (req, res, next){const { id } req.query//创建数据库模型要限制field类型一一对应数据库集合await UserService.deleteUser(id)res.send(delete success)}}module.exports IndexController
//UserServiceconst UserModel require(../dbModel/UserModel)
const UserService {addUser: (username, password) {return UserModel.create({ username, password }).then(result {})},updateUser:(id,username, password){return UserModel.updateOne({ id, username, password }).then(result {})},queryUser:(page,limit){return UserModel.find({}, [username]).sort({ _id: -1 }).skip((page - 1) * limit).limit(3)},deleteUser:(id){UserModel.deleteOne({ _id: id }).then(result { //插入数据})}
}module.exports UserService
Cookie与session
http是无状态的也就是说http请求方和响应方之间无法维持状态都是一次性的他不知道前后的请求都发生了什么但有的场景下我们需要维护状态。比如登录情况下才可以发送业务请求等。
使用express-session插件 npm i express-session 在app.js中配置
var session require(express-session)app.use(session({ //注册sessionname:sys, //名称secret:qwer1234, //服务器生成session的签名cookie:{maxAge:1000*60*60, //过期时间secure:false //为true只有https协议才能访问cookie},resave:true, //刷新sessionsaveUninitialized:true //true一开始会给一个无效的cookie
}))
//设置中间件session过期校验
app.use((req,res,next){if(req.url.includes(login)){ //登录页面放行next()return}if(req.session.user){next()}else{res.redirect(/login)}
}) 在登录时添加sessionindexController中配置
loginUser: async (req, res, next) {const { username, password } req.body//创建数据库模型要限制field类型一一对应数据库集合let arr await UserService.loginUser(username, password)console.log(arr);if (arr.length 0) {res.send({ code: 0 })} else {req.session.user 123 //设置sesion对象res.send({ code: 1 })}},
在index.js接口文件中引用
router.post(/loginUser, IndexController.loginUser);
清除session,session失效后可以通过前端跳转到login界面
router.get(/logout,(req,res){req.sessiion.destroy((){res.send({ok:1})})
})
刷新重置session
app.use((req,res,next){if(req.url.includes(login)){ //登录页面放行req.session.date Date.now() //刷新重置sessionnext()return}if(req.session.user){next()}else{res.redirect(/login)}
})
将session存储在数据库保证每次后端变化前端不用重新获取session使用中间件connect-mongo npm i connect-mongo app.js的配置
const MongoStore require(connect-mongo)app.use(session({ //注册sessionname:sys, //名称secret:qwer1234, //服务器生成session的签名cookie:{maxAge:1000*60*60, //过期时间secure:false //为true只有https协议才能访问cookie},resave:true, //刷新sessionsaveUninitialized:true, //true一开始会给一个无效的cookiestore:MongoStore.create({mongoUrl:mongodb://127.0.0.1:27017/sys,ttl:1000*60*10 //过期时间})
}))
缺点由于session都是存在内存或者数据库中但是如果用户量越来越多所占的空间也就会越来越大那么就是问题了。
JSON Web Token
cookie容易被csrf跨站请求伪造导致安全性问题可以存储在客户端localstorage并且实行加密使用sha256加密。前端请求会自动带上cookie但不会带上token。
缺点占带宽正常情况下要比session_id更大需要消耗更多流量挤占更多宽带。无法在服务端注销那么就很难解决劫持问题。性能问题JWT的卖点之一就是加密签名由于这个特性接收方得以验证JWT是否有效且被信任。对于有着严格性能要求的web应用并不理想尤其是对于单线程环境。 npm i jsonwebtoken 封装token
const jwt require(jsonwebtoken)
const secret test const JWT {generate(data,expires){return jwt.sign(data,secret,{expiresIn:expires})},verify(token){try {return jwt.verify(token,secret)} catch (error) {return false}}
}module.exports JWT 在登录接口处引用
loginUser: async (req, res, next) {const { username, password } req.body//创建数据库模型要限制field类型一一对应数据库集合let arr await UserService.loginUser(username, password)console.log(arr);const token JWT.generate({id:arr[0]._id,username:arr[0].username},10s)res.header(Authorization,token)if (arr.length 0) {res.send({ code: 0 })} else {req.session.user 123 //设置sesion对象res.send({ code: 1 })}},
前端使用axios可以在响应拦截器中获取到响应头中的token然后存储在localStorage中同理在发送请求时在请求头中添加localstorage中的token同时后端要时刻刷新token。
axios.post(/loginUser,{username:username.value,password:password.value}).then(res{console.log(res);if(res.code0){alert(res)}else{this.localStorage.setItem(token,res.headers.authorization)location.href /}})
//设置中间件session过期校验
app.use((req,res,next){if(req.url.includes(login)){next()return}const token req.headers[authorization]?.split( )[1]if(token){const payload JWT.verify(token)if(payload){const newToken JWT.generate({_id:payload._id,username:payload.username},10s)res.header(Authorization,newToken)next()}else{res.status(401).send({msg:token过期})}}else{next()}
}) !DOCTYPE html
html
headlink relstylesheet href/stylesheets/style.css /
/head
script srchttps://cdn.jsdelivr.net/npm/axios/dist/axios.min.js/scriptbodyh1login页面/h1div用户名input idusername typetext/divdiv密码input idpassword typetext/divbutton idlog登录/buttonbr
/body
scriptaxios.interceptors.request.use((config) {console.log(config);const token localStorage.getItem(token)config.headers.Authorization Bearer ${token}return config}, err {return Promise.reject(err)})axios.interceptors.response.use((response) {this.localStorage.setItem(token, response.headers.authorization)//响应拦截获取存储tokenreturn response}, err {return Promise.reject(err)})log.onclick () {axios.post(/loginUser, { username: username.value, password: password.value }).then(res {console.log(res);if (res.code 0) {alert(res)} else {location.href /}})}
/script/html
其它接口token失效要清除token并且跳转到登录界面
axios.interceptors.request.use((config) {const token localStorage.getItem(token)config.headers.Authorization Bearer ${token}return config}, err {return Promise.reject(err)})axios.interceptors.response.use((response) {localStorage.setItem(token, response.headers.authorization)//响应拦截获取存储tokenreturn response}, err {if (err.response.status 401) {localStorage.removeItem(token)location.href /login}return Promise.reject(err)})reg.onclick () {axios.post(/register, { username: username.value, password: password.value }).then(res {console.log(res);})}
文件上传
前端通过multipart/form-data表单传递给后端但是后端没办法处理所以使用multer中间件 npm i multer 前端上传请求 upload.ejs文件
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
bodyform action/register enctypemultipart/form-data methodpostinput typetext nameusernameinput typepassword namepasswordinput typefile nameavatarinput typesubmit value提交/form
/body
/html
添加upload路由服务端渲染页面
var express require(express);
const multer require(multer);
var router express.Router();
const upload multer({dest:public/uploads/})
/* GET users listing. */
router.get(/, function(req, res, next) {res.render(upload)
});module.exports router;添加路由模块
var uploadRouter require(./routes/upload);app.use(/upload, uploadRouter); 在注册接口中添加中间件法则会依次执行
const UserModel require(../dbModel/UserModel)
var express require(express);
var router express.Router();
const multer require(multer);
const upload multer({dest:public/uploads/}) //指定前端传来图片文件放在的路径
const IndexController require(../controllers/indexController)
/* GET home page. */
router.get(/, IndexController.queryUser);
//注册
router.post(/register,upload.single(avatar),IndexController.addUser);//将头像传入到指定的目录
//修改
router.post(/update/:id,IndexController.updateUser );
// 删除
router.get(/delete, IndexController.deleteUser);
//登录页面
router.get(/login, IndexController.login);
//用户登录
router.post(/loginUser, IndexController.loginUser);module.exports router;在controller中获取文件同时修改model限制模型穿参到service并且存入数据库
const mongoose require(mongoose)// 限制模型 类似于接口interface限制参数
const type {username:String,password:String,avatar:String //添加头像的字段
}
// 模型user将会对应users集合
const UserModel mongoose.model(user,new mongoose.Schema(type))module.exportsUserModel
addUser: async (req, res, next) {const { username, password } req.bodyconst avatar req.file? /uploads/${req.file.filename}:/images/default.png //文件信息 避免不传文件给默认值避免报错//创建数据库模型要限制field类型一一对应数据库集合await UserService.addUser(username, password,avatar)res.send(register success);},
addUser: (username, password,avatar) {return UserModel.create({ username, password,avatar }).then(result {})},
前端获取服务端渲染
在service中查询接口中查询avatar的数据返回给index.ejs页面渲染通过img标签src属性来获取存入的图片
controller
queryUser: async (req, res, next) { //服务端渲染//创建数据库模型要限制field类型一一对应数据库集合let page 1let limit 3let result await UserService.queryUser(page, limit)console.log(result);res.render(index, { list: result });},
service
queryUser:(page,limit){return UserModel.find({}, [username,avatar]).sort({ _id: -1 }).skip((page - 1) * limit).limit(3)}, controller 返回渲染
queryUser: async (req, res, next) { //服务端渲染//创建数据库模型要限制field类型一一对应数据库集合let page 1let limit 3let result await UserService.queryUser(page, limit)console.log(result);res.render(index, { list: result });},
!DOCTYPE html
htmlheadlink relstylesheet href/stylesheets/style.css /
/headbodyh1index页面/h1div用户名input idusername typetext/divdiv密码input idpassword typetext/divbutton idreg注册/buttonbutton idupdate修改/buttonbutton iddeleteItem删除/buttonbrdivul% for (let index0; index list.length; index) { %li%list[index].username %- img src% list[index].avatar% alt /li% } %/ul/div
/body/html 在前端数据上使用非form的表单使用FormData对象
!DOCTYPE html
htmlheadlink relstylesheet href/stylesheets/style.css /
/head
styleimg{width: 100px;}
/style
bodyh1index页面/h1div用户名input idusername typetext/divdiv密码input idpassword typetext/divdiv头像input typefile idavatar/divbutton idreg注册/buttonbutton idupdate修改/buttonbutton iddeleteItem删除/buttonbrdivul% for (let index0; index list.length; index) { %li%list[index].username %- img src% list[index].avatar% alt /li% } %/ul/div
/body
script srchttps://cdn.jsdelivr.net/npm/axios/dist/axios.min.js/script
scriptaxios.interceptors.request.use((config) {const token localStorage.getItem(token)config.headers.Authorization Bearer ${token}return config}, err {return Promise.reject(err)})axios.interceptors.response.use((response) {localStorage.setItem(token, response.headers.authorization)//响应拦截获取存储tokenreturn response}, err {if (err.response.status 401) {localStorage.removeItem(token)location.href /login}return Promise.reject(err)})reg.onclick () {const formdata new FormData()formdata.append(username,username.value)formdata.append(password,password.value)formdata.append(avatar,avatar.files[0])axios.post(/register, formdata,{headers:{Content-Type:multipart/form-data}}).then(res {console.log(res);})}update.onclick () {fetch(/update/64eb4d5df24d574cfca6e518, {method: post,headers: {Content-Type: application/json},body: JSON.stringify({ username: 修改, password: 修改 })}).then(res res.text()).then(res {console.log(res);})}deleteItem.onclick () {fetch(/delete?id64eb4df708eff7b8d60cadbf).then(res res.text()).then(res {console.log(res);})}
/script/html
如果想要批量上传
//前端
input typefile idavatar multiple //后端
//注册
router.post(/photoUpload,upload.array(photos,12),(req,res,next){//req.files是文件数组信息//req.body 文本域数据//pjotos是对应的参数名
});
APIDOC--api文档生成工具
apidoc是一个简单的restful api 文档生成工具有以下特点
1.跨平台linuxwindowsmacOS等都支持
2.支持语言广泛
3.支持多个不同语言的多个项目生成一份文档
4.输出模版可自定义
5.根据模版生成mock数据 npm i -g apidoc vscode可以下载插件 APIDoc Snippets
输入apiDocumentation会生成以下的代码我们可以按照自己的接口情况修改
/**** api {post} /register 添加用户* apiName addUser* apiGroup usergroup* apiVersion 1.0.0** apiParam {String} username 用户名* apiParam {String} password 密码* apiParam {File} avatar 头像** apiSuccess (200) {string} register success** apiParamExample {multipart/form-data} Request-Example:* {* username : test* password : 123123* avatar : file对象* }*** apiSuccessExample {string} * register success*/
//注册
router.post(/register,upload.single(avatar),IndexController.addUser); 在终端中输入指令生成doc文件夹
apidoc -i ./routes/ -o ./doc
doc文件夹中打开index.html页面就可以看到接口文档。 配置apidoc
根目录下创建apidoc.json文件配置以下选项即可
{name:后台系统接口文档,version:1.0.0,description:接口文档,title:定制系统
} koa2
koa编写web应用通过组合不同的generator可以免除重复繁琐的回调函数嵌套并极大地提升错误处理的效率。koa不再内核方法中绑定任何中间件仅仅提供了一个轻量优雅的函数库使编写web应用变得得心应手。防守打法第三方结算单
安装koa2 初始化npm init 安装npm i koa //index.js
const Koa require(koa)const app new Koa()app.use((ctx,next){ //ctx执行上下文中含有request以及responseconsole.log(server start);ctx.response.bodyhello world
})
app.listen(3000)
启动服务nodemon index.js
ctx.reqNode的request对象ctx.resNode的response对象ctx.requestKoa的request对象ctx.responseKoa的response对象
在访问时可以不访问request或者response对象直接访问属性即可简写
ctx.response.bodyctx.bodyctx.request.pathctx.path............
Koa vs express
通常会说Koa是洋葱模型是由于在于中间件的设计。express也是类似的但是express中间件使用了callback实现如果出现异步问题则会让你在执行顺序上感到困惑因此如果我们想要做接口耗时同级错误处理koa的这种中间件模式处理起来更方便些。最后一点响应机制也很重要koa不是立即响应使整个中间件处理完再最外层进行了响应而express是立即响应。
更加轻量
koa不提供内置的中间件不提供路由把路由这个库分离出来了。
Context对象
koa增加了一个context对象作为这次请求的上下文对象在koa中作为中间件的第一个参数传入。同时context上也挂载了request和response两个对象。和express类似这两个额对象都提供了大量的便捷方法辅助开发。
异步流程控制
express采用callback来处理异步koa1采用generatorkoa2采用async/await
generator和async/await使用同步的写法来处理异步明显好于callback和promise
中间件模型
express基于connect中间件线性模型
koa采用洋葱模型对于每个中间件在完成了一些事情后可以有丫的将控制权传递给下一个中间件并且能够等待它完成网后续的中间件完成处理后控制权又回到了自己 const Koa require(koa)const app new Koa()app.use(async (ctx,next){ //ctx执行上下文中含有request以及responseconsole.log(server start);console.log(1111);ctx.response.bodyhello world //类似于express中res.sendawait next()console.log(2222);
})app.use(async (ctx,next){ //ctx执行上下文中含有request以及responseconsole.log(3333);await test()console.log(444);
})function test(){return new Promise((resolve,reject){resolve(123)})
}app.listen(3000) 以上代码结果为 1111 3333 444 2222 路由
npm i koa-router
基本用法
const Koa require(koa)
const Router require(koa-router)const app new Koa()
const router new Router()router.get(/list,(ctx,next){ctx.body [1,2,3]
}).post(/list/:id,(ctx,next){console.log(ctx.params); //id存储在params中ctx.body add success
}).put(/list/:id,(ctx,next){ctx.body update success
}).delete(/list/:id,(ctx,next){ctx.body delete success
})app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示app.listen(3000)
分模块引入
//home.js
const Router require(koa-router)
const router new Router()router.get(/,(ctx,next){ctx.body h1HomePage/h1
})module.exports router
//user.js
const Router require(koa-router)
const router new Router()router.get(/,(ctx,next){ctx.body [1,2,3]
}).post(/:id,(ctx,next){console.log(ctx.params); //id存储在params中ctx.body add success
}).put(/:id,(ctx,next){ctx.body update success
}).delete(/:id,(ctx,next){ctx.body delete success
})module.exports router
//list.js
const Router require(koa-router)
const router new Router()router.get(/,(ctx,next){ctx.body [1,2,3]
}).post(/:id,(ctx,next){console.log(ctx.params); //id存储在params中ctx.body add success
}).put(/:id,(ctx,next){ctx.body update success
}).delete(/:id,(ctx,next){ctx.body delete success
})module.exports router//routes文件夹index.js 整合路由
const Router require(koa-router)
const router new Router()
//引入路由
const userRouter require(./user)
const listRouter require(./list)
const homeRouter require(./home)
//统一加前缀
// router.prefix(/api)
//注册路由级组件
router.use(/user,userRouter.routes(),userRouter.allowedMethods())
router.use(/list,listRouter.routes(),listRouter.allowedMethods())
router.use(/home,homeRouter.routes(),homeRouter.allowedMethods())
router.redirect(/,/home) //重定向
module.exports router
//index.js入口文件
const Koa require(koa)
const router require(./routes/index)const app new Koa()//注册应用级组件
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)
托管静态资源
npm i koa-staticconst Koa require(koa)
const router require(./routes/index)
const static require(koa-static)
const path require(path)
const app new Koa()app.use(static(path.join(__dirname,public)))
//注册应用级组件
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)
在public下创建html文件并且在http://localhost:3000/center.html直接访问
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
link relstylesheet href./css/center.css
bodydivcenter/div
/body
/html
获取请求参数
获取get请求数据
获取get请求数据源头是koa中request对象中的query或者querystring方法query返回是格式化好的参数对象querystring返回的是请求字符串ctx有对request的api有直接引入放入方式所以获取get请求数据有两个途径
1.从上下文中直接获取请求对象ctx.query返回如{ a:1,b:2}请求字符串ctx.querystring返回 a1b2。
2.从上下文中的request直接获取请求对象ctx.request.query返回如{ a:1,b:2}请求字符串ctx.request.querystring返回 a1b2。
获取post参数
对于post请求的处理koa-bodyparser中间件可以吧koa2上下文的formData数据解析到ctx.request.body中不可简写。
npm i koa-bodyparser
const Koa require(koa)
const router require(./routes/index)
const static require(koa-static)
const path require(path)
const bodyParser require(koa-bodyparser)
const app new Koa()app.use(bodyParser()) //获取前端post请求传来的参数
app.use(static(path.join(__dirname,public)))
//注册应用级组件
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)
router.post(/:id,(ctx,next){console.log(ctx.request.body); //获取post传参ctx.body add success
})
ejs模版
npm i --save ejsnpm i koa-views
const Koa require(koa)
const router require(./routes/index)
const static require(koa-static)
const path require(path)
const bodyParser require(koa-bodyparser)
const views require(koa-views)
const app new Koa()app.use(bodyParser()) //获取前端传来的参数
app.use(static(path.join(__dirname,public)))
app.use(views(path.join(__dirname,views),{extension:ejs})) //配置views为模版引擎
//注册应用级组件
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)
//home.js
const Router require(koa-router)
const router new Router()router.get(/,async (ctx,next){console.log(123);// ctx.body h1HomePage/h1await ctx.render(home)
})module.exports router
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
bodyhomePage模版页面
/body
/html
cookiesession
cookie
koa提供了从上下文直接读取写入cookie的方法
ctx.cookies.get(name,[options]) //读取上下文请求中的cookiectx.cookies.set(name,[options]) //写入上下文中的cookie
session
koa-session-minimal适用于koa2的session中间件提供存储介质的读写接口
const Koa require(koa)
const router require(./routes/index)
const static require(koa-static)
const path require(path)
const bodyParser require(koa-bodyparser)
const views require(koa-views)
const app new Koa()
const session require(koa-session-minimal)app.use(bodyParser()) //获取前端传来的参数
app.use(static(path.join(__dirname,public)))
app.use(views(path.join(__dirname,views),{extension:ejs})) //配置views为模版引擎
//注册应用级组件
app.use(session({key:test,cookie:{maxAge:1000*60*60}
}))
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)
router.get(/,async (ctx,next){ctx.session.user {username:test}await ctx.render(home)
})
// session判断拦截
app.use(async (ctx,next){if(ctx.url.includes(/login)){await next()return}if(ctx.session.user){//重置sessionctx.session.mydate Date.now()await next()}else{ctx.redirect(/login)}
})
koa-jwt npm i jsonwebtoken 可以使用之前写好的jwt模块
const jwt require(jsonwebtoken)
const secret test const JWT {generate(data,expires){return jwt.sign(data,secret,{expiresIn:expires})},verify(token){try {return jwt.verify(token,secret)} catch (error) {return false}}
}module.exports JWT
const JWT require(../util/jwt)router.post(/login,async (ctx , next) {const { username, password } req.body//创建数据库模型要限制field类型一一对应数据库集合let arr await UserService.loginUser(username, password)const token JWT.generate({id:arr[0]._id,username:arr[0].username},10s)ctx.set(Authorization,token)ctx.body {ok:1}});
前端请求拦截器以及后端校验刷新token
axios.interceptors.request.use((config) {const token localStorage.getItem(token)config.headers.Authorization Bearer ${token}return config}, err {return Promise.reject(err)})axios.interceptors.response.use((response) {localStorage.setItem(token, response.headers.authorization)//响应拦截获取存储tokenreturn response}, err {if (err.response.status 401) {localStorage.removeItem(token)location.href /login}return Promise.reject(err)})
app.use(async (ctx,next){if(ctx.url.includes(login)){await next()return}const token ctx.headers[authorization]?.split()[1]if(token){const payload JWT.verify(token)if(payload){const newToken JWT.generate({_id:payload.id,username:payload.username},10s)ctx.set(authorization,newToken) //重置tokenawait next()}else{ctx.status 401ctx.body {errCode:-1,errInfo:token过期}}}else{await next()}
})
文件上传
npm i --save koa/multer multer
前端传参必须是表单的形式
axios.post(/register, formdata,{headers:{Content-Type:multipart/form-data}}).then(res {console.log(res);})
const multer require(koa/multer);
const upload multer({dest:public/uploads/}) //目标地址//前端传输的文件参数名为avatar
router.post(/upload,upload.single(avatar),(ctx){console.log(ctx.request.body,ctx.file) //表单信息文件信息ctx.body upload success
});
操作MongoDB npm i mongoose const mongoose require(mongoose)mongoose.connect(mongodb://127.0.0.1:27017/koa_test)
//插入集合数据数据库会自动创建 可以直接使用之前express的配置文件以及限制模型
const mongoose require(mongoose)mongoose.connect(mongodb://127.0.0.1:27017/koa_test)
//插入集合数据数据库会自动创建
const mongoose require(mongoose)// 限制模型 类似于接口interface限制参数
const type {username:String,password:String,// avatar:String
}
// 模型user将会对应users集合
const UserModel mongoose.model(user,new mongoose.Schema(type))module.exportsUserModel
router.post(/,async (ctx,next){console.log(ctx.params); //id存储在params中const {username,password} ctx.request.bodyawait UserModel.create({username,password})ctx.body add success
})
MySql关系型数据库
免费数据库mysql与非关系型数据库区别
主要差异是数据存储的方式。关系型数据天然就是表格格式因此存储在数据表的行和列中。数据表可以彼此关联写作存储也很容易提取数据。
与其相反非关系型数据库不适合存储在数据表的行和列中而是大块组合在一起非关系型数据通常存储在数据集中就像文档键值对或者图结构你的数据及其特性是选择数据存储和提取方式的首要影响因素。
优点
易于维护都是使用表结构格式一致。
使用方便sql语言通用可用于复杂查询。
复杂操作支持sql可用于一个表以及多个表之间非常复杂的查询。
缺点
读写性能比较差尤其是海量数据的高效率读写
固定的表结构灵活度稍欠。
高并发读写需求传统关系型数据库来说硬盘I/O是一个很大的瓶颈。
非关系型数据库严格上不是一种数据库应该是一种数据结构化存储方法的集合可以是文档或者键值对等
优点
格式灵活存储数据的格式可以是keyvalue形式文档形式图片形式等使用灵活应用场景广泛而关系型数据库则只支持基础类型。
速度快nosql可以使用硬盘或者随机存储器作为载体而关系型数据库只能用硬盘。
高扩展性成本低nosql数据库部署简单基本都是开源。
缺点
不提供sql支持无事务处理数据结构相对复杂复杂查询方面稍欠。
sql语句
插入插入的数据的类型需要严格按照表中数据的数据类型
INSERT INTO students(id,name,score,gender) VALUES (null,test,100,1)
更新
UPDATE student SET name test,score 22 WHERE id2
删除
DELETE FROM student WHERE id2
查询
//查询所有
SELECT * FROM student WHERE 1//查询所有数据某个字段
SELECT id,name,score,gender FROM student WHERE 1//条件查询
SELECT * FROM student WHERE id1//模糊查询
SELECT * FROM student WHERE name like k%//排序
SELECT id,name,gender,score FROM student ORDER BY score
SELECT id,name,gender,score FROM student ORDER BY score DESC //降序//分页查询
SELECT id,name,gender,score FROM student LIMIT 50 OFFSET 0//记录条数
SELECT COUNT(*) FROM student
SELECT COUNT(*)totalNumber FROM student
//多表查询(多表查询成为笛卡尔查询使用时要非常小心由于结果是目标表的行数乘积各有100条返回10000条)
SELECt * FROM students,class;//要使用表名列名这样的方式来引用列和设置别名这样就避免了结果集的列名重复问题。
SELECT students.id sid,students.name,students.gender,students.score,class.id cid,class.name cname FROM students,class//联表查询
SELECT s.id,s.name sname,s.gender,s.score,c.name cname FROM students s INNER JOIN class c ON s.class_id c.id//如果students的数据匹配不到也会被查出来对应的数据会为null
SELECT s.id,s.name sname,s.gender,s.score,c.name cname FROM students s LEFT JOIN class c ON s.class_id c.id//如果class的数据匹配不到也会被查出来对应的数据会为null
SELECT s.id,s.name sname,s.gender,s.score,c.name cname FROM students s RIGHT JOIN class c ON s.class_id c.id//如果class或students的数据匹配不到也都会被查出来对应的数据会为null
SELECT s.id,s.name sname,s.gender,s.score,c.name cname FROM students s FULL JOIN class c ON s.class_id c.id
外键约束
InnoDB支持事务MyISAM不支持事务。这是mysql将默认存储引擎从MyISAM
变为InnoDB的重要原因之一。
InnoDB支持外键MyISAM不支持。对一个包含外键的InnoDB表转为MyISAM会失败。
cascade
在父表上update/delete时同步update/delete子表
的记录
set null
在父表上update/delete时将子表上匹配记录的列设为null要注意子表的外键列不能为null
no action
如果子表中有匹配的记录则不允许父表对应候选键进行update/delete操作
restrict
同no action都是立即检查外键约束。
nodejs 操作数据库
npm i mysql2
const express require(express)
const msql2 require(mysql2)
const app express()app.get(/,(req,res){const config getDBconfig()const promisePool mysql2.createPool(config).promise()let name testlet gender 1let id 1//let result await promisePool.query(select * from students)//let result await promisePool.query(select * from students where name? and gender?,[name,gender] )//let result await promisePool.query(insert into student (name,gender) values (?,?),[name,gender])//let result await promisePool.query(update students set name? where id?,[name,id])let result await promisePool.query(delete from students where id?,[id])res.send({ok:1,data:result[0]})
})app.listen(3000)function getDBconfig(){return {host:127.0.0.1,port:3306,user:root,password:,database:test,connectionLimit:1 //一个连接池}
}
Socket编程
webSocket
应用场景弹幕媒体聊天协同编辑基于位置的应用体育实况更新股票基金报价实时更新。
webSocket并不是全新的协议而是利用了http协议来建立连接必须由浏览器发起因为请求协议是一个标准的http请求格式如下
GET ws://loacalhost:3000/ws/chat HTTP/1.1
Host:localhost
Upgrade:websocket
Connection:Upgrade
origin: http://localhost:3000
Sec-webSocket-Key:client-random-string
Sec- WebSocket-Version: 13
该请求和普通的HTTP请求有几点不同
1.get请求的地址不是类似/path/而是以ws://开头的地址
2.请求头Upgrade:websocket和connection:Upgrade 表示这个连接将要被转换为WebSocket连接。
3.Sec-webSocket-Key是用于标识这个连接并非用与加密数据。
4.Sec- WebSocket-Version指定了WebSocket的协议版本。
如果服务器接收该请求就会有如下反应
HTTP/1.1 101 Switching Protocols
Upgrade:webSocket
Connection:Upgrade
Sec-WebSocket-Accept: server-random-string
该响应代码101表示本次连接的http协议即将被更高更改后就是Upgrade:websocket指定的WebSocket协议。
版本号和子协议规定了双方能理解的数据格式以及是否支持压缩等。如果仅仅使用WebSocket
的api就不用关心这些。
现在一个webSocket连接就能建立成功浏览器和服务器可以随时主动发送消息给对方。消息有两种一种是文本一种是二进制通常我们发送json格式文本在浏览器处理会很方便。
为什么WebSocket链接可以事项全双工通信而http不可以实际上http是建立在tcp协议之上的tcp协议本身就实现了全双工通信但是http协议的请求-响应机制限制了全双工通信websocket链接建立以后其实就是简单规定了一下接下来咱们不用http协议直接互相发消息。
安全的websocket连接机制和https类似。首先浏览器用wss://xxx创建websocket连接时回先通过https创建安全的连接。然后该https链接升级为websocket连接底层走的仍然是安全的SSL/TSL协议。
浏览器支持
很显然要支持websocket通信浏览器要支持这个协议才能发出ws://xxx的请求。目前支持的主流浏览器如下chromefirefoxie10Safari6Android 4.4 ios8。
服务器支持
由于websocket是一个协议服务器具体怎么实现取决于所用的编程语言和框架本身。node.js本身支持的协议包括tcp和http要支持websocket协议需要对node.js提供法的httpServer做额外的开发。已经有若干基于node.js的稳定可靠的webSocket实现我们直接用npm安装即可。
ws模块
服务器
npm init
npm i ws express
//webSocket响应
const WebSocket require(ws)
const { WebSocketServer } WebSocket
const wss new WebSocketServer({ port: 8080 })
wss.on(connection, (ws) {ws.on(message, (data) {console.log(data);wss.clients.forEach((client) { //给所有用户转发//检查所有用户是否处于连接状态不用发送给自己if (client!ws client.readyState WebSocket.OPEN) { client.send(data,{binary:false})//数据为非二进制数据否则会成为blob类型}})})ws.send(欢迎来到聊天室)
})
客户端
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
bodychatRoom
scriptlet ws new WebSocket(ws://localhost:8080)ws.onopen (){console.log(连接成功);}ws.onmessage (msgObj){console.log(msgObj);}ws.onerror (){console.log(err);}
/script
/body
/html
获取客户端请求参数做登录鉴权
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
bodychatRoom
scriptconst WebSocketType {Error:0, //错误走这里GroupList:1,GroupChat:2,SingleChat:3}let ws new WebSocket(ws://localhost:8080?token${localStorage.getItem(token)})ws.onopen (){console.log(连接成功);}ws.onmessage (msgObj){console.log(JSON.parse(msgObj.data));const {data} JSON.parse(msgObj)switch(data.type){case WebSocket.Error:localStorage.removeItem(token)break;case WebSocket.GroupChat:console.log(data)break;}}ws.onerror (){console.log(err);}
/script
/body
/html
//webSocket响应
const WebSocket require(ws)
const JWT require(./jwt.js)
const { WebSocketServer } WebSocket
const wss new WebSocketServer({ port: 8080 })
wss.on(connection, (ws,req) {const myUrl new URL(req.url,http://127.0.0.1:3000)let mytoken myUrl.searchParams.get(token)const payload JWT.verify(myToken)if(myToken){ws.send(createMessageWebSocket.GroupChat,null,欢迎来到聊天室)}else{ws.send(createMessage(WebSocketType.Error,null,token过期))}
})const WebSocketType {Error:0, //错误走这里GroupList:1,GroupChat:2,SingleChat:3}
function createMessage(type,user,data){return JSON.stringify({type,user,data})
} 注意在登录时还是使用httpx协议获取token在聊天的页面使用ws协议
给前端返回数据
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
bodychatRoom
scriptconst WebSocketType {Error:0, //错误走这里GroupList:1,GroupChat:2,SingleChat:3}let ws new WebSocket(ws://localhost:8080?token${localStorage.getItem(token)})ws.onopen (){console.log(连接成功);}ws.onmessage (msgObj){console.log(JSON.parse(msgObj.data));const {data} JSON.parse(msgObj)switch(data.type){case WebSocket.Error:localStorage.removeItem(token)location.href/loginbreak;case WebSocket.GroupList:console.log(JSON.parse(data))break;case WebSocket.GroupChat:console.log(data)break;}}ws.onerror (){console.log(err);}
/script
/body
/html
ws.send({type:1}) //获取所有用户ws.send({type:2,data:你好) //群发给所有人ws.send({type:3,data:你好,tom) //私聊
//webSocket响应
const WebSocket require(ws)
const JWT require(./jwt.js)
const { WebSocketServer } WebSocket
const wss new WebSocketServer({ port: 8080 })
wss.on(connection, (ws,req) {const myUrl new URL(req.url,http://127.0.0.1:3000)let mytoken myUrl.searchParams.get(token)const payload JWT.verify(myToken)if(myToken){ws.send(createMessageWebSocket.GroupChat,null,欢迎来到聊天室))ws.user payload //user中包含username以及password}else{ws.send(createMessage(WebSocketType.Error,null,token过期))}ws.on(message, (data) {console.log(data);const msgObj JSON.parse(data)switch(msgObj.type){//获取所有成员case WebSocketType.GroupList:let userList Array.from(wss.clients).map(elel.user)sendAll()bresk;//群发case WebSocketType.GroupChat:wss.clients.forEach((client) { //给所有用户转发//检查所有用户是否处于连接状态不用发送给自己if (client!ws client.readyState WebSocket.OPEN) { client.send(createMessage(WebSocketType.GroupChat,ws.user,msgObj.data),{binary:false})//数据为非二进制数据否则会成为blob类型}})bresk;case WebSocketType.SingleChat:wss.clients.forEach((client) {if (client.user.username msgObj.to client.readyState WebSocket.OPEN) { client.send(createMessage(WebSocketType.SingleChat,ws.user,msgObj.data),{binary:false})//数据为非二进制数据否则会成为blob类型}})bresk;}})ws.on(close,(){//断开连接时候的回调sendAll()})
})const WebSocketType {Error:0, //错误走这里GroupList:1,GroupChat:2,SingleChat:3}
function createMessage(type,user,data){return JSON.stringify({type,user,data})
}function sendAll(){wss.clients.forEach((client) { //给所有用户转发//检查所有用户是否处于连接状态不用发送给自己if (client.readyState WebSocket.OPEN) { ws.send(createMessge(WebSocket.GroupList,null,JSON.stringify(userList)))}})
}
参考文档ws - npm
socket.io模块
有express/koa集成的用法可以自己选择能够自定义的创建想要的事件参考文档socket.io - npm
npm i socket.io
前端需要socket.io.min.js参考https://github.com/socketio/socket.io/blob/main/client-dist/socket.io.min.js
在复制后放在本地public文件夹下
固定的事件connectiondisconnect其他可自定义传递数据会自动转为JSON或解析JSON。
const express require(express)
const JWT require(./util/jwt)
const app express()app.use(express.static(public))
const server require(http).createServer(app)
const io require(socket.io)(server)
io.on(connection, (socket, req) {const payload JWT.verify(socket.handshake.query.token)console.log(payload);if (payload) {console.log(222);socket.user payload//发送欢迎socket.emit(connect, { user: socket.user, data: 欢迎来到聊天室 })//获取列表socket.on(groupList, () {//用户数据存储在io.sockets.sockets以map对象存储const arr Array.from(io.sockets.sockets).map(item item[1].user)//发送所有人io.sockets可以直接发所有人io.sockets.emit(groupList, { data: arr.filter(elel) })//首次或刷新会出现报错的情况是因为用户会有个断开再连接的过程所以或出现undfined的情况过滤后就不会了//断开连接时的再次发送所有人listsocket.on(disconnect, () {socket.emit(groupList, { data: arr })})//群聊socket.on(groupChat, (msg) {//所有人都发// io.sockets.emit(groupChat,{user:socket.user, data:msg})// 除了自己不发其他人发socket.broadcast.emit(groupChat, { user: socket.user, data: msg })})socket.on(single, (msg) {Array.from(io.sockets.sockets).forEach(item {if (item[1].user.username msg.to) {item[1].emit(single, { user: socket.user, msg: msg.data })}})})})} else {socket.emit(error, { errorMessage: token过期 })}})
server.listen(3000)
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/head
bodychatRoomscript src./js/socketio.js/script
script//const socket io() //默认连接localhos:3000const socket io(ws://localhost:3000?token${localStorage.getItem(token)})socket.on(connect,(msg){console.log(msg);})socket.on(error,(msg){localStorage.removeItem(token)location.href /login})socket.on(groupList,(msg){console.log(msg);})socket.on(groupChat,(msg){console.log(msg);})socket.on(single,(msg){console.log(msg);})socket.emit(groupChat,{data:群聊})socket.emit(single,{data:私聊,to:**})
/script
/body
/html
mocha
单元测试是用来对弈歌迷快一个函数或者一个类来进行正确性检验的测试工作mocha是js的一种单元测试框架既可以咋浏览器环境下运行也可以在node.js环境下运行。使用mocha只需要专注于编写单元测试本身然偶让mocha去自动运行所有的测试并给出测试结果。
特点既可以测试简单的js函数又可以测试异步代码因为异步是js的特性之一可以自动运行所有测试也可以只运行特定的测试可以支持beforeafterbeforeEach和afterEach来编写初始化代码。
编写测试
npm i initnpm i mocha
scripts: {test: mocha},
npm test
//sum.js 测试文件
function sum(...rest){return rest.reduce((pre,next){return pre next},0)
}module.exports sum
配合mocha测试创建test文件夹将要执行的test1.js文件放入文件夹下。
const sum require(../sum)
const { describe } require(mocha);
const assert require(assert)//node内置模块断言函数//describe 一组测试可嵌套
describe(大的组1测试,(){describe(小的组1测试,(){//it 一个测试it(sum()结果返回为0,(){assert.strictEqual(sum(),0)})it(sum(1)结果返回为1,(){assert.strictEqual(sum(1),1)})it(sum(1,2)结果返回为3,(){assert.strictEqual(sum(1,2),3)})it(sum(1,2,3)结果返回为6,(){assert.strictEqual(sum(1,2,3),6)})})describe(小的组2测试,(){})
})
describe(大的组2测试,(){})chai断言库
mocha允许你使用任意的断言库比如
should.js BDD风格贯穿始终
expect.js expect()样式断言
chai expect()assert()和should风格的断言
better-assert C风格的自文档化的assert()
unexpected 可扩展到饿BDD断言工具
npm i chai
// assert风格
const chai require(chai)
const { describe } require(mocha);
const assert chai.assert
const sum require(../sum)//describe 一组测试可嵌套
describe(大的组1测试,(){describe(小的组1测试,(){//it 一个测试it(sum()结果返回为0,(){assert.equal(sum(),0)})it(sum(1)结果返回为1,(){assert.equal(sum(1),1)})it(sum(1,2)结果返回为3,(){assert.equal(sum(1,2),3)})it(sum(1,2,3)结果返回为6,(){assert.equal(sum(1,2,3),6)})})describe(小的组2测试,(){})
})
describe(大的组2测试,(){})//should风格
const chai require(chai)
const { describe } require(mocha);
const sum require(../sum)
chai.should()//describe 一组测试可嵌套
describe(大的组1测试,(){describe(小的组1测试,(){//it 一个测试it(sum()结果返回为0,(){sum().should.exist.and.equal(0)//sum.should.be.a(string)//sum.should.not.equal(6)//sum.should.have.length(5)})})describe(小的组2测试,(){})
})
describe(大的组2测试,(){})//expect风格
const chai require(chai)
const { describe } require(mocha);
const sum require(../sum)
const expect chai.expect//describe 一组测试可嵌套
describe(大的组1测试,(){describe(小的组1测试,(){//it 一个测试it(sum()结果返回为0,(){expect(sum()).to.equal(0)//expect(sum()).to.be.at.most(5)//expect(sum()).to.be.at.least(3)//expect(sum()).to.be.at.within(1,4)//expect(sum()).to.exist//expect(sum()).to.be.a(string)//expect(sum()).to.not.equal(你好)//expect(sum()).to.have.length(5)})})describe(小的组2测试,(){})
})
describe(大的组2测试,(){})异步测试
const fs require(fs)
const fsp fs.promises
const { describe } require(mocha);
const assert require(assert)//describe 一组测试可嵌套
describe(异步测试1, () {//it 一个测试it(异步读取文件, (done) {fs.readFile(./1.txt,utf8,(err,data){if(err){done(err)}else{assert.strictEqual(data,12344)done()}})})
})
describe(异步测试2, () {//it 一个测试it(异步读取文件, async () {let data await fsp.readFile(./1.txt,utf8)assert.strictEqual(data,123446)})
})
http测试
实现能够在测试时启动服务器
sudo npm i supertest
//两种
const { describe } require(mocha);
const assert require(assert)
const axios require(axios)
describe(测试接口,(){it(返回接口数据,async (){let res await axios.get(http://localhost:3000/)assert.strictEqual(res.data,你好)})
})----------------------------------------
//能够自己启动服务器并且关闭
const { describe } require(mocha);
const supertest require(supertest)
const app require(../app)
describe(测试接口,(){let server app.listen(3000)it(返回接口数据,async (){await supertest(server).get(/).expect(Content-Type,/text\/plain/).expect(200,你好)})after((){ //mocha钩子函数server.close()//结束执行后关闭服务器})
})
//对应上述两种
const koa require(koa)
const app new koa()app.use((ctx){ctx.body你好
})app.listen(3000)---------------------------------------------------const koa require(koa)
const app new koa()app.use((ctx){ctx.body你好
})// app.listen(3000)module.exports app
钩子函数
const { describe } require(mocha);
const supertest require(supertest)
const app require(../app)
describe(测试接口, () {let server;it(返回接口数据, async () {await supertest(server).get(/).expect(Content-Type, /text\/plain/).expect(200, 你好)})before(() {//在用所有例执行之前调用server app.listen(3000) })after(() { //所有用例执行后执行关闭服务器server.close()})beforeEach((){//在每个用例执行之前})afterEach((){//在每个用例执行后})
}) 目前基础就是这些有后续的再补充。