生成订单号

一、前言

之前做个一个购票项目,在这里记录下,生成订单号的代码,以备后续参考.
思路:订单号是不能重复的,每个订单加1,且不能重复。因为多用户可能同时创建订单,导致订单号重复,所以用redis的自增incr。

二、环境

  • jdk 1.8
  • redis

三、枚举类

枚举类配置各种单号(订单号、退款单号、对账单号)等,之后按需添加即可。

/**
* 单号生成类型枚举
*
* @author qiao
* @since 2022-03-18
*/
@Getter
public enum NumberTypeEnum {

    /**
     * 订单号:
     * 固定前缀:ORDER
     * 流水号长度:7
     * 随机数长度:3
     */
    ORDER_NUMBER("ORDER", "ls:bus:order",7, 3),

    /**
     * 退款单号:
     * 固定前缀:REFUND
     * 流水号长度:7
     * 随机数长度:3
     */
    REFUND_NUMBER("REFUND" , "ls:bus:refund",7, 3),

    /**
     * 子订单票号
     * 固定前缀:TICKET
     * 流水号长度:7
     * 随机数长度:3
     */
    TICKET_NUMBER("TICKET" , "ls:bus:ticket",7, 3),

    /**
     * 对账单号
     * 固定前缀:RECONCILIATION
     * 流水号长度:7
     * 随机数长度:3
     */
    RECONCILIATION_NUMBER("RECONCILIATION" , "ls:bus:reconciliation",7, 3);

    /**
     * 单号前缀
     * 为空时填""
     */
    private String prefix;

    /**
     * redis key
     */
    private String redisKey;

    /**
     * 流水号长度 redis自增 不够补0
     */
    private Integer serialLength;
    /**
     * 随机数长度
     */
    private Integer randomLength;


    NumberTypeEnum(String prefix, String redisKey, Integer serialLength, Integer randomLength) {
        this.prefix = prefix;
        this.redisKey = redisKey;
        this.serialLength = serialLength;
        this.randomLength = randomLength;
    }
}

四、具体实现

1、接口

public interface IBusOrderService extends IService<BusOrder> {
  /**
     * 生成单号
     *
     * @param numberTypeEnum 单号生成规则
     * @return
     */
    String generateOrderNumber(NumberTypeEnum numberTypeEnum);
}

2、实现类

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class BusOrderServiceImpl extends ServiceImpl<BusOrderMapper, BusOrder> implements IBusOrderService {

    @Autowired
    private RedisUtil redisUtil;
/**
     * 生成订单号
     *
     * @param numberTypeEnum
     * @return
     */
    @Override
    public String generateOrderNumber(NumberTypeEnum numberTypeEnum) {
        Calendar instance = Calendar.getInstance();
        instance.setTime(new Date());
        int day = instance.get(Calendar.DAY_OF_YEAR);
        String dayFormat = String.format("%1$03d", day);
        String incrKey = numberTypeEnum.getRedisKey().concat(dayFormat);
        long incr = redisUtil.incr(incrKey, 1);
        redisUtil.expire(incrKey, BusConstants.THREE_DAY_SECONDS);
        String orderPrefix = BusUtils.completionPrefix(numberTypeEnum.getPrefix());
        String random = BusUtils.completionRandom(numberTypeEnum.getRandomLength());
        String serial = BusUtils.completionSerial(incr, numberTypeEnum.getSerialLength());
        return MessageFormat.format("{0}{1}{2}", orderPrefix, random, serial);
    }
}

3、RedisUtils.java

@Component
public class RedisUtil {
 /**
     * 递增
     *
     * @param key 键
     * @param by  要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }
     /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

4、BusUtils.java

public class BusUtils {
 /**
     * 生成订单号前缀
     * @param prefix
     * @return
     */
    public static String completionPrefix(String prefix){
        Calendar instance = Calendar.getInstance();
        instance.setTime(new Date());
        int year = instance.get(Calendar.YEAR);
        int day = instance.get(Calendar.DAY_OF_YEAR);
        int hour = instance.get(Calendar.HOUR_OF_DAY);
        String dayFormat = String.format("%1$03d", day);
        String hourFormat = String.format("%1$02d", hour);
        return MessageFormat.format("{0}{1}{2}{3}", prefix, (year - 2000), dayFormat, hourFormat);
    }
    /**
     * 补全随机数
     * @param length 长度
     * @return
     */
    public static String completionRandom(int length) {
        StringBuffer sb = new StringBuffer();
        //随机数长度
        if (length > 0) {
            Random random = new Random();
            for (int i = 0; i < length; i++) {
                //十以内随机数补全
                sb.append(random.nextInt(10));
            }
        }
        return sb.toString();
    }


    /**
     * 补全流水号
     * @param incrementalSerial redis当日自增数
     * @param total 长度
     * @return
     */
    public static String completionSerial(Long incrementalSerial, int total){
        String express = MessageFormat.format("%1$0{0}d", total);
        String dayFormat = String.format(express, incrementalSerial);
        return dayFormat;
    }
}

5、调用

String orderNumber = this.generateOrderNumber(NumberTypeEnum.ORDER_NUMBER);

6、测试并发单号是否重复

这里实在其他大佬哪里找的代码,其实也可以用 jemter 测试并发


  /**
     * 生产单号并发测试
     */
    @Test
    public void testLoad() {
        // 1. 定义闭锁来拦截线程
        final CountDownLatch startGate = new CountDownLatch(1);
        final CountDownLatch endGate = new CountDownLatch(100);

        ConcurrentLinkedQueue<String> strings = new ConcurrentLinkedQueue<>();
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(() -> {
                try {
                    startGate.await();

                    String s = busOrderService.generateOrderNumber(NumberTypeEnum.ORDER_NUMBER);
                    // ORDER2207612480000001
//                    System.out.println(s);
                    strings.add(s);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    endGate.countDown();
                }
            });
            thread.start();
        }
        // 3. 线程统一放行,并记录时间!
        long start = System.nanoTime();

        startGate.countDown();
        try {
            endGate.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long end = System.nanoTime();
        System.out.println("cost times :" + (end - start));
        strings.forEach(System.out::println);
        List<String> collect = strings.stream().distinct().collect(Collectors.toList());
        log.info("去重结果:{}", collect.size() == strings.size());

    }

总结

之前总是项目要用被动去学、去抄,过了一段时间,再用到,又会遗忘,再次浏览大佬博客。自己想着改变现状,便有了复盘,将自己用过的技术点,变成帖子,加深记忆,学习思维逻辑,方便以后查阅。

学海无涯

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值