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