一入冬懒癌发做,给本身找点事干。以前博客程序写过几回,php 的写过两次,nodejs 用 ThinkJS 写过,随着 ThinkJS 版本从1.x 升级到 2.x 以前的博客程序也作过升级。可是由于前面考虑搜索引擎抓取仍是用传统的方式开发,没有作先后端分离。此次准备用 vue2.x 和 ThinkJS 3.X 从新写一次。这里主要记录一下开发过程当中遇到的问题和解决方法。javascript
地址 https://github.com/lscho/Thin...php
还没有写完,持续更新中,后续更新发布在我的博客中:https://lscho.com/tech/vue-th...html
1.先后端分离
2.后端只提供接口
3.RESTful API
4.使用 jwt 身份认证前端
"dependencies": { "think-logger3": "^1.0.0", "think-model": "^1.0.0", "think-model-mysql": "^1.0.0", "think-session": "^1.0.0", "think-session-jwt": "^1.0.8", "thinkjs": "^3.0.0" }
"dependencies": { "axios": "^0.17.0", "iview": "^2.5.1", "mavon-editor": "^2.4.13", "vue": "^2.5.2", "vue-axios": "^2.0.2", "vue-router": "^3.0.1", "vuex": "^3.0.0", "vuex-router-sync": "^5.0.0" }
jwt 的原理很清楚,以前本身也实现过相似的功能,搜索了一下,找到了 node-jsonwebtoken 这个包,使用起来很简单,主要就是加密和解密两个功能。一番折腾以后成功运行,可是去 ThinkJS 仓库看了一下,居然有发现了 think-session-jwt 这个插件,也是基于 node-jsonwebtoken 的。这个就更好用了,配置完以后直接用 ThinkJS 的 ctx.session 方法就能够生成和验证。配置的时候须要注意一下 tokenType 这个参数,他决定了如何获取 token ,我这里用的是 header ,也就是说后面会从每一个请求的 header 中找 token,key 值为配置的 tokenName。java
而后要处理前端部分,为每一个请求附加上 token。这里我用的是 axios ,在请求拦截器中很方便的就能够加上。node
let loadinginstace; axios.interceptors.request.use(config => { if (localStorage.getItem('token')) { config.headers.Authorization = localStorage.getItem('token') } return config; },error => { return error; })
而后登陆以后的每一个请求中就能够看到mysql
由于 API 接口遵循 RESTful 风格,因此对除了 GET 类型的请求,都要验证 token 是否有效,ThinkJS 的控制器提供了前置操做 __before
。在这里能够作一下逻辑判断,经过的才会继续执行。ios
async __before(action) { try { this.userInfo=await this.ctx.session('userInfo'); } catch(err) { this.userInfo={}; } if(this.resource!='token'&&this.ctx.method!='GET'&&think.isEmpty(this.userInfo)){ this.ctx.status=401; return this.ctx.fail(-1,"请登陆后操做"); } }
这里遇到一个问题,就是当 token 为错误时,node-jsonwebtoken 会抛出一个异常,因此这里用了 try catch
捕获,可能有更好的解决办法,暂时放后面处理。git
为了安全起见,咱们的 token 通常设置的都有效期,因此有两种状况须要咱们进行处理.
beforeEnter:(to, from, next)=>{ if(!localStorage.getItem('token')){ next({ path: '/login' }); }else{ next(); } }
2.token 超过有效期或者被篡改。这种须要后端检测以后才能知道该 token 是否有效。这里服务端检测失效以后会返回 401 状态码以便前端识别。
if(this.resource!='token'&&this.ctx.method!='GET'&&think.isEmpty(this.userInfo)){ this.ctx.status=401; return this.ctx.fail(-1,"请登陆后操做"); }
咱们在axios的请求响应拦截器中进行判断便可,由于 4XX 的状态码会抛出异常,因此代码以下
axios.interceptors.response.use(data => { //这里能够对成功的请求进行各类处理 return data; },error=>{ if (error.response) { switch (error.response.status) { case 401: store.commit("clearToken"); router.replace("/login"); break; } } return Promise.reject(error.response.data) })
markdown 编辑器用了 mavonEditor 配置很方便,很少说,主要说一下文件上传遇到的一个问题。
前端代码
<mavon-editor ref=md @imgAdd="imgAdd" class="editor" v-model="formItem.content"></mavon-editor>
imgAdd(pos, $file){ var formdata = new FormData(); formdata.append('image', $file); image.upload(formdata).then(res=>{ if(res.errno==0&&res.data.url){ this.$refs.md.$img2Url(pos, res.data.url); } }); }
后端处理
const file = this.file('image'); const extname=path.extname(file.name);//path.extname获取文件后缀名,可作控制 const filename = path.basename(file.path); const basename=think.md5(filename)+extname; const savepath = '/upload/'+basename; const filepath = path.join(think.ROOT_PATH, "www"+savepath); think.mkdir(path.dirname(filepath)); try{ //跨盘符移动会抛出异常 await rename(file.path, filepath); }catch(e){ const readStream = fs.createReadStream(file.path); const writeStream = fs.createWriteStream(filepath); readStream.pipe(writeStream); }
这里也用了一个 try catch
来捕获异常,主要是由于 ThinkJS 会将上传的文件先放到临时目录中,而在 windows 下临时目录可能和项目目录不在同一盘符下,进行移动的话就会抛出一个异常:Error: EXDEV, cross-device link not permitted
,没有权限移动,这时候就只能先读文件,再写文件
2017-12-27 更新 在群里@阿特 大佬提到,能够对 payload
这个中间件设置指定临时目录为项目下的某个目录,这样就不存在跨盘{ handle: 'payload', options: { uploadDir: path.join(think.ROOT_PATH, 'runtime/data') } }这样就能够直接使用 rename 来操做了,关于跨盘 rename 的问题,在 https://github.com/nodejs/nod... 找到了缘由,大意是操做系统限制 rename 仅仅是重命名路径引用地址,并无将数据移动过去,重命名不能跨文件系统操做,因此若是跨文件系统操做须要先复制、而后删除旧数据
由于前端路由使用 history 模式,因此要将请求转发至 index.html 入口页面处理,跟有些 mvc 框架单入口是一个概念。
# 请求转发至入口 location / { try_files $uri $uri/ /index.html; }
而后还要处理一下后端请求部分,若是不是同一域名,就要解决跨域问题。这里先后端使用同一个域名,针对 api 请求作一下反向代理便可。
set $node_port 8360; location ~ ^/api/ { proxy_http_version 1.1; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://127.0.0.1:$node_port$request_uri; proxy_redirect off; }
后端使用 pm2 守护进程便可。