使用 vue + thinkjs 开发博客程序记录
一入冬懒癌发作,给自己找点事干。之前博客程序写过几次,php 的写过两次,nodejs 用 ThinkJS 写过,随着 ThinkJS 版本从1.x 升级到 2.x 之前的博客程序也做过升级。但是因为前面考虑搜索引擎抓取还是用传统的方式开发,没有做前后端分离。这次准备用 vue2.x 和 ThinkJS 3.X 重新写一次。这里主要记录一下开发过程中遇到的问题和解决方法。
地址 https://github.com/lscho/Thin...
尚未写完,持续更新中,后续更新发布在个人博客中:https://lscho.com/tech/vue-th...
设计方案
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 身份认证
jwt 的原理很清楚,之前自己也实现过类似的功能,搜索了一下,找到了 node-jsonwebtoken 这个包,使用起来很简单,主要就是加密和解密两个功能。一番折腾之后成功运行,但是去 ThinkJS 仓库看了一下,竟然有发现了 think-session-jwt 这个插件,也是基于 node-jsonwebtoken 的。这个就更好用了,配置完之后直接用 ThinkJS 的 ctx.session 方法就可以生成和验证。配置的时候需要注意一下 tokenType 这个参数,他决定了如何获取 token ,我这里用的是 header ,也就是说后面会从每个请求的 header 中找 token,key 值为配置的 tokenName。
然后要处理前端部分,为每个请求附加上 token。这里我用的是 axios ,在请求拦截器中很方便的就可以加上。
let loadinginstace;
axios.interceptors.request.use(config => {
if (localStorage.getItem('token')) {
config.headers.Authorization = localStorage.getItem('token')
}
return config;
},error => {
return error;
})
然后登录之后的每个请求中就可以看到

后端权限认证
因为 API 接口遵循 RESTful 风格,所以对除了 GET 类型的请求,都要验证 token 是否有效,ThinkJS 的控制器提供了前置操作 __before。在这里可以做一下逻辑判断,通过的才会继续执行。
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 捕获,可能有更好的解决办法,暂时放后面处理。
前端身份失效检测
为了安全起见,我们的 token 一般设置的都有效期,所以有两种情况需要我们进行处理.
- 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 编辑器及文件上传
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 守护进程即可。
原文地址:https://segmentfault.com/a/1190000012610084
使用 vue + thinkjs 开发博客程序记录的更多相关文章
- 使用django开发博客过程记录3——博客侧栏实现
说起这个侧栏真是苦恼我很长时间,一开始以为和之前的一样传递额外参数就可以了就像下面这样: class IndexView(ListView): template_name = 'apps/index. ...
- 使用django开发博客过程记录4——Category分类视图
在写点击博客的所属分类,显示所有该分类的文章时真是让我想了好一会,为什么呢?因为我使用的是cbv模式开发的而不是简单的视图处理逻辑的,所以,有些操作会被包装好了,你并不知道它的细节,那么我们今天要实现 ...
- 使用django开发博客过程记录5——日期归档和视图重写
针对每条博客的观看次数我么是使用django的Mixin实现的: def get(self, request, *args, **kwargs): last_visit = request.sessi ...
- 使用django开发博客过程记录2——博客首页及博客详情的实现
1.什么是CBV(Class-based views) 2.博客首页及博客详情实现 1.什么是CBV 什么是CBV?说白了就是以前是视图为处理请求返回响应的函数,有了cbv之后我们就可以用类处理请求和 ...
- 使用django开发博客过程记录1——数据库设计
1.数据库设计 2.插入测试数据 3.配置相关问题 1.数据库设计 数据库有简单的三张表:Article.Category.Tag以下是代码 # -*- coding:utf-8 -*- from _ ...
- 记录android开发博客
1.一国外android开发博客,值得关注 https://blog.stylingandroid.com/page/2/ 2.一个app设计博客,很赞 http://androidniceties. ...
- Vue实战狗尾草博客后台管理系统第三章
Vue实现狗尾草博客后台管理系统第三章 本章节,咱们开发管理系统侧边栏及面包屑功能. 先上一张效果图 样式呢,作者前端初审,关于设计上毫无美感可言,大家可根据自己情况设计更好看的哦~ 侧边栏 这里我们 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
随机推荐
- luoguP4238 【模板】多项式求逆 NTT
Code: #include <bits/stdc++.h> #define N 1000010 #define mod 998244353 #define setIO(s) freope ...
- 路飞学城Python-Day19
[23.绑定方法与非绑定方法介绍] 再类的内部的定义的函数分为两大类: 1.绑定方法: 绑定到对象的方法:直接用def做的函数属性,类内部定义的函数,如果没有绑定装饰器,就是给对象使用的函数,绑定给谁 ...
- vue实现分页器(仿element)
1.起因 今日看完element中分页器的源码实现,比较简单,遂自己按着理解实现了一个简单的分页器,记录下来,以便日后温习. 2.实现难点 分页器的实现难点主要是什么时候显示分页器的省略, 我的思路是 ...
- SVN提交代码时报405 Method Not Allowed
原因: 1.删除了某个文件夹,然后又创建了一个同名文件夹 2.之前执行过Add操作,但没上传代码,在电脑上提交了同路径的代码,再次上传时会报错 解决方法: 1. 删除出现错误的文件夹 2. SVN U ...
- poj 2663 Tri Tiling (状压dp+多米诺骨牌问题+滚动数组反思)
本来直接一波状压dpAC的 #include<cstdio> #include<cstring> #include<algorithm> #define REP(i ...
- 再识Quartz
在之前的项目中使用过Quartz,但都是基于XML配置定义任务的.目前一个项目应用需要对任务进行创建.暂停.删除等动态管理.所以再次在网上翻了翻,再来好好重新认识下Quartz. 名词解释: sche ...
- JavaScript和CSS实用工具、库与资源
JavaScript和CSS实用工具.库与资源 JavaScript 库 Particles.js - 一个用于在网页上创建漂亮的浮动粒子的 JS 库: Three.js - 用于在网页上创建 3 ...
- css不定高度实现垂直居中
1.不知道自己高度和父容器高度的情况下, 利用绝对定位只需要以下三行: parentElement{ position:relative; } childElement{ position: abso ...
- http响应的封装
响应的封装: 资源的初始化 分析请求响应信息,根据状态响应码,发送不同的状态码 浏览器根据状态信息,做出不同的执行 构建正文,也就是根据浏览器客服端的请求发送响应信息: 发送响应:code 是状态码, ...
- 110个经常使用Oracle函数总结
1. ASCII 返回与指定的字符相应的十进制数; SQL> select ascii(A) A,ascii(a) a,ascii(0) zero,ascii( ) space from dua ...