需求
最近在做公司的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的過程,直到執行完畢。