Java二维码登录流程实现代码(包含短地址生成,含部分代码)
近年来,二维码的使用越来越风生水起,笔者最近手头也遇到了一个需要使用二维码扫码登录网站的活,所以研究了一下这一套机制,并用代码实现了整个流程,接下来就和大家聊聊二维码登录及的那些事儿。
二维码原理
二维码是微信搞起来的,当年微信扫码二维码登录网页微信的时候,感觉很神奇,然而,我们了解了它的原理,也就没那么神奇了。二维码实际上就是通过黑白的点阵包含了一个url请求信息。端上扫码,请求url,做对应的操作。
一般性扫码操作的原理
微信登录、支付宝扫码支付都是这个原理:
1.请求二维码
桌面端向服务器发起请求一个二维码的。
2.生成包含唯一id的二维码
桌面端会随机生成一个id,id唯一标识这个二维码,以便后续操作。
3.端上扫码
移动端扫码二维码,解chu出二维码中的url请求。
4.移动端发送请求到服务器
移动端向服务器发送url请求,请求中包含两个信息,唯一id标识扫的是哪个码,端上浏览器中特定的cookie或者header参数等会标识由哪个用户来进行扫码的。
5.服务器端通知扫码成功
服务器端收到二维码中信息的url请求时,通知端上已经扫码成功,并添加必要的登录Cookie等信息。这里的通知方式一般有几种:websocket、轮训hold住请求直到超时、隔几秒轮训。
二维码中url的艺术
如何实现自有客户端和其他客户端扫码(如微信)表现的不同
比如,在业务中,你可能想要这样的操作,如果是你公司的二维码被其他app(如微信)所扫描,想要跳转一个提示页,提示页上可以有一个app的下载链接;而当被你自己的app所扫描时,直接进行对应的请求。
这种情况下,可以这样来做,所有二维码中的链接都进行一层加密,然后统一用另一个链接来处理。
如:www.test.com/qr?p=xxxxxx,p参数中包含服务器与客户端约定的加解密算法(可以是对称的也可以是非对称的),端上扫码到这种特定路径的时候,直接用解密算法解p参数,得到www.testqr.com/qrcode?key=s1arV,这样就可以向服务器发起请求了,而其他客户端因为不知道这个规则,只能直接去请求www.test.com/qr?p=xxxxxx,这个请求返回提示页。
如何让二维码更简单
很多时候,又要马儿跑,又要马儿不吃草。想要二维码中带有很多参数,但是又不想要二维码太复杂,难以被扫码出来。这时候,就需要考虑如何在不影响业务的情况下让二维码变的简单。
- 与端上约定规则:比如定义编码信息中i参数为1,2,3表示不同的uri,端上来匹配遇到不同的i参数时请求哪个接口
- 简化一切能简化的地方:简化uri,简化参数中的key、value。如www.a.com/q?k=s1arV就比www.abc.def.adfg.edu.com.cn/qrcode/scan?k=77179574e98a7c860007df62a5dbd98b要简化很多,生成的二维码要好扫很多。
- 简化唯一id参数:上一条中前一个请求中参数值只有5位,后一个请求中参数值为生成的32位md5值。生成一个端的key至关重要。
示例代码
生成二维码(去掉白边,增加中间的logo)
需要导入jar包:zxing的core-2.0.jar
importjava.awt.BasicStroke; importjava.awt.Color; importjava.awt.Graphics; importjava.awt.Graphics2D; importjava.awt.Image; importjava.awt.Shape; importjava.awt.geom.RoundRectangle2D; importjava.awt.image.BufferedImage; importjava.io.ByteArrayOutputStream; importjava.io.FileOutputStream; importjava.io.IOException; importjava.util.HashMap; importjava.util.Map; importjavax.imageio.ImageIO; importcom.google.zxing.BarcodeFormat; importcom.google.zxing.EncodeHintType; importcom.google.zxing.MultiFormatWriter; importcom.google.zxing.common.BitMatrix; importcom.google.zxing.qrcode.decoder.ErrorCorrectionLevel; publicclassQrCodeUtil{ privatestaticfinalintBLACK=Color.black.getRGB(); privatestaticfinalintWHITE=Color.WHITE.getRGB(); privatestaticfinalintDEFAULT_QR_SIZE=183; privatestaticfinalStringDEFAULT_QR_FORMAT="png"; privatestaticfinalbyte[]EMPTY_BYTES=newbyte[0]; publicstaticbyte[]createQrCode(Stringcontent,intsize,Stringextension){ returncreateQrCode(content,size,extension,null); } /** *生成带图片的二维码 *@paramcontent二维码中要包含的信息 *@paramsize大小 *@paramextension文件格式扩展 *@paraminsertImg中间的logo图片 *@return */ publicstaticbyte[]createQrCode(Stringcontent,intsize,Stringextension,ImageinsertImg){ if(size<=0){ thrownewIllegalArgumentException("size("+size+")cannotbe<=0"); } ByteArrayOutputStreambaos=null; try{ Map<EncodeHintType,Object>hints=newHashMap<EncodeHintType,Object>(); hints.put(EncodeHintType.CHARACTER_SET,"utf-8"); hints.put(EncodeHintType.ERROR_CORRECTION,ErrorCorrectionLevel.M); //使用信息生成指定大小的点阵 BitMatrixm=newMultiFormatWriter().encode(content,BarcodeFormat.QR_CODE,size,size,hints); //去掉白边 m=updateBit(m,0); intwidth=m.getWidth(); intheight=m.getHeight(); //将BitMatrix中的信息设置到BufferdImage中,形成黑白图片 BufferedImageimage=newBufferedImage(width,height,BufferedImage.TYPE_INT_RGB); for(inti=0;i<width;i++){ for(intj=0;j<height;j++){ image.setRGB(i,j,m.get(i,j)?BLACK:WHITE); } } if(insertImg!=null){ //插入中间的logo图片 insertImage(image,insertImg,m.getWidth()); } //将因为去白边而变小的图片再放大 image=zoomInImage(image,size,size); baos=newByteArrayOutputStream(); ImageIO.write(image,extension,baos); returnbaos.toByteArray(); }catch(Exceptione){ }finally{ if(baos!=null) try{ baos.close(); }catch(IOExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } } returnEMPTY_BYTES; } /** *自定义二维码白边宽度 *@parammatrix *@parammargin *@return */ privatestaticBitMatrixupdateBit(BitMatrixmatrix,intmargin){ inttempM=margin*2; int[]rec=matrix.getEnclosingRectangle();//获取二维码图案的属性 intresWidth=rec[2]+tempM; intresHeight=rec[3]+tempM; BitMatrixresMatrix=newBitMatrix(resWidth,resHeight);//按照自定义边框生成新的BitMatrix resMatrix.clear(); for(inti=margin;i<resWidth-margin;i++){//循环,将二维码图案绘制到新的bitMatrix中 for(intj=margin;j<resHeight-margin;j++){ if(matrix.get(i-margin+rec[0],j-margin+rec[1])){ resMatrix.set(i,j); } } } returnresMatrix; } //图片放大缩小 publicstaticBufferedImagezoomInImage(BufferedImageoriginalImage,intwidth,intheight){ BufferedImagenewImage=newBufferedImage(width,height,originalImage.getType()); Graphicsg=newImage.getGraphics(); g.drawImage(originalImage,0,0,width,height,null); g.dispose(); returnnewImage; } privatestaticvoidinsertImage(BufferedImagesource,ImageinsertImg,intsize){ try{ intwidth=insertImg.getWidth(null); intheight=insertImg.getHeight(null); width=width>size/6?size/6:width;//logo设为二维码的六分之一大小 height=height>size/6?size/6:height; Graphics2Dgraph=source.createGraphics(); intx=(size-width)/2; inty=(size-height)/2; graph.drawImage(insertImg,x,y,width,height,null); Shapeshape=newRoundRectangle2D.Float(x,y,width,width,6,6); graph.setStroke(newBasicStroke(3f)); graph.draw(shape); graph.dispose(); }catch(Exceptione){ e.printStackTrace(); } } publicstaticbyte[]createQrCode(Stringcontent){ returncreateQrCode(content,DEFAULT_QR_SIZE,DEFAULT_QR_FORMAT); } publicstaticvoidmain(String[]args){ try{ FileOutputStreamfos=newFileOutputStream("ab.png"); fos.write(createQrCode("test")); fos.close(); }catch(Exceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } } }
生成短链接
基本思路:
短网址映射算法的理论:
1.将长网址加随机数用用md5算法生成32位签名串,分为4段,每段8个字符
2.对这4段循环处理,取每段的8个字符,将他看成16进制字符串与0x3fffffff(30位1)的位与操作,超过30位的忽略处理
3.将每段得到的这30位又分成6段,每5位的数字作为字母表的索引取得特定字符,依次进行获得6位字符串;
4.这样一个md5字符串可以获得4个6位串,取里面的任意一个就可作为这个长url的短url地址。
5.最好是用一个key-value数据库存储,万一发生碰撞换一个,如果四个都发生碰撞,重新生成md5(因为有随机数,会生成不一样的md5)
publicclassShortUrlUtil{ /** *传入32位md5值 *@parammd5 *@return */ publicstaticString[]shortUrl(Stringmd5){ //要使用生成URL的字符 String[]chars=newString[]{"a","b","c","d","e","f","g","h", "i","j","k","l","m","n","o","p","q","r","s","t", "u","v","w","x","y","z","0","1","2","3","4","5", "6","7","8","9","A","B","C","D","E","F","G","H", "I","J","K","L","M","N","O","P","Q","R","S","T", "U","V","W","X","Y","Z" }; String[]resUrl=newString[4]; for(inti=0;i<4;i++){ //把加密字符按照8位一组16进制与0x3FFFFFFF进行位与运算,超过30位的忽略 StringsTempSubString=md5.substring(i*8,i*8+8); //这里需要使用long型来转换,因为Inteper.parseInt()只能处理31位,首位为符号位,如果不用long,则会越界 longlHexLong=0x3FFFFFFF&Long.parseLong(sTempSubString,16); StringoutChars=""; for(intj=0;j<6;j++){ //把得到的值与0x0000003D进行位与运算,取得字符数组chars索引 longindex=0x0000003D&lHexLong; //把取得的字符相加 outChars+=chars[(int)index]; //每次循环按位右移5位 lHexLong=lHexLong>>5; } //把字符串存入对应索引的输出数组 resUrl[i]=outChars; } returnresUrl; } publicstaticvoidmain(String[]args){ String[]test=shortUrl("fdf8d941f23680be79af83f921b107ac"); for(Stringstring:test){ System.out.println(string); } } }
说明:核心代码非原创,借鉴了他人的代码,感谢!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。