当前位置: 首页 > news >正文

建设银行e路护航官方网站登陆网游推广

建设银行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((){//在每个用例执行后}) }) 目前基础就是这些有后续的再补充。
http://www.huolong8.cn/news/130274/

相关文章:

  • 国外免费网站做推广wordpress导购主题免费
  • 友点企业网站管理系统 模板东莞南城招聘网
  • 做网站应该考虑哪些问题出国游做的好的网站
  • 保定市建设施工许可证查询网站泰安网络平台
  • 精品课网站建设 辽宁wordpress媒体库上图
  • 哈尔滨市做淘宝的网站成品网源码7w8w
  • 网站开发学的啥网站网站制作网站的
  • 石家庄专业网站制wordpress顶部高度
  • 拐角型网站华为云建站官网
  • 网站建设佰首选金手指十05网全部答案数学
  • 单页电影网站源码wordpress检验上传的文档
  • 办个网站多少钱房屋平面图在线制作网站
  • wordpress设置中改网站美食网站建设策划报告
  • 怎样建手机网站广州企业网站制作公司
  • 几度设计网站上海哪家公司做网站比较好
  • 网站建设中存在的问题东莞有哪些好的网站建设公司
  • 做网站需要什么费用怎么和网站主联系方式
  • 二级域名网站可以做关键词优化吗高端建造
  • 境外电商网站建设北仑静态网站建设
  • 传播公司可以做门户网站吗百度快照优化推广
  • 专业免费网站建设一般外汇网站建设制作
  • 做网站 怎么连到数据库wordpress app页面模板
  • 企业做可信网站认证的好处图床外链生成工具
  • 高港区企业网站建设wordpress图书页面
  • 云服务器网站解析凡科小程序建站官网
  • 韩国做暖暖网站html5做的网站代码
  • 郑州平台网站建设企业网站html5
  • 成都便宜网站建设公司哪家好wordpress柳城
  • 网站建设需要哪些材料如何避免网站被攻击
  • wordpress 仿站 教程网深圳网站建设公