node验证码

公司业务需求,需要前端这边处理一下验证码,废了不少功夫,总算是解决了。

需求分析

原本是打算node层做验证码,但是发现依赖node-canvas还有其他的东西,可能还需要本机去装c++的编译之类的,而且windows与linux环境也有差异,就改为java后端
传回图片的二进制流文件,直接展示在前端,提交的时候再验证一次。
方案如下: node层请求java拿到图片,node层传到页面。直接在图片的src属性放node层的接口,直接展示,通过绑定click时间来进行刷新。

第一次尝试 fail

本来是打算直接在页面上通过ajax请求,然后进行展示,但是这里面遇到了几个问题

  • 前端使用的jq的ajax,ajax本身不支持二进制流的传输(解决方案:使用源生的xhr)
  • 所有的java接口需要进行签名,签名不能放在页面上做(否定上一中方案,解决方案:页面请求到node层,node层做签名,请求java,然后返回到页面)

node端是基于express的,请求使用的是request这个模块,
请求代码如下:

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
return new promise(function (resolve,reject){
if(options.type ==='get'){
request({
method:options.type,
url:configs.severUrl+options.url+'?'+urlArray.sort().join('&'),
data:{},
headers: userHeaders,
json:true
},function (error,response,body){
if(!error && response.statusCode == 200){
resolve(body)
}else{
reject(error)
}
});
}else{
request({
method:options.type,
url:configs.severUrl+options.url+'?'+urlArray.sort().join('&'),
data:Object.assign(options.data,urlData),
headers: userHeaders,
json:true
},function (error,response,body){
if(!error && response.statusCode == 200){
resolve(body)
}else{
reject(error)
}
});
}

});

node暴露前端请求接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
exports.getImg = function(req,res,next){
var id = uuid.v1();
ajax(req,{
type:'get',
url:'/login/tools/captcha',
data:{
uuid:id
}
}).then(function(result){
//result返回的是二进制流,使用buffer来接收
var buff = new Buffer(result,'binary');
//第二个参数指定buffer编码
//传入到页面设置指定头,即该文件类型
res.writeHead(200,{'content-type':"image/png"});
//使用end方法返回流文件
res.end(buff);
}).catch(function(err){
console.log(err);
});
};

这里解释一下,这个已经是正确的代码,之前的错误代码就不放了,避免歧义。具体看注释
html代码如下:

1
<img src="/getImg" alt="" id="img"/>

通过这次尝试,发现传到页面,图片并不展示,当时打印出来的后端返回的是一堆乱码,当时java端说返回的就是这样的,没有多想。
到此,只能进行第二次尝试。

第二次尝试 success

去看node关于buffer的解释,buffer是nodejs是node内核本身的一个核心库,让nodejs可以有存储数据类型的方法,可是处理二进制数据。
这里关于buffer不做太多的解释,有兴趣的可以去官网看哦~与本文的关系不是特别密切。但是这里需要注意buffer里面的第二个参数,encoding,即编码。
其中编码有:

  • ascii
  • utf8
  • utf16le
  • ucs2 Alias of ‘utf16le’. utf16le的别名
  • base64
  • binary Alias for ‘latin1’
  • hex

从网上的一些资料或者博客查到的 编码格式指定为’binary’,还有往下接着走,可以确定,不是在处理流的时候出问题。
java端将二进制流转成base64,然后放入页面图片可以显示,然而我在转成base64的时候却不可以。又找到一篇资料,这个就是解决问题的核心了
如果流的操作是没有问题的,那问题肯定就出现在流之前,即请求回来的时候是否有错。

去看request模块,并在github查看文档,发现,里面是需要制定数据类型,默认utf-8,我前面的请求代码里面是node的通用,都是采用json传输,所以指定了json,
问题貌似就出现在这儿!
request的文档里面找到,如果不指定有默认,那我们现在需要的处理就是我们不进行转码,就是讲encoding设置为null,
更改过之后的代码如下:

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
return new promise(function (resolve,reject){
if(options.type ==='get'){
request({
method:options.type,
url:configs.severUrl+options.url+'?'+urlArray.sort().join('&'),
data:{},
headers: userHeaders,
encoding:null
},function (error,response,body){
if(!error && response.statusCode == 200){
resolve(body)
}else{
reject(error)
}
});
}else{
request({
method:options.type,
url:configs.severUrl+options.url+'?'+urlArray.sort().join('&'),
data:Object.assign(options.data,urlData),
headers: userHeaders,
encoding:null
},function (error,response,body){
if(!error && response.statusCode == 200){
resolve(body)
}else{
reject(error)
}
});
}

然后在node接口层,我先输出了base64格式,然后填入图片的src,发现,图片出来了!!!!老泪纵横啊,坑了一下午了。
此时说明,现在buffer里面拿到的就是正确的了,之后不做base64处理,直接返回流,放入图片的src属性(很多验证码估计都是这样做的),成功!!!
这里之所以不转成base64是因为我们pc端还需要兼容ie8,么办法,害怕出现兼容问题,就用这种方法一劳永逸的解决好了。

总结

在第一次尝试的时候,并没有准确的定位问题,一方面自己对流的操作了解不多,无法准确定位。
第二个就是对request里面了解并不多(github的文档确实看着比较吃力。。。哎,四级没过,伤不起啊)
第三,因为java可以对这里做处理,那么nodejs肯定可以,如果没有解决问题,就说明是自己代码的问题。

最后的一个建议,不要害怕解决问题,你一定要相信你自己可以解决问题。解决问题就是你提高的一个过程。
如果这个问题是自己通过各种手段最终解决的,下次遇到类似的,你肯定记忆犹新,但是如果只是网上随便找了个代码贴上来解决问题,下一次
你估计八成还是无法解决问题。