工作紀實_01_按規則生成分佈式全局唯一的編號

需求

最近在做公司的ERP會議系統,遇到了一個需求,需要根據會議的信息來生成規定的序列號:

序列號規則:客戶簡稱首拼-OP-銷售姓名首拼-項目開始時間+項目開始時間當天的第幾個會議

難點:

​ 1.客戶名稱漢字轉首拼,銷售姓名漢字轉首拼

​ 2.根據會議開始時間,獲取到該會議是開始時間的第幾個會議

說明

客戶簡稱:九次方大數據

銷售姓名:張三

開始時間:2019-12-01

當前時間爲:2019-10-01

則生成客戶編號:JCFDSJ-OP-ZS-20191201-01

如果再創建一個開始時間相同,其他信息不同的會議,則應該生成編號:xxxx-OP-xxx-20191201-02

01->02->03自增下去

項目環境

3個服務一同運行,共享一個reids服務器

解決思路

​ 1.首拼,沒有別的辦法,只能是找插件了,漢字轉拼音插件

​ 2.序列號有兩種方案,sql查詢出會議開始時間當天有多少個,count累加【存在一定的分佈式併發問題】,另一種藉助第三方分佈式協調工具,個人感覺,只要是能提供分佈式服務的框架應該就可以,比如zk,或者redis,首先當然還是輕量級的redis了

核心代碼

1.漢字轉首拼

package com.micecs.erp.util.thirdpart;

import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 漢字轉拼音插件
 *
 * @author liulei [email protected]
 * @version 1.0
 */
public class CharacterUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(CharacterUtil.class);
    /***
     * 將漢字轉成拼音(取首字母或全拼)
     * @param chinese
     * @param full 是否全拼
     * @return
     */
    public static String ConvertChinese2Pinyin(String chinese, boolean full) {
        /***
         * ^[\u2E80-\u9FFF]+$ 匹配所有東亞區的語言
         * ^[\u4E00-\u9FFF]+$ 匹配簡體和繁體
         * ^[\u4E00-\u9FA5]+$ 匹配簡體
         */
        String regExp = "^[\u4E00-\u9FFF]+$";
        StringBuffer sb = new StringBuffer();
        if (chinese == null || "".equals(chinese.trim())) {
            return "";
        }
        String pinyin;
        for (int i = 0; i < chinese.length(); i++) {
            char unit = chinese.charAt(i);
            //是漢字,則轉拼音
            if (match(String.valueOf(unit), regExp)) {
                pinyin = convertSingleChinese2Pinyin(unit);
                if (full) {
                    sb.append(pinyin);
                } else {
                    sb.append(pinyin.charAt(0));
                }
            } else {
                sb.append(unit);
            }
        }
        return sb.toString();
    }

    /***
     * 將單個漢字轉成拼音
     * @param chinese
     * @return
     */
    private static String convertSingleChinese2Pinyin(char chinese) {
        HanyuPinyinOutputFormat outputFormat = new HanyuPinyinOutputFormat();
        outputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        String[] res;
        StringBuffer sb = new StringBuffer();
        try {
            res = PinyinHelper.toHanyuPinyinStringArray(chinese, outputFormat);
            //對於多音字,只用第一個拼音
            sb.append(res[0]);
        } catch (Exception e) {
            LOGGER.error("CONVERT SINGLE CHINESE TO PINYIN ERROR", e);
            return "";
        }
        return sb.toString();
    }

    /***
     * @param str 源字符串
     * @param regex 正則表達式
     * @return 是否匹配
     */
    public static boolean match(String str, String regex) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);
        return matcher.find();
    }

    /**
     * 生成length長度的隨機數字和字母
     *
     * @param length
     * @return
     */
    public static String GetStringRandom(int length) {
        String val = "";
        Random random = new Random(1);
        //參數length,表示生成幾位隨機數
        int num = 0;
        while(num < length){
            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
            //輸出字母還是數字
            if ("char".equalsIgnoreCase(charOrNum)) {
                //輸出是大寫字母還是小寫字母
                int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
                val += (char) (random.nextInt(26) + temp);
                num++;
            }
        }
        return val;
    }

}

2.生成某一天的序列號

private RedisTemplate<String, Long> seqNumberGenerator;

public Long increment(String key, Date expireDate) {
    Long seqNum = null;
    try {
        seqNum = seqNumberGenerator.execute(new SessionCallback<Long>() {
            @Override
            public Long execute(RedisOperations operations) {
                //設置要監控的Key
            	operations.watch("key1");
                //開啓事務。在exec命令執行前,全部都只是進入隊列
                operations.multi();
                operations.opsForValue().increment(key, 1L);
                if (Objects.nonNull(expireDate)) {
                    //對key的過期時間做控制[取某一天的結束時間,舉例中的2019-12-01 23:59:59]
                    operations.expireAt(key, expireDate);
                }
                //執行exec命令,將先判斷key是否在監控後被修改過,如果是則不執行事務,否則就執行事務
                List exec = operations.exec();
                Long seqNum = (Long) exec.get(0);
                LOGGER.info("redis exec result {}", seqNum);
                return seqNum;
            }
        });
    } catch (Exception e) {
        LOGGER.error("redis error {}", e.getMessage(), e);
    }
    Validate.notNull(seqNum);
    return seqNum;
}

採用了redis的事物來對序列號做控制,強保證序列號的正確與唯一性

難點解讀

1.對於自增手段的控制:redis提供的基礎數字類型的increment方法

2.對於分佈式服務下,保證編號的唯一,採用redis的事物控制[需要對watch、multi、exec方法有基本理解]

redis的事務是由multi和exec包圍起來的部分,當發出multi命令時,redis會進入事務,redis會進入阻塞狀態,不再響應任何別的客戶端的請求,直到發出multi命令的客戶端再發出exec命令爲止。那麼被multi和exec包圍的命令會進入獨享redis的過程,直到執行完畢。

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