整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

全站开发流程(Vue.js+Node+Mongodb)

邹个人站点:http://www.itzoujie.com/

不懂后端的前端不是一个大前端,不懂后端的前端会大大限制你的发展空间,所以小邹在网上找了一篇不错的文章来分享给大伙,这里说一下,小邹的个人站点技术栈是(node+express+vue+mysql),跟这篇文章的技术栈略有不同,当然站点里面涉及的组件库和小程序等,小邹这里就不一一说了。好了,下面直接开始分享:

技术栈

Vue + vuex + element-ui + webpack + nodeJs + koa2 + mongodb

目录结构讲解

说明:

  • build - webpack的配置文件
  • code - 放置代码文件
  • config - 项目参数配置的文件
  • logs - 日志打印文件
  • node_modules - 项目依赖模块
  • public - 项目静态文件的入口 例如: public下的 demo.html文件, 可通过 localhost:3000/demo.html 访问
  • static - 静态资源文件
  • .babelrc - babel编译
  • postcss.config.js - css后处理器配置

build 文件讲解

说明:

  • build.js - 执行webpack编译任务, 还有打包动画 等等
  • get-less-variables.js - 解析less文件, 赋值less全局变量
  • style-loader.js - 样式loader配置
  • vue-config.js - vue配置
  • webpack.base.conf.js - webpack 基本通用配置
  • webpack.dev.conf.js - webpack 开发环境配置
  • webpack.prod.conf.js - webpack 生产环境配置

code 文件

1.admin - 后台管理界面源码

src - 代码区域:

  1. components - 组件
  2. filters - 过滤器
  3. font - 字体/字体图标
  4. images - 图片
  5. router - 路由
  6. store - vuex状态管理
  7. styles - 样式表
  8. utils - 请求封装
  9. views - 页面模块
  10. App.vue - app组件
  11. custom-components.js - 自定义组件导出
  12. main.js - 入口JS
  13. index.html - webpack 模板文件

2.client - web端界面源码

跟后台管理界面的结构基本一样。

3.server - 服务端源码

说明:

  1. controller: 所有接口逻辑代码
  2. middleware: 所有的中间件
  3. models: 数据库model
  4. router: 路由/接口
  5. app.js: 入口
  6. config.js: 配置文件
  7. index.js: babel编译
  8. mongodb.js: mongodb配置

其他文件

  • config - 项目参数配置的文件
  • logs - 日志文件
  • public - 项目静态文件的入口
  • static - 静态资源文件
  • .babelrc - babel编译
  • postcss.config.js - css后处理器配置

后台管理

开发中用的一些依赖模块

  • vue/vue-router/vuex - Vue全家桶
  • axios - 一个现在主流并且很好用的请求库 支持Promise
  • qs - 用于解决axios POST请求参数的问题
  • element-ui - 饿了么出品的vue2.0 pc UI框架
  • babel-polyfill - 用于实现浏览器不支持原生功能的代码
  • highlight.js / marked- 两者搭配实现Markdown的常用语法
  • js-md5 - 用于登陆时加密
  • nprogress - 顶部加载条

components

这个文件夹一般放入常用的组件, 比如 Loading组件等等。

views

所有模块页面。

store

vuex 用来统一管理公用属性, 和统一管理接口。

登陆

登陆是采用 jsonwebtoken方案 来实现整个流程的。

1. jwt.sign(payload,secretOrPrivateKey,[options,callback]) 生成TOKEN

2. jwt.verify(token,secretOrPublicKey,[options,callback]) 验证TOKEN

3.获取用户的账号密码。

4.通过 jwt.sign 方法来生成token:

5.每次请求数据的时候通过 jwt.verify 检测token的合法性 jwt.verify(token,secret)。

权限

通过不同的权限来动态修改路由表。

通过 vue的 钩子函数 beforeEach 来控制并展示哪些路由, 以及判断是否需要登陆。

通过调用 getUserInfo方法传入 token 获取用户信息, 后台直接解析 token 获取里面的信息返回给前台。

通过调用 setRoutes方法 动态生成路由。

axios 请求封装,统一对请求进行管理

面包屑 / 标签路径

  • 通过检测路由来把当前路径转换成面包屑。
  • 把访问过的路径储存在本地,记录下来,通过标签直接访问。

上面介绍了几个主要以及必备的后台管理功能,其余的功能模块 按照需求增加就好

前台

前台展示的页面跟后台管理界面差不多, 也是用vue+webpack搭建,基本的结构都差不多。

server端

权限

主要是通过 jsonwebtoken 的verify方法检测 cookie 里面的 token 验证它的合法性。

日志是采用 log4js 来进行管理的, log4js 算 nodeJs 常用的日志处理模块,用起来额也比较简单。

log4js 的日志分为九个等级,各个级别的名字和权重如下:

1.图。

2.设置 Logger 实例的类型 logger=log4js.getLogger('cheese')。

3.通过 Appender 来控制文件的 名字路径类型

4.配置到 log4js.configure。

5.便可通过 logger 上的打印方法 来输出日志了 logger.info(JSON.stringify(currTime:当前时间为${Date.now()}s ))。

定制书写规范(API)

设计思路

当应用程序启动时候,读取指定目录下的 js 文件,以文件名作为属性名,挂载在实例 app 上,然后把文件中的接口函数,扩展到文件对象上。

读取出来的便是以下形式:

app.controller.admin.other.markdown_upload_img

便能读取到 markdown_upload_img 方法。

在把该形式的方法赋值过去就行:

router.post('/markdown_upload_img',app.controller.admin.other.markdown_upload_img)


通过 mongoose 链接 mongodb

封装返回的send函数

通过 koa-static 管理静态文件入口

注意事项:

1. cnpm run server 启动服务器 //没装cnpm的使用npm命令

2.启动时,记得启动mongodb数据库,账号密码 可以在 server/config.js 文件下进行配置

3. db.createUser({user:"cd",pwd:"123456",roles:[{role:"readWrite",db:'test'}]})(mongodb 注册用户)

4. cnpm run dev:admin 启动后台管理界面

5.登录后台管理界面录制数据

6.登录后台管理时需要在数据库 创建 users 集合注册一个账号进行登录

7. cnpm run dev:client 启动前台页面

者:五月君 来源:Nodejs技术栈

“前端如何解决跨域问题?” 这个是前段在知乎看到的一个提问,这几乎是做前端都会遇到的一个问题,产生的情况可能会很多,解决一个问题还是要先了解下为什么会产生这样问题,学习最好的方法就是结合一些实际的案例来学习,理解和掌握也会更加的深刻,本文结合 Node.js 写一些 Demo 看一下跨域问题及解决办法,最好是自己看完也能够动手操作下~

Cross-origin Resource Sharing 中文名称 “跨域资源共享” 简称 “CORS”,它突破了一个请求在浏览器发出只能在同源的情况下向服务器获取数据的限制。

本文会先从一个示例开始,分析是浏览器还是服务器的限制,之后讲解什么时候会产生预检请求,在整个过程中,也会讲解一下解决该问题的实现方法,文末会再总结如何使用 Node.js 中的 cors 模块和 Nginx 反向代理来解决跨域问题。

文中使用 Node.js 做一些 Demo 的演示,每一小节之后也会给予代码的 Demo 地址。

浏览器还是服务器的限制

先思考下,CORS 是浏览器端还是服务器端的限制?为了更好地说明这个问题,从一段示例开始。

从一段示例开始

index.html

<body> 
  <!-- <script src="https://cdn.bootcdn.net/ajax/libs/fetch/3.0.0/fetch.min.js"></script> --> 
  <script> 
    fetch('http://127.0.0.1:3011/api/data'); 
  </script> 
</body> 

client.js

创建 client.js 用来加载上面 index.html。设置端口为 3010。

const http = require('http'); 
const fs = require('fs'); 
const PORT = 3010; 
http.createServer((req, res) => { 
  fs.createReadStream('index.html').pipe(res); 
}).listen(PORT); 

server.js

创建 server.js 开启一个服务,根据不同的请求返回不同的响应。设置端口为 3011。

const http = require('http'); 
const PORT = 3011; 
 
http.createServer((req, res) => { 
  const url = req.url; 
  console.log('request url: ', url); 
  if (url === '/api/data') { 
    return res.end('ok!'); 
  } 
  if (url === '/script') { 
    return res.end('console.log("hello world!");'); 
  } 
}).listen(PORT); 
 
console.log('Server listening on port ', PORT); 

测试分析原因

运行上面的 client.js、server.js 浏览器输入 http://127.0.0.1:3010 在 Chrome 浏览器中打开 Network 项查看请求信息,如下所示:

左侧是使用 fetch 请求的 127.0.0.1:3011/api/data 接口,在请求头里可以看到有 Origin 字段,显示了我们当前的请求信息。另外还有三个 Sec-Fetch-* 开头的字段,这是一个新的草案 Fetch Metadata Request Headers[1]。

其中 Sec-Fetch-Mode 表示请求的模式,通过左右两侧结果对比也可以看出左侧是跨域的。Sec-Fetch-Site 表示的是这个请求是同源还是跨域,由于我们这两个请求都是由 3010 端口发出去请求 3011 端口,是不符合同源策略的。

看下浏览器 Console 下的日志信息,根据提示得知原因是从 “http://127.0.0.1:3010” 访问 “http://127.0.0.1:3011/api/data” 被 CORS 策略阻止了,没有 “Access-Control-Allow-Origin” 标头。

在看下服务端的日志,因为请求 3011 服务,所以就看下 3011 服务的日志信息:

Server listening on port  3011 
request url:  /script 
request url:  /api/data 

在服务端是有收到请求信息的,说明服务端是正常工作的。

我们也可以在终端通过 curl 命令测试下,在终端脱离浏览器环境也是可以正常请求的。

$ curl http://127.0.0.1:3011/api/data 
ok! 

本节代码示例:

github.com/qufei1993/http-protocol/tree/master/example/cors/01 

总结回答最开始提出的问题

浏览器限制了从脚本内发起的跨源 HTTP 请求,例如 XMLHttpRequest 和我们本示例中使用的 Fetch API 都是遵循的同源策略。

当一个请求在浏览器端发送出去后,服务端是会收到的并且也会处理和响应,只不过浏览器在解析这个请求的响应之后,发现不属于浏览器的同源策略(地址里面的协议、域名和端口号均相同)也没有包含正确的 CORS 响应头,返回结果被浏览器给拦截了。

预检请求

预检请求是在发送实际的请求之前,客户端会先发送一个 OPTIONS 方法的请求向服务器确认,如果通过之后,浏览器才会发起真正的请求,这样可以避免跨域请求对服务器的用户数据造成影响。

看到这里你可能有疑问为什么上面的示例没有预检请求?因为 CORS 将请求分为了两类:简单请求和非简单请求。我们上面的情况属于简单请求,所以也就没有了预检请求。

让我们继续在看下简单请求和非简单请求是如何定义的。

预检请求定义

根据 MDN 的文档定义,请求方法为:GET、POST、HEAD,请求头 Content-Type 为:text/plain、multipart/form-data、application/x-www-form-urlencoded 的就属于 “简单请求” 不会触发 CORS 预检请求。

例如,如果请求头的 Content-Type 为 application/json 就会触发 CORS 预检请求,这里也会称为 “非简单请求”。

“MDN 文档 developer.mozilla.org/en-US/docs/Web/HTTP/CORS 简单请求”[2] 有更多关于简单请求的字段定义。

预检请求示例

通过一个示例学习下预检请求。

设置客户端

为 index.html 里的 fetch 方法增加一些设置,设置请求的方法为 PUT,请求头增加一个自定义字段 Test-Cors。

<script> 
  fetch('http://127.0.0.1:3011/api/data', { 
    method: 'PUT', 
    headers: { 
      'Content-Type': 'text/plain', 
      'Test-Cors': 'abc' 
    } 
  }); 
</script> 

上述代码在浏览器执行时会发现是一个非简单请求,就会先执行一个预检请求,Request Headers 会有如下信息:

OPTIONS /api/data HTTP/1.1 
Host: 127.0.0.1:3011 
Access-Control-Request-Method: PUT 
Access-Control-Request-Headers: content-type,test-cors 
Origin: http://127.0.0.1:3010 
Sec-Fetch-Mode: cors 

可以看到有一个 OPTIONS 是预检请求使用的方法,该方法是在 HTTP/1.1 协议中所定义的,还有一个重要的字段 Origin 表示请求来自哪个源,服务端则可以根据这个字段判断是否是合法的请求源,例如 Websocket 中因为没有了同源策略限制,服务端可以根据这个字段来判断。

Access-Control-Request-Method 告诉服务器,实际请求将使用 PUT 方法。

Access-Control-Request-Headers 告诉服务器,实际请求将使用两个头部字段 content-type,test-cors。这里如果 content-type 指定的为简单请求中的几个值,Access-Control-Request-Headers 在告诉服务器时,实际请求将只有 test-cors 这一个头部字段。

设置服务端

上面讲解了客户端的设置,同样的要使请求能够正常响应,还需服务端的支持。

修改我们的 server.js 重点是设置 Response Headers 代码如下所示:

res.writeHead(200, { 
  'Access-Control-Allow-Origin': 'http://127.0.0.1:3010', 
  'Access-Control-Allow-Headers': 'Test-CORS, Content-Type', 
  'Access-Control-Allow-Methods': 'PUT,DELETE', 
  'Access-Control-Max-Age': 86400 
}); 

为什么是以上配置?首先预检请求时,浏览器给了服务器几个重要的信息 Origin、Method 为 PUT、Headers 为 content-type,test-cors 服务端在收到之后,也要做些设置,给予回应。

Access-Control-Allow-Origin 表示 “http://127.0.0.1:3010” 这个请求源是可以访问的,该字段也可以设置为 “*” 表示允许任意跨源请求。

Access-Control-Allow-Methods 表示服务器允许客户端使用 PUT、DELETE 方法发起请求,可以一次设置多个,表示服务器所支持的所有跨域方法,而不单是当前请求那个方法,这样好处是为了避免多次预检请求。

Access-Control-Allow-Headers 表示服务器允许请求中携带 Test-CORS、Content-Type 字段,也可以设置多个。

Access-Control-Max-Age 表示该响应的有效期,单位为秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。还有一点需要注意,该值要小于浏览器自身维护的最大有效时间,否则是无效的。

看下增加了预检请求的效果,第一次先发出了 OPTIONS 请求,并且在请求头设置了本次请求的方法和 Headers 信息,服务端在 Response 也做了回应,在 OPTIONS 成功之后,浏览器紧跟着才发起了我们本次需要的真实请求,如图右侧所示 Resquest Method 为 PUT。

本节代码示例:

github.com/qufei1993/http-protocol/tree/master/example/cors/02 

CORS 与认证

对于跨域的 XMLHttpRequest 或 Fetch 请求,浏览器是不会发送身份凭证信息的。例如我们要在跨域请求中发送 Cookie 信息,就要做些设置:

为了能看到效果,我先自定义了一个 cookie 信息 id=NodejsRoadmap。

重点是设置认证字段,本文中 fetch 示例设置 credentials: "include" 如果是 XMLHttpRequest 则设置 withCredentials:"include"

<body> 
  <script> 
    document.cookie = `id=NodejsRoadmap`; 
    fetch('http://127.0.0.1:3011/api/data', { 
      method: 'PUT', 
      headers: { 
        'Content-Type': 'application/json', 
        'Test-Cors': 'abc', 
      }, 
      credentials: "include" 
    }); 
  </script> 
</body> 

经过以上设置,浏览器发送实际请求时会向服务器发送 Cookies,同时服务器也需要在响应中设置 Access-Control-Allow-Credentials 响应头

res.writeHead(200, { 
  'Access-Control-Allow-Origin': 'http://127.0.0.1:3010', 
  'Access-Control-Allow-Credentials': true 
}); 

如果服务端不设置浏览器就不会正常响应,会报一个跨域错误,如下所示:

Access to fetch at 'http://127.0.0.1:3011/api/data' from origin 'http://127.0.0.1:3010' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.

还有一点需要注意,如果我们在请求中设置了 credentials: "include" 服务端就不能设置 Access-Control-Allow-Origin: "*" 只能设置为一个明确的地址。

本节代码示例:

github.com/qufei1993/http-protocol/tree/master/example/cors/03 

解决跨域问题的几种方法

通过上面的分析了解跨域产生的原因之后,解决其实并不难,上面的讲解中其实也提供了解决方案,例如在 Node.js 中我们可以设置响应头部字段 Access-Control-Allow-Origin、Access-Control-Expose-Headers、Access-Control-Allow-Methods 等,但是在实际开发中这样设置难免繁琐,下面介绍几种常用的解决方法。

使用 CORS 模块

在 Node.js 中推荐你使用 cors 模块 github.com/expressjs/cors[3]。

在我们本节的示例中,一直使用的 Node.js 原生模块来编写我们的示例,在引入 cors 模块后,可以按照如下方式改写:

const http = require('http'); 
const PORT = 3011; 
const corsMiddleware = require('cors')({ 
  origin: 'http://127.0.0.1:3010', 
  methods: 'PUT,DELETE', 
  allowedHeaders: 'Test-CORS, Content-Type', 
  maxAge: 1728000, 
  credentials: true, 
}); 
 
http.createServer((req, res) => { 
  const { url, method } = req; 
  console.log('request url:', url, ', request method:', method); 
  const nextFn = () => { 
    if (method === 'PUT' && url === '/api/data') { 
      return res.end('ok!'); 
    } 
    return res.end(); 
  } 
  corsMiddleware(req, res, nextFn); 
}).listen(PORT); 

cors 在预检请求之后或在预检请求里并选项中设置了 preflightContinue 属性之后才会执行 nextFn 这个函数,如果预检失败就不会执行 nextFn 函数。

如果你用的 Express.js 框架,使用起来也很简单,如下所示:

const express = require('express') 
const cors = require('cors') 
const app = express() 
 
app.use(cors()); 

JSONP

浏览器是允许像 link、img、script 标签在路径上加载一些内容进行请求,是允许跨域的,那么 jsonp 的实现原理就是在 script 标签里面加载了一个链接,去访问服务器的某个请求,返回内容。

<body> 
  <script> 
    // fetch('http://127.0.0.1:3011/api/data', { 
    //   method: 'PUT', 
    //   headers: { 
    //     'Content-Type': 'application/json', 
    //     'Test-Cors': 'abc', 
    //   }, 
    //   credentials: "include" 
    // }); 
    <srcipt src="http://127.0.0.1:3011/api/data"></srcipt> 
  </script> 
</body> 

相比上面 CORS 模块,JSONP 只支持 GET 请求,显然是没有 CORS 模块强大的。

Nginx 代理服务器配置跨域

使用 Nginx 代理服务器之后,请求不会直接到达我们的 Node.js 服务器端,请求会先经过 Nginx 在设置一些跨域等信息之后再由 Nginx 转发到我们的 Node.js 服务端,所以这个时候我们的 Nginx 服务器去监听的 3011 端口,我们把 Node.js 服务的端口修改为 30011,简单配置如下所示:

server { 
  listen          3011; 
  server_name     localhost; 
 
  location / { 
    if ($request_method = 'OPTIONS') { 
      add_header 'Access-Control-Allow-Origin' 'http://127.0.0.1:3010'; 
      add_header 'Access-Control-Allow-Methods' 'PUT,DELETE'; 
      add_header 'Access-Control-Allow-Headers' 'Test-CORS, Content-Type'; 
      add_header 'Access-Control-Max-Age' 1728000; 
      add_header 'Access-Control-Allow-Credentials' 'true'; 
      add_header 'Content-Length' 0; 
      return 204; 
    } 
 
    add_header 'Access-Control-Allow-Origin' 'http://127.0.0.1:3010'; 
    add_header 'Access-Control-Allow-Credentials' 'true'; 
 
    proxy_pass http://127.0.0.1:30011; 
    proxy_set_header Host $host; 
  } 
} 

本节代码示例:

github.com/qufei1993/http-protocol/tree/master/example/cors/04 

总结

如果你是一个前端开发者,在工作难免会遇到跨域问题,虽然它属于浏览器的同源策略限制,但是要想解决这问题还需浏览器端与服务端的共同支持,希望读到这篇文章的读者能够理解跨域产生的原因,最后给予的几个解决方案,也希望能解决你对于跨域这个问题的困惑。

作者简介:五月君,软件设计师,公众号「Nodejs技术栈」作者。

参考资料

[1]Fetch Metadata Request Headers: https://w3c.github.io/webappsec-fetch-metadata/

[2]“MDN 文档 developer.mozilla.org/en-US/docs/Web/HTTP/CORS 简单请求”: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

[3]github.com/expressjs/cors: https://github.com/expressjs/cors

、前端EventLoop

1、什么是eventLoop?

同步任务和异步任务在js中是如何执行的呢?js的代码运行会形成一个主线程和一个任务队列。主线程会从上到下一步步执行我们的js代码,形成一个执行栈。同步任务就会被放到这个执行栈中依次执行。而异步任务被放入到任务队列中执行,执行完就会在任务队列中打一个标记,形成一个对应的事件。当执行栈中的任务全部运行完毕,js会去提取并执行任务队列中的事件。这个过程是循环进行的,这就是我们今天想要了解的event loop

2、为什么js是单线程

想要了解event loop我们就要从js的工作原理说起。首先,大家都知道js是单线程的。所谓单线程就是进程中只有一个线程在运行。那么,js为什么是单线程而不是做成多线程的呢?个人理解,js是用来实现浏览器与用户之间的交互的。如果同时要处理用户点击,用户输入,用户关闭等操作,浏览器无法知道这个时间我到底应该做什么。所以js是从上至下按顺序运行下去的

3、什么是宏任务&&什么是微任务

宏任务: 需要多次事件循环才能执行完,事件队列中的每一个事件都是一个宏任务。浏览器为了能够使得js内部宏任务与DOM任务有序的执行,会在一个宏任务执行结束后,在下一个宏执行开始前,对页面进行重新渲染 (task->渲染->task->…)鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTML
微任务: 微任务是一次性执行完的。微任务通常来说是需要在当前task执行结束后立即执行的任务,例如对一些动作做出反馈或者异步执行任务又不需要分配一个新的task,这样便可以提高一些性能

4、案例

console.log("script start");
•
setTimeout(function(){
   console.log("setTimeout");
},0)
•
newPromise(resolve=>{
   console.log("promise start");
   resolve();
}).then(function(){
   console.log("promise1");
}).then(()=>{
   console.log("promise2");
})
•
console.log("script end");
console.log(1);
•
setTimeout(()=>{
 console.log(2);
})
•
newPromise((resolve)=>{
 console.log(4)
 resolve()
}).then(()=>{
setTimeout(()=>{
     console.log(5);
  })
}).then(()=>{
 console.log(6)
})
•
console.log(7)
setTimeout(() => {
 console.log(5)
• new Promise(resolve => {
    console.log(6)
•    setTimeout(() => {
      console.log(7)
    })
    resolve()
  }).then(() => {
    console.log(8)
  })
}, 500)
new Promise(resolve => {
 console.log(9)
 resolve()
}).then(() => {
 console.log(10)
• setTimeout(() => {
    console.log(11)
  }, 0)
})
•console.log(12)


二、后端EventLoop

1、NodeJS中的宏任务分类

Timers 类型的宏任务队列

  • setTimeout()
  • setInterval

Check 类型的宏任务队列

  • setImmediate()

Close callback 类型的宏任务队列

  • socket.on(‘close’, () => {})

Poll 类型的宏任务队列

  • 除了上面几种的其他所有回调


2、nodeJs 里面的微任务队列

process.nextTick()Promise.then() process.nextTick()的优先级高于所有的微任务,每一次清空微任务列表的时候,都是先执行process.nextTick()

3、setTimeout && setImmediate执行顺序

Node 并不能保证 timers 在预设时间到了就会立即执行,因为 Node 对 timers 的过期检查不一定靠谱,它会受机器上其它运行程序影响,或者那个时间点主线程不空闲
虽然 setTimeout 延时为 0,但是一般情况 Node 把 0 会设置为 1ms,所以,当 Node 准备 event loop 的时间大于 1ms 时,进入 timers 阶段时,setTimeout 已经到期,则会先执行 setTimeout;反之,若进入 timers 阶段用时小于 1ms,setTimeout 尚未到期,则会错过 timers 阶段,先进入 check 阶段,而先执行 setImmediate

三、postman的基本使用

1、打开postman之后,首先输入URL,方法选择GET



2、然后我们在Headers里面输入我们所需要的内容,如果需要Cookie也填写在这里面即可(不需要可以不填),填写的时候可以单个填写,也可以点击【Bulk Edit】进行填写方式切换,切换后可以一次填写所有内容



3、填写了Headers之后,如果这个get请求里面有传参,我们接下来可以填写参数,如果不需要也可以不填。填了参数之后,内容就会拼接在我们的url里面



4、最后,我们点击send,就可以看到返回值了



post同理

四、项目的基本搭建

一、express生成器搭建项目结构

1、安装 express-generator

npm install express-generator -g

2、通过express -e生成项目结构


3、安装依赖

$ cd myapp
$ npm install

4、启动项目

npm start

二、MVC架构思想

MVC即Model-View-Controller(模型-视图-控制器)是一种软件设计模式,最早出现在Smalltalk语言中,后被Sun公司推荐为Java EE平台的设计模式。
  MVC把应用程序分成了上面3个核心模块,这3个模块又可被称为业务层-视图层-控制层。顾名思义,它们三者在应用程序中的主要作用如下:
业务层:负责实现应用程序的业务逻辑,封装有各种对数据的处理方法。它不关心它会如何被视图层显示或被控制器调用,它只接受数据并处理,然后返回一个结果。
视图层:负责应用程序对用户的显示,它从用户那里获取输入数据并通过控制层传给业务层处理,然后再通过控制层获取业务层返回的结果并显示给用户。
控制层:负责控制应用程序的流程,它接收从视图层传过来的数据,然后选择业务层中的某个业务来处理,接收业务层返回的结果并选择视图层中的某个视图来显示结果。
  可以用下图来表示MVC模式中三者之间的关系:



三、图片上传

  1. 安装multer模块
npm install multer
  1. 引用模块 它是依赖于express的一个模块
//引用express并配置
var express = require("express");
var app = express();
app.listen(3000);
var multer = require('multer');
/*var upload = multer({
    //如果用这种方法上传,要手动添加文明名后缀
        //如果用下面配置的代码,则可以省略这一句
    dest: 'uploads/'
})*/
  1. 配置 设置保存文件的地方,并根据上传的文件名对应文件添加后缀 可以通过filename属性定制文件保存的格式

属性值用途destination设置资源的保存路径。注意,如果没有这个配置项,默认会保存在/tmp/uploads下。此外,路径需要自己创建filename设置资源保存在本地的文件名

var storage = multer.diskStorage({
    //设置上传后文件路径,uploads文件夹会自动创建。
    destination: function(req, file, cb) {
        cb(null, './uploads')
    },
    //给上传文件重命名,获取添加后缀名
    filename: function(req, file, cb) {
        var fileFormat = (file.originalname).split(".");
        //给图片加上时间戳格式防止重名名
        //比如把 abc.jpg图片切割为数组[abc,jpg],然后用数组长度-1来获取后缀名
        cb(null, file.fieldname + '-' + Date.now() + "." + fileFormat[fileFormat.length - 1]);
    }
});
var upload = multer({
    storage: storage
});
  1. 接受文件 upload.single('xxx'),xxx与表单中的name属性的值对应 这里虽然用到post请求,但实际上不需要bodyParser模块处理
app.post('/upload-single', upload.single('logo'), function(req, res, next) {
    console.log(req.file)
    console.log('文件类型:%s', req.file.mimetype);
    console.log('原始文件名:%s', req.file.originalname);
    console.log((req.file.originalname).split("."))
    console.log('文件大小:%s', req.file.size);
    console.log('文件保存路径:%s', req.file.path);
    res.send({
        ret_code: '0'
    });
});
  1. 多图上传 多图上传只要更改一下地方,前端往file输入框加多一个multiple="multiple"属性值,此时就可以在选图的时候多选了,当然也可以并列多个file输入框(不推荐多个上传图片输入框),这样体验会不好
<input type="file" name="logo" multiple="multiple" />

后端也需要相应的改变

app.post('/upload-single', upload.single('logo'), function(req, res, next) {
//upload.single('logo')变为upload.array('logo', 2),数字代表可以接受多少张图片
app.post('/upload-single', upload.array('logo', 2), function(req, res, next) {

如果不想有图片数量上传限制,我们可以用upload.any()方法

app.post('/upload-single', upload.any(), function(req, res, next) { 
    res.append("Access-Control-Allow-Origin","*");
    res.send({
        wscats_code: '0'
    });
});
  1. 前端部分
  • formData表单提交
<form action="http://localhost:3000/upload-single" method="post" enctype="multipart/form-data">
    <h2>单图上传</h2>
    <input type="file" name="logo">
    <input type="submit" value="提交">
</form>
  • formData表单+ajax提交
<form id="uploadForm">
    <p>指定文件名: <input type="text" name="filename" value="" /></p>
    <p>上传文件: <input type="file" name="logo" /></ p>
    <input type="button" value="上传" onclick="doUpload()" />
</form>

FormData对象,是可以使用一系列的键值对来模拟一个完整的表单,然后使用XMLHttpRequest发送这个"表单"

注意点

  • processData设置为false。因为data值是FormData对象,不需要对数据做处理。
  • <form>标签添加enctype="multipart/form-data"属性。
  • cache设置为false,上传文件不需要缓存。
  • contentType设置为false。因为是由<form>表单构造的FormData对象,且已经声明了属性enctype="multipart/form-data",所以这里设置为false

上传后,服务器端代码需要使用从查询参数名为logo获取文件输入流对象,因为<input>中声明的是name="logo"

function doUpload() {
    $.ajax({
        url: 'http://localhost:3000/upload-single',
        type: 'POST',
        cache: false, //不必须
        data: new FormData($('#uploadForm')[0]),
        processData: false,//必须
        contentType: false,//必须
        success: function(data) {
            console.log(data)
        }
    })
}


四、编写接口

api接口

  • RestfulApi 规范
  • 接口文档的生成(apidoc)
  • 接口请求方式区别

跨域解决

  • cors
  • jsonp
  • proxy


五、JWT

JWT

  • 用户登录 服务器端产生一个token (加密字符串) 发送给前端
  • 前端将token 进行保存
  • 前端发起数据请求的时候携带token
  • 服务端 验证token 是否合法 如果合法继续操作 不合法终止操作
  • token 的使用场景 无状态请求 保持用户的登录状态 第三方登录(token+auth2.0)


非对称加密 通过私钥产生token 通过公钥解密token

// 1.产生公钥和私钥
// 产生私钥  openssl genrsa -out ./private_key.pem 1024    1024 代表私钥长度
// 产生公钥  openssl rsa -in ./private_key.pem -pubout -out ./public_key.pem

 let private_key=fs.readFileSync(path.join(__dirname,'./private_key.pem'))
 let public_key=fs.readFileSync(path.join(__dirname,'./public_key.pem'))
 var token = jwt.sign(palyload, private_key,{ algorithm: 'RS256'});
 console.log(token)
 let  token='eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IueUqOaIt2lkIiwiaWF0IjoxNTUxMTUyNzk1fQ.TI_xDBvObHGAH7EV40WWpQemm5nx077Gdjq-pzDx0NWN5YFd40S7XcLmgoDdYscLM7vMOP0c7z1l83JUixqk7IBjBCU-tMNo_G5_-LGkQjV3vDYq_3TkXTl42lgmFA-EBey7W6W1PgPfYlowyHAyp-07hXaMRevgVkXm2lPEFXo'
​
  var decoded = jwt.verify(token, public_key);


const jwt=require('jsonwebtoken')
const scrict='sdjfksdjflajflasjflasjflksf'
​
function creatToken(palyload){
    // 产生token
    palyload.ctime=Date.now()
    return jwt.sign(palyload,scrict)
}
function checkToken(token){
    return  new Promise((resovle,reject)=>{
        jwt.verify(token,scrict,(err,data)=>{
           if(err){ reject('token 验证失败')}
           resovle(data)
           })
    })
    
}
module.exports={
    creatToken,checkToken
}


Cookie+Session

const  cookieParse=require('cookie-parser')
const  session = require('express-session')
​
app.use(session({
    secret: 'hubwizApp', //为了安全性的考虑设置secret属性
    cookie: {maxAge: 60 * 1000 * 60 * 24 }, //设置过期时间
    resave: true, // 即使 session 没有被修改,也保存 session 值,默认为 true
    saveUninitialized: false, //无论有没有session cookie,每次请求都设置个session cookie ,默认给个标示为 connect.sid
}));
​

登录成功

req.session.sign = true;
req.session.name = us;

需要验证的接口判断是否存在

注销session

app.get('/out', function(req, res){
    req.session.destroy();
    res.redirect('/');
})

六、登录验证码