二维码是微信搞起来的,当年微信扫码二维码登录网页微信的时候,感觉很神奇,然而,我们了解了它的原理,也就没那么神奇了。二维码实际上就是通过黑白的点阵包含了一个 url 请求信息。端上扫码,请求 url,做对应的操作。
微信登录、支付宝扫码支付都是这个原理:
如图所示:
1. 请求二维码
桌面端向服务器发起请求一个二维码的。
2. 生成包含唯一 id 的二维码
桌面端会随机生成一个 id,id 唯一标识这个二维码,以便后续操作。
3. 端上扫码
移动端扫码二维码,解 chu 出二维码中的 url 请求。
4. 移动端发送请求到服务器
移动端向服务器发送 url 请求,请求中包含两个信息,唯一 id 标识扫的是哪个码,端上浏览器中特定的 cookie 或者 header 参数等会标识由哪个用户来进行扫码的。
5. 服务器端通知扫码成功
服务器端收到二维码中信息的 url 请求时,通知端上已经扫码成功,并添加必要的登录 Cookie 等信息。这里的通知方式一般有几种:websocket、轮训 hold 住请求直到超时、隔几秒轮训。
比如,在业务中,你可能想要这样的操作,如果是你公司的二维码被其他 app(如微信)所扫描,想要跳转一个提示页,提示页上可以有一个 app 的下载链接;而当被你自己的 app 所扫描时,直接进行对应的请求。
这种情况下,可以这样来做,所有二维码中的链接都进行一层加密,然后统一用另一个链接来处理。
如:
,p 参数中包含服务器与客户端约定的加解密算法(可以是对称的也可以是非对称的), 端上扫码到这种特定路径的时候,直接用解密算法解 p 参数,得到
- www.test.com/qr?p=xxxxxx
, 这样就可以向服务器发起请求了,而其他客户端因为不知道这个规则,只能直接去请求
- www.testqr.com/qrcode?key=s1arV
,这个请求返回提示页。
- www.test.com/qr?p=xxxxxx
很多时候,又要马儿跑,又要马儿不吃草。想要二维码中带有很多参数,但是又不想要二维码太复杂,难以被扫码出来。这时候,就需要考虑如何在不影响业务的情况下让二维码变的简单。
表示不同的 uri,端上来匹配遇到不同的 i 参数时请求哪个接口
- 1,2,3
就比
- www.a.com/q?k=s1arV
要简化很多,生成的二维码要好扫很多。
- www.abc.def.adfg.edu.com.cn/qrcode/scan?k=77179574e98a7c860007df62a5dbd98b
需要导入 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();
- }
- }
- }
基本思路:
短网址映射算法的理论:
- 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);
- }
- }
- }
核心代码非原创,借鉴了他人的代码,感谢!
来源: http://www.cnblogs.com/shenpengyan/p/6120257.html