卡券生成方案總結

業務場景

需要生成類似微信openid的字符串,openid 在相同公衆號下,openid前面部分字符是相等的,例如:

某一個公衆號下的三個關注用戶的openid是這樣的:

oB4nYjnoHhuWrPVi2pYLuPjnCaU0

oB4nYjhJHQVaD0PL7qs0W1kL-_ls

oB4nYjvY13SVtaWC-AFztM2f3TlU

注意到,openid 的前面一部分都是 oB4nYj,但具體的算法,微信未公佈。

前面部分,是表示特定的公衆號,後面部分則是該用戶的標識符,該openid在該公衆號下是唯一的。

對於自身業務需求,現在需要一串前半部分標識供應商、後半部分則是隨機碼的字符串券碼,且供應商標識需要加密、長度不能過長。

思考過程

對於前半部分供應商標識

  • 需要加密

  • 加密後的長度不能過長

    對於加密算法,主要有如下幾類算法:

加密和解密(1):常用數據加密和解密方法彙總

但這些算法加密後的字符串長度都過長。此時就思索,有沒有一種既能滿足安全性高,且加密後的字符串長度也短的算法呢?

對於這種需求,也試驗了很多種算法。RC4算法 就能滿足。【密碼學】RC4加解密原理及其Java和C實現算法

對於後半部分字符串券碼

需要唯一。即生成唯一ID,對此查閱相關資料

分佈式系統唯一ID生成方案彙總

主要有幾種解決方案:

  1. 數據庫自增長序列或字段(美團點評分佈式ID生成系統
  2. UUID
  3. Redis生成ID
  4. Twitter的snowflake算法(Twitter的雪花算法(snowflake)自增ID)
  5. 利用zookeeper生成唯一ID
  6. MongoDB的ObjectId

這些技術方案滿足ID唯一,其中snowflake算法在這些方案中最佳,但生成的ID都過長。

技術方案

  • RC4算法加密的三位運營商id(加密後變爲6位)
  • uuid(壓縮後變成8位的短字符串)
  1. 系統本身已有供應商標識,例如:001
  2. 對該標識用RC4,這樣的好處是加密後的字符串長度短,加密性比較好。提供工具類如下:
package com.ceb.mental.util.IdGen;

import java.io.UnsupportedEncodingException;

/**
 * 負責生成ACCESS_TOKEN||REFRESH_TOKEN令牌
 *
 */
public class RC4 {
    /** ACCESS_TOKEN的加密鑰匙 **/
    public static final String ACCESSKEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxACCESSKEY";
    /** REFRESH_TOKEN的加密鑰匙 **/
    public static final String REFRESHKEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxREFRESHKEY";

    /** 加密 **/
    public static String encrypt(String data, String key) {
        if (data == null || key == null) {
            return null;
        }
        return toHexString(asString(encrypt_byte(data, key)));
    }

    /** 解密 **/
    public static String decrypt(String data, String key) {
        if (data == null || key == null) {
            return null;
        }
        return new String(RC4Base(HexString2Bytes(data), key));
    }

    /** 加密字節碼 **/
    public static byte[] encrypt_byte(String data, String key) {
        if (data == null || key == null) {
            return null;
        }
        byte b_data[] = data.getBytes();
        return RC4Base(b_data, key);
    }

    private static String asString(byte[] buf) {
        StringBuffer strbuf = new StringBuffer(buf.length);
        for (int i = 0; i < buf.length; i++) {
            strbuf.append((char) buf[i]);
        }
        return strbuf.toString();
    }

    private static byte[] initKey(String aKey) {
        byte[] b_key = aKey.getBytes();
        byte state[] = new byte[256];

        for (int i = 0; i < 256; i++) {
            state[i] = (byte) i;
        }
        int index1 = 0;
        int index2 = 0;
        if (b_key == null || b_key.length == 0) {
            return null;
        }
        for (int i = 0; i < 256; i++) {
            index2 = ((b_key[index1] & 0xff) + (state[i] & 0xff) + index2) & 0xff;
            byte tmp = state[i];
            state[i] = state[index2];
            state[index2] = tmp;
            index1 = (index1 + 1) % b_key.length;
        }
        return state;
    }

    private static String toHexString(String s) {
        String str = "";
        for (int i = 0; i < s.length(); i++) {
            int ch = (int) s.charAt(i);
            String s4 = Integer.toHexString(ch & 0xFF);
            if (s4.length() == 1) {
                s4 = '0' + s4;
            }
            str = str + s4;
        }
        return str;// 0x表示十六進制
    }

    private static byte[] HexString2Bytes(String src) {
        int size = src.length();
        byte[] ret = new byte[size / 2];
        byte[] tmp = src.getBytes();
        for (int i = 0; i < size / 2; i++) {
            ret[i] = uniteBytes(tmp[i * 2], tmp[i * 2 + 1]);
        }
        return ret;
    }

    private static byte uniteBytes(byte src0, byte src1) {
        char _b0 = (char) Byte.decode("0x" + new String(new byte[] { src0 })).byteValue();
        _b0 = (char) (_b0 << 4);
        char _b1 = (char) Byte.decode("0x" + new String(new byte[] { src1 })).byteValue();
        byte ret = (byte) (_b0 ^ _b1);
        return ret;
    }

    private static byte[] RC4Base(byte[] input, String mKkey) {
        int x = 0;
        int y = 0;
        byte key[] = initKey(mKkey);
        int xorIndex;
        byte[] result = new byte[input.length];

        for (int i = 0; i < input.length; i++) {
            x = (x + 1) & 0xff;
            y = ((key[x] & 0xff) + y) & 0xff;
            byte tmp = key[x];
            key[x] = key[y];
            key[y] = tmp;
            xorIndex = ((key[x] & 0xff) + (key[y] & 0xff)) & 0xff;
            result[i] = (byte) (input[i] ^ key[xorIndex]);
        }
        return result;
    }

    /**
     * 字符串轉換成十六進制字符串
     */
    public static String str2HexStr(String str) {
        char[] chars = "0123456789ABCDEF".toCharArray();
        StringBuilder sb = new StringBuilder("");
        byte[] bs = str.getBytes();
        int bit;
        for (int i = 0; i < bs.length; i++) {
            bit = (bs[i] & 0x0f0) >> 4;
            sb.append(chars[bit]);
            bit = bs[i] & 0x0f;
            sb.append(chars[bit]);
        }
        return sb.toString();
    }

    /**
     *
     * 十六進制轉換字符串
     *
     * @throws UnsupportedEncodingException
     */

    public static String hexStr2Str(String hexStr) {
        String str = "0123456789ABCDEF";
        char[] hexs = hexStr.toCharArray();
        byte[] bytes = new byte[hexStr.length() / 2];
        int n;
        for (int i = 0; i < bytes.length; i++) {
            n = str.indexOf(hexs[2 * i]) * 16;
            n += str.indexOf(hexs[2 * i + 1]);
            bytes[i] = (byte) (n & 0xff);
        }
        return new String(bytes);
    }

    public static void main(String[] args) {
        String data="002";
        System.out.println("加密結果 "+RC4.encrypt(data, ACCESSKEY));
        System.out.println("解密結果 "+RC4.decrypt(RC4.encrypt(data, ACCESSKEY), ACCESSKEY));
    }
}
  1. 生成UUID,網上找的都是生成32位的字符串,長度過長,這邊對生成的UUID進行壓縮,最終生成8位的UUID,代碼如下:

    package com.bridgeintelligent.ercp.common.utils.IdGen;
    
    import java.util.List;
    import java.util.UUID;
    import java.util.Vector;
    
    public class IdGenUtil {
    
    
        /**
         * @param num
         * @return
         * @function 生成num位的隨機字符串(數字 、 大寫字母隨機混排)
         */
        public static String createBigSmallLetterStrOrNumberRadom(int num) {
    
            String str = "";
            for (int i = 0; i < num; i++) {
                int intVal = (int) (Math.random() * 58 + 65);
                if (intVal >= 91 && intVal <= 96) {
                    i--;
                }
                if (intVal < 91 || intVal > 96) {
                    if (intVal % 2 == 0) {
                        str += (char) intVal;
                    } else {
                        str += (int) (Math.random() * 10);
                    }
                }
            }
            return str;
        }
    
        public static 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"
                };
    
        /**
         * 獲取8位的uuid
         *
         * @return
         */
        public static String getShortUuid() {
            StringBuffer stringBuffer = new StringBuffer();
            String uuid = UUID.randomUUID().toString().replace("-", "");
            for (int i = 0; i < 8; i++) {
                String str = uuid.substring(i * 4, i * 4 + 4);
                int strInteger = Integer.parseInt(str, 16);
                stringBuffer.append(chars[strInteger % 0x3E]);
            }
    
            return stringBuffer.toString();
        }
    
    
        public static String idGen(String provideId) {
            // 先對提供商進行加密
            String encryptPID = RC4.encrypt(provideId, RC4.ACCESSKEY);
            //隨機字符串
    //        String radom = createBigSmallLetterStrOrNumberRadom(8);
            String radom = getShortUuid();
            return encryptPID + radom;
        }
    
        public static List<String> idBatchGen(String provideId, int idNum) {
            List<String> idList = new Vector<>();
            for (int i = 0; i < idNum; i++) {
                idList.add(idGen(provideId));
            }
            return idList;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                System.out.println(idGen("001"));
            }
            System.out.println(RC4.decrypt("0247BC"));
    
            System.out.println("=======================");
    
            System.out.println(idBatchGen("001", 10000));
        }
    }
    
  2. 其中主要包括生成8位的uuid方法,傳入運營商ID,即可生成相應的券碼,運行結果如下:

    生成的券碼列表

其中前6位是加密後的字符串,與預期結果一致。後八位是UUID壓縮後的字符串。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章