首頁  >  文章  >  Java  >  Java二維碼登入流程實作程式碼(包含短位址生成,含部分程式碼)

Java二維碼登入流程實作程式碼(包含短位址生成,含部分程式碼)

高洛峰
高洛峰原創
2017-01-20 14:21:302339瀏覽

近年來,二維碼的使用越來越風生水起,筆者最近手頭也遇到了一個需要使用二維碼掃碼登錄網站的活,所以研究了一下這套機制,並用代碼實現了整個流程,接下來就和大家聊聊二維碼登入及的那些事兒。

二維碼原理

二維碼是微信搞起來的,當年微信掃碼二維碼登入網頁微信的時候,感覺很神奇,然而,我們了解了它的原理,也就沒那麼神奇了。二維碼其實就是透過黑白的點陣包含了一個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

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);
    }
  }
   
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持PHP中文網。

更多Java二維碼登入流程實現代碼(包含短地址生成,含部分代碼)相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn