0%

token登录是前端登录最常用的方法,jwt提供了多种后台语言生成token的方法,本次主要实践的是node中express框架的生成和校验方法。

首先在express项目中安装jsonwebtoken和express-jwt,其中jsonwebtoken主要是用于生成token,express-jwt作为封装好的中间件用于解析和验证token

1
2
npm install jsonwebtoken --save
npm install express-jwt --save

在server目录下新增token\constant.js和token\index.js

Token验证的配置文件放在index.js

1
2
3
4
5
6
7
8
const expressJwt = require('express-jwt');
const { secretKey } = require('./constant');

const jwtAuth = expressJwt({secret: secretKey}).unless({path:['/users/login']})

//unless 为排除那些接口,不验证Token,这里排除 '/users/login'

module.exports = jwtAuth;

公共配置放在contant.js

1
2
3
4
5
6
7
8
9
10
const crypto = require('crypto');

module.exports = {
MD5_SUFFIX: '805696667',
md5: (pwd) => {
let md5 = crypto.createHash('md5');
return md5.update(pwd).digest('hex');
},
secretKey: 'secret12345' //Token加密公共部分
};

在实际的使用的业务代码中,博主是在登录的时候用到的加密,写在user.js中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var express = require("express");
var router = express.Router();
var jwt = require("jsonwebtoken");
var User = require("./../models/users");
const { secretKey } = require("./../token/constant"); //提取Token加密内容
// // 全局验证Token是否合法
const tokens = require("./../token/index"); //验证Token配置文件
// //注册token配置文件
router.use(tokens);
let tokenKey = secretKey; //加密内容

router.post("/login", function(req, res, next) {
var params = {
userName: req.body.userName
// password: req.body.password
};
User.findOne(params, (err, doc) => {
// console.log(err, doc, params);
if (err) {
res.json({
status: "0",
msg: err.message
});
} else {
if (doc) {
//存在该账号
if (doc.password == req.body.password) {
//验证密码是否正确
let tokenObj = {//需要加密的数据
userName: doc.userName,
password: doc.password
};
let token = jwt.sign(tokenObj, tokenKey, {
expiresIn: 60 * 60 * 24 // token时长
});
res.json({
status: "1",
msg: "success",
data: {
token: token,
userName: doc.userName
}
});
} else {
res.json({
status: "0",
msg: "密码错误"
});
}
}
}
});
});

到此后端就结束了,保存重启服务就用postman直接调用接口就已经成功了,但是在前端的axios的请求拦截的请求头中加上Authorization之后,再去请求接口一直报401未登录,尝试了很多办法,最后发现用jwt生成的token在请求头中传给后台的时候需要加上一段字符串:’Bearer ‘,注意Bearer后边一定要跟一个英文的空格,目前博主还没有深究这个问题的原因,只是实现了这个功能,待以后研究原理后再行解释。

1
2
3
4
5
6
config => {
if (localStorage.token) { //判断token是否存在
config.headers.Authorization = 'Bearer ' + localStorage.token; //将token设置成请求头
}
return config;
},

参考文档:https://juejin.im/post/5dad720ff265da5bbb1e5571

https://juejin.im/post/5d146767f265da1bac402976

解决方法:

1、“我的电脑”,右键点击属性。

2、高级系统->环境变量,编辑系统变量PATH。

alt

3、在末尾增加“;C:\Program Files\MongoDB\Server\4.2\bin”。(注意 mongodb安装下的目录)

“;”不能缺省,且为英文状态下的分号。如果是window10系统,新建时候自动换行,可以不加“;”

4、重新打开命令窗口,输入命令“mongo”,即可看到相关信息。

首先在官网上下载MongoDB,下载地址:https://www.mongodb.com/download-center/community,选择适合你电脑的版本安装即可,目前安装的稳定版本是4.2,按照上边所提示的安装步骤一步步走下去就安装完成了。

alt

打开你的安装目录,我的安装目录是 C:\Program Files\MongoDB\Server\4.2\,然后进入bin文件夹下,因为4.2版本的配置文件已经定义了数据和日志保存的位置,所以无需再次配置,当然你想更改配置的话也是可以的。

插播一下修改配置文件的方法:打开bin文件夹下的mongod.cfg文件,storage下的dbpath是数据库保存的路径,修改成你想要保存的路径即可,systemLog下的path是日志保存的路径,同样的修改方式。

在命令行输入mongod –dbpath yourDbpathAddress(如果你修改了配置的db数据存储的路径,这里就写你修改后的路径,默认是C:\Program Files\MongoDB\Server\4.2\data)

至此,你已经启动了mongoDB,打开浏览器输入 http://127.0.0.1:27017/ 即可看到

It looks like you are trying to access MongoDB over HTTP on the native driver port.

这下就已经成功了。

配置为windows本地服务

每次先进入到安装目录下,然后再输入一大堆的启动过于繁琐,把mongodDB配置为windows的本地服务,直接启动服务的方式简单便捷。

1、快捷键 win + r 输入cmd然后回车,切换到安装目录的bin下,执行以下命令:

1
mongod --dbpath D:\MongoDB\data --logpath D:\MongoDB\log\mongo.log --logappend --serviceName mongoDB --install

其中数据库路径为D:\MongoDB\data\db,日志路径为D:\MongoDB\log\mongo.log,服务名为mongoDB。

安装成功后现在命令行没有什么提示,这时候需要你继续 win + r 输入cmd回车后,在命令行输入

1
services.msc

打开你本地的服务,找到mongoDB,启动服务

alt

1
net start mongoDB //启动服务

第一次配置完之后出现了:服务没有响应控制 的报错,这时候就需要先删除原来安装的MongoDB,输入命令 sc delete mongoDB,然后再从第一步开始执行即可。

接上篇文章中说到因为跨域的原因,需要把从后台获取过来的图片链接转换为base64格式,这里就用到了canvas的toDataURL来转换,不多说,上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
imageToBase64(url,callback){
var mycanvas = document.createElement("canvas")
var ctx = mycanvas.getContext('2d')
//此处最好是获取页面已经有的元素,以方便获取宽高,否则可能会出现获取到的宽高为0,canvas画图失败的问题
var img = document.querySelector('.share-img')
var data = null
//这里需要设置crossOrigin的值,允许跨域,不然会报错,一定要在设置完之后再给src赋值
img.setAttribute("crossOrigin",'anonymous')
img.src = url;
var w = parseInt(window.getComputedStyle(img).width);
var h = parseInt(window.getComputedStyle(img).height);
mycanvas.width = w
mycanvas.height = h
img.onload = function(){
//一定要在img.onload加载完图片之后再去执行drawImage,否则会因为获取不到图片元素而报错,或者图片没有画到canvas画布上
// 将图片画到canvas上面上去!
ctx.drawImage(img,0,0,w,h);
data = mycanvas.toDataURL('image/jpeg')
//这里一定要回调一下,在回调里获取到base64的值,然后赋值给原来的图片的src
callback.call(this, data);
}
},

this.imageToBase64(this.img,function(url){
//本来是在这里直接把返回的base64的值赋值给了this.img以期望页面上的图片地址替换为base64格式,奈何怎么赋值都没有实现,使用了vue的深度监听,也没有监听到img值的变化,至今不知道是什么原因没有监听到,所以就用了最原始的方法直接获取DOM元素,然后给元素的src赋值,这样竟然就实现了
document.querySelector('.share-img').setAttribute("src",url)
})

踩坑点都已经标注在注释里,在此就不一一解释了,仅作记录。

最近更新博客没有那么频繁了,一是公司的项目时间紧,而且在这个项目中遇到了个小问题一直没有解决,昨天刚把问题解决了,特来记录下过程。

产品需求是这样的,安卓和ios点击分享按钮,有一项是保存图片,本来是说要让app自主开发的,结果他们时间紧任务重,自然就想到了由H5来开发,这样安卓和ios就可以共用一套。分享的商品详情图片上有基本的商品信息、商品图片以及一张h5的二维码。先说下技术选型吧,二维码就交给qrcode.js了,生成图片的功能用的是html2canvas,这个选定了就一点点的踩坑了。

因为项目是vue脚手架搭建的,首先要安装依赖qrcode和heml2canvas

1
2
npm install qrcode --save
npm install html2canvas --save

接下来在页面中引用qrcode和html2canvas

1
2
import html2canvas from 'html2canvas';
import QRCode from 'qrcodejs2'

页面html的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div id="share" ref="share">
<!--页面显示的内容-->
<div class="share-area">
<p class="share-top">今日待售!</p>
<p>{{detail.address}}</p>
<p>{{detail.category_type_name}}{{detail.weight}}</p>
<img :src="img" class="share-img"/>
<div id="qrcode"></div>
<p class="share-img-tip">扫描二维码查看详情并报价</p>
<p @click="createCanvas" class="save-img" v-if="isCanvas">点击生成图片</p>
</div>
<!--生成截图后展示的部分-->
<div class="canvas-mask" v-show="showImg">
<div class="canvas"><img class="img" :src="qrContentImage" crossOrigin="anonymous" alt="" /></div>
<p class="tip">长按图片保存至相册</p>
</div>
</div>
</template>

js部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
data(){
return{
qrContentImage:'',
showImg:false,
isCanvas:true,
img:'',
detail:{},
}
},
methods: {
getData(id){
//这里调用从后台获取商品数据的接口,返回的数据赋值给this.detail
this.img = this.detail.images
},
qrcode (text) { //生成二维码
let qrcode = new QRCode('qrcode', {
width: 138,
height: 138, // 高度
text: text // 二维码内容
})
},
createCanvas() {
this.isCanvas = false
this.$nextTick(() => {
var canvas2 = document.createElemen('canvas');
//此处是一个坑,最好在页面上定义一个元素,然后在此获取这个dom节点,再去获取到宽高,网上搜到很多用直接创建的canvas元素来获取宽高,结果宽高都为0,导致画图不成功的问题
let tar = document.querySelecto('.canvas');
var w = parseIn(window.getComputedStyle(tar).width);
var h = parseIn(window.getComputedStyle(tar).height;
var context = canvas2.getContex('2d');
canvas2.width = w * 2;
canvas2.height = h * 2;
canvas2.style.width = w + 'rem';
canvas2.style.height = h + 'rem';
context.scale(2, 2);//图片或者文字不清晰可以调整缩放倍数,本项目用到的调整为二倍,保存的图片和文字已经很清晰了
html2canvas(document.querySelecto('#share'), {
useCORS: true,//此处为允许图片跨域,但是也需要你们项目的图片服务器上允许跨域
}).then(canvas => {
this.qrContentImage =canvas.toDataURL('image/jpg');
this.showImg = true
});
});
}
},

这里只介绍了html2canvas将页面生成图片的方法,但是因为是用canvas来实现的画图,所以会涉及到图片跨域的问题,html2canvas官网提供了一些中间件的方法,我没有尝试,我的解决办法是将跨域的图片在执行画图前先转换为base64格式的图片流,这样就不会出现跨域问题了。关于将图片转换为base64格式下一篇博客会记录实现方式。

网站统计对接友盟,官网上已经有相关demo,具体的步骤可点击这里查看。现在要说的是,大部分的前端开发已经摒弃了jquery的方式,在用vue或者react等单页的方式开发,而友盟监测不到单页面路由的变化,这时候就需要做一些改造。

如果你是需要全网站监测,那么可在app.vue中直接引用,如果是监测部分页面,只需在所需要的页面引入以下代码即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
watch: {
'$route' () {
if (window._czc) {
//监听路由变化
let location = window.location;
let contentUrl = location.pathname +location.hash;
let refererUrl = '/';
window._czc.push(['_trackPageview',contentUrl, refererUrl])
console.log(location,contentUrlrefererUrl)
}
},
},
mounted(){
//友盟
const script = document.createElemen('script')
script.src = "https://s4.cnzz.com/z_stat.phpid=yourId&web_id=yourId"//友盟链接
script.language = 'JavaScript'
document.body.appendChild(script)
}

谁知之后在Chrome浏览器上测试,友盟一直没有统计到数据,在页面上打印window._czc,打印出来是undefined,网上搜了资料才发现是Chrome的一些插件会把友盟的js给屏蔽掉,然后换了其他浏览器,打开控制台的network已经看到友盟的js在运行,再次打开幽梦也已经有了统计数据了。

这个已经足以监测到最简单的pv、uv了,如果需要事件监测、下载监测等就需要自己对接友盟的api了,等我用到了再来分享吧。

后台返回的数据是一个数据,然后我把这个数组赋值给了data里边的一个变量,上传图片成功之后改变这个变量中的图片地址,结果在图片上传成功的函数中,打印出来的数据是已经变了的,但是vue的视图就是没变,可把我急坏了。线上代码看最初的写法:

1
2
3
4
5
6
getDataList(){//从后台获取原始数据
this.adList = info//把获取到的info数据赋值给data里已经存在的变量adList
},
cardUpload1(val){
this.adList[this.uploadTag].image_url = val.info.url//图片上传成功之后,替换原有的图片地址,结果在这里打印出来的adlist是已经变化的,但是html的视图就是不发生变化
},

然后查了一大堆资料才发现,vue对于数组变化的监听,有两种情况是监听不到的:

① 利用索引直接设置一个项时,vm.items[indexOfItem] = newValue

② 修改数组的长度时,例如: vm.items.length = newLength

而我最初的写法刚好就是属于第一种的改变数组的方式,这个时候想要改变数组,同时让视图更新,就需要用到数组的splice方法,该方法可以更改数组原来的值。所以修改后的写法为:

1
2
3
4
5
cardUpload1(val){
var item = this.adList[this.uploadTag]
item.image_url = val.info.url
this.adList.splice(this.uploadTag, item)
},

element时间范围选择的快捷键:最近一周、最近三个月、最近半年官网的demo上已经有了,最近我们需要去快速点击显示:本月、上月、上上月的日期,记录一下方便后期查询:

html部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<el-date-picker
size="small"
v-model="timeList"
format="yyyy-MM-dd"
value-format="timestamp"
type="daterange"
range-separator="至"
@change="changeTime"
align="right"
unlink-panels
time-arrow-control
:picker-options="pickerOptions"
:clearable="false"
start-placeholder="开始日期"
end-placeholder="结束日期">
</el-date-picker>

主要逻辑是写在pickerOptions里的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
data(){
return{
pickerOptions: {
shortcuts: [{
text: '本月',
onClick(picker) {
const end =new Date();
end.setTime(end.getTime() -3600 *1000 *24 + 86400000);
const start = new Date(new Date().getFullYear(),new Date().getMonth(),1);
picker.$emit('pick', [start,end]);
}
},
{
text: '上月',
onClick(picker) {
var now =new Date();//当前日期
var nowYear =now.getYear();//当前年
nowYear += (nowYear <2000) ?1900 :0;
var lastMonthDate =new Date();//上月日期
lastMonthDate.setDate(1);
lastMonthDate.setMonth(lastMonthDate.getMonth() -1);
var lastMonth =lastMonthDate.getMonth();
//获得某月天数
var monthStartDate =new Date(nowYear,lastMonth,1);
var monthEndDate =new Date(nowYear,lastMonth +1,1);
var days = (monthEndDate -monthStartDate) / (1000 *60 *60 *24);
const start =new Date(nowYear,lastMonth,1);
const end =new Date(nowYear,lastMonth,days);
picker.$emit('pick', [start,end]);
}
}],
disabledDate(time) {
let curDate = (new Date()).getTime();
let start = (new Date(new Date().getFullYear(),new Date().getMonth(),1)).getTime();
return time.getTime() > Date.now() || time.getTime() < start;
},
},
}
}

同时的可选范围变成了仅可选择当月日期,其他的日期不可选也做了限制。

最近打算自己手撸个后台管理项目练练手,封装好了axios请求,一直没有后台的支持,不知道哪里有问题也没敢放上来,今天简单的启了个express的接口,调通后特来记录下axios的封装过程,其中参考了axios中文文档和掘金上的这篇文章

安装axios
1
npm install axios --save

项目目录是这样的,在src目录下创建一个专门用来放网络请求的文件夹request,然后在文件夹下创建一个axios.js文件和api文件夹,axios的基本配置写在axios.js里,业务逻辑的接口代码放在api文件夹中,方便统一管理。
alt

在axios.js中引入

1
import axios from 'axios'

axios.js中的基本配置

弹出的提示,因为我全局用了element-ui所以弹窗直接用的是里边的组件,大家可以选择自己所用的UI框架的弹窗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//创建实例
var instance = axios.create({
timeout: 1000 * 10 //设置请求超时
})

//设置post请求时添加请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'

//跳转登录,携带当前页面参数,登陆成功后跳转当前页
const toLogin = () => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
}

//请求拦截
instance.interceptors.request.use(
//发送请求前所做
config => {
return config
},
//请求失败
error => {
return Promise.reject(error)
}
)

//响应拦截
instance.interceptors.response.use(
response => {
//返回200请求成功,否则抛出异常
if(response.status === 200) {
return Promise.resolve(response)
}else{
return Promise.reject(response)
}
},
//错误处理
error => {
if(error.response.status){
switch(error.response.status){
//401未登录,跳转登录页并记录下当前页面路径,登陆成功后跳转回该页
case 401:
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
break;
//403登录失效,清除登录信息并重新登录
case 403:
//清除登录信息
//重新登录
setTimeout(() => {
toLogin
}, 1000);
break;
//404找不到页面
case 404:
this.$message({
message:"未找到页面",
type: "info"
})
break;
default:
this.$message({
message: error.response.data.msg,
type: "error"
})
}
return Promise.reject(error.response)
}
return Promise.reject(error)
}
)

export default instance
base.js

用来管理baseUrl,在多人合作的时候也能很方便。

1
2
3
4
5
6
const baseUrl = {
base:'http://localhost:3000',
baseTwo:'http://localhost:4000',
}

export default baseUrl

index.js

所有的需要导出的接口都写在api文件夹的index.js文件中统一管理

1
2
3
4
5
import loginModel from './login.js'

export default {
loginModel
}

login.js

真正的业务代码可以分模块放在不同的文件中,例如login模块

1
2
3
4
5
6
7
8
9
10
import axios from './../axios'
import baseUrl from './base'
import qs from 'qs'

const loginModel = {
login(params){
return axios.post(`${baseUrl.base}/login`,qs.stringify(params))
}
}
export default loginModel

main.js

最后还需要把axios挂载在vue原型上,这样就可以直接用this来调用了,在main.js文件夹中添加如下代码:

1
2
3
//挂载axios
import axios from './request/api/index.js'
Vue.prototype.$axios = axios

调用接口的时候直接用this.$axios对应的名字就能获取数据了

1
2
3
4
5
login(){
this.$axios.loginModel.login().then(res =>{
console.log(res)
})
}

最近打算自己搭建一个简单的后台,来为自己的前端代码提供基础的接口,学过一些node.js的基础,首先就想到了基于node的express框架,记录下首次搭建的过程。

首先确认电脑上安装了node环境,在命令行工具敲node -v,如果显示除了版本号,则已经安装了node环境。如果没有安装,点击下载链接 http://nodejs.cn/download/,选择和你电脑系统相匹配的版本下载即可。

1
2
node -v
v10.16.0

安装express

新建一个文件夹,然后在文件夹内执行npm init为应用程序创建 package.json文件,全部回车默认执行即可。

1
npm init

然后安装express,并将其保存在依赖项列表中。

1
npm install express --save

初始化express服务

创建一个名为server的文件夹,在里边创建一个app.js,在里边添加如下代码:

1
2
3
4
5
6
7
8
9
10
var express = require('express')
var app = express()

app.get('/',function(req,res){
res.send('hello world')
})

app.listen(3000, function(){
console.log('hello')
})

使用以下命令运行程序

1
node app.js

然后在浏览器输入http://localhost:3000/就可以看到刚启动的服务了。

附express中文网链接https://expressjs.com/zh-cn/starter/installing.html