工作纪实_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的过程,直到执行完毕。

你可能感兴趣的:(工作纪实)