近年來,二維碼的使用越來越風生水起,筆者最近手頭也遇到了一個需要使用二維碼掃碼登錄網站的活,所以研究了一下這一套機制,并用代碼實現了整個流程,接下來就和大家聊聊二維碼登錄及的那些事兒。
二維碼原理
二維碼是微信搞起來的,當年微信掃碼二維碼登錄網頁微信的時候,感覺很神奇,然而,我們了解了它的原理,也就沒那么神奇了。二維碼實際上就是通過黑白的點陣包含了一個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,這個請求返回提示頁。
如何讓二維碼更簡單
很多時候,又要馬兒跑,又要馬兒不吃草。想要二維碼中帶有很多參數,但是又不想要二維碼太復雜,難以被掃碼出來。這時候,就需要考慮如何在不影響業務的情況下讓二維碼變的簡單。
示例代碼
生成二維碼(去掉白邊,增加中間的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);    }  }  }說明:核心代碼非原創,借鑒了他人的代碼,感謝!
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。
新聞熱點
疑難解答