关于java:Java二维码登录流程实现包含短地址生成含部分代码

4次阅读

共计 6492 个字符,预计需要花费 17 分钟才能阅读完成。

近年来,二维码的应用越来越风生水起,笔者最近手头也遇到了一个须要应用二维码扫码登录网站的活,所以钻研了一下这一套机制,并用代码实现了整个流程,接下来就和大家聊聊二维码登录及的那些事儿。

二维码原理

二维码是微信搞起来的,当年微信扫码二维码登录网页微信的时候,感觉很神奇,然而,咱们理解了它的原理,也就没那么神奇了。二维码实际上就是通过黑白的点阵蕴含了一个 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 包:zxingcore-2.0.jar

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

public class QrCodeUtil {private static final int BLACK = Color.black.getRGB();
    private static final int WHITE = Color.WHITE.getRGB();
    private static final int DEFAULT_QR_SIZE = 183;
    private static final String DEFAULT_QR_FORMAT = "png";
    private static final byte[] EMPTY_BYTES = new byte[0];
    
    public static byte[] createQrCode(String content, int size, String extension) {return createQrCode(content, size, extension, null);
    }

    /**
     * 生成带图片的二维码
     * @param content  二维码中要蕴含的信息
     * @param size  大小
     * @param extension  文件格式扩大
     * @param insertImg  两头的 logo 图片
     * @return
     */
    public static byte[] createQrCode(String content, int size, String extension, Image insertImg) {if (size <= 0) {throw new IllegalArgumentException("size (" + size + ")  cannot be <= 0");
        }
        ByteArrayOutputStream baos = null;
        try {Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);

            // 应用信息生成指定大小的点阵
            BitMatrix m = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, hints);
            
            // 去掉白边
            m = updateBit(m, 0);
            
            int width = m.getWidth();
            int height = m.getHeight();
            
            // 将 BitMatrix 中的信息设置到 BufferdImage 中,造成黑白图片
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            for (int i = 0; i < width; i++) {for (int j = 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 = new ByteArrayOutputStream();
            ImageIO.write(image, extension, baos);
            
            return baos.toByteArray();} catch (Exception e) { } finally {if(baos != null)
                try {baos.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();}
        }
        return EMPTY_BYTES;
    }
    
    /**
     * 自定义二维码白边宽度
     * @param matrix
     * @param margin
     * @return
     */
    private static BitMatrix updateBit(BitMatrix matrix, int margin) {
        int tempM = margin * 2;
        int[] rec = matrix.getEnclosingRectangle(); // 获取二维码图案的属性
        int resWidth = rec[2] + tempM;
        int resHeight = rec[3] + tempM;
        BitMatrix resMatrix = new BitMatrix(resWidth, resHeight); // 依照自定义边框生成新的 BitMatrix
        resMatrix.clear();
        for (int i = margin; i < resWidth - margin; i++) { // 循环,将二维码图案绘制到新的 bitMatrix 中
            for (int j = margin; j < resHeight - margin; j++) {if (matrix.get(i - margin + rec[0], j - margin + rec[1])) {resMatrix.set(i, j);
                }
            }
        }
        return resMatrix;
    }
    
    // 图片放大放大
    public static BufferedImage zoomInImage(BufferedImage originalImage, int width, int height) {BufferedImage newImage = new BufferedImage(width, height, originalImage.getType());
        Graphics g = newImage.getGraphics();
        g.drawImage(originalImage, 0, 0, width, height, null);
        g.dispose();
        return newImage;
    }
    
    private static void insertImage(BufferedImage source, Image insertImg, int size) {
        try {int width = insertImg.getWidth(null);
            int height = insertImg.getHeight(null);
            width = width > size / 6 ? size / 6 : width;  // logo 设为二维码的六分之一大小
            height = height > size / 6 ? size / 6 : height;
            Graphics2D graph = source.createGraphics();
            int x = (size - width) / 2;
            int y = (size - height) / 2;
            graph.drawImage(insertImg, x, y, width, height, null);
            Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
            graph.setStroke(new BasicStroke(3f));
            graph.draw(shape);
            graph.dispose();} catch (Exception e) {e.printStackTrace();
        }
    }

    public static byte[] createQrCode(String content) {return createQrCode(content, DEFAULT_QR_SIZE, DEFAULT_QR_FORMAT);
    }

    public static void main(String[] args){
        try {FileOutputStream fos = new FileOutputStream("ab.png");
            fos.write(createQrCode("test"));
            fos.close();} catch (Exception e) {
            // TODO Auto-generated catch block
            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)
public class ShortUrlUtil {

    /**
     * 传入 32 位 md5 值
     * @param md5
     * @return
     */
    public static String[] shortUrl(String md5) {
        // 要应用生成 URL 的字符
        String[] chars = new String[] { "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 = new String[4];  
        
        for (int i = 0; i < 4; i++) {

            // 把加密字符依照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算,超过 30 位的疏忽
            String sTempSubString = md5.substring(i * 8, i * 8 + 8);

            // 这里须要应用 long 型来转换,因为 Inteper .parseInt() 只能解决 31 位 , 首位为符号位 , 如果不必 long,则会越界
            long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);
            String outChars = "";
            for (int j = 0; j < 6; j++) {
                // 把失去的值与 0x0000003D 进行位与运算,获得字符数组 chars 索引
                long index = 0x0000003D & lHexLong;
                // 把获得的字符相加
                outChars += chars[(int) index];
                // 每次循环按位右移 5 位
                lHexLong = lHexLong >> 5;
            }
            // 把字符串存入对应索引的输入数组
            resUrl[i] = outChars;
        }
        return resUrl;
    }
    
    public static void main(String [] args){String[] test = shortUrl("fdf8d941f23680be79af83f921b107ac");
        for (String string : test) {System.out.println(string);
        }
    }
    
}

任何人想要转载我的文章,无需和我分割,请转载后把链接私信贴给我,谢谢!

正文完
 0