业务单据号每日重置后从1开始

概述


如果你从事过类似B端进销存系统相关的开发工作,一定会遇到一个需求:

业务单据号每天重置,后几位每天从1开始递增。

比如说门店每日的订货单:

  • DH202407010001
  • DH202407010002
  • DH202407010003
  • DH202407010004
头两个字母DH表示订货;
中间6位是年份和日期
后四位是自增的

到了第二天的时候,订货单据号会重置重新从1开始:

  • DH202407020001
  • DH202407020002
  • DH202407020003
  • DH202407020004

有线下门店的各个餐饮、茶饮店门店端系统的单据,基本都是使用如上的规则。

解决方案讨论


处理这个需求之前,需要先了解这个需求背后隐含的2个问题:

  • 单据号当天不能重复,就算在【分布式环境】且有【并发】的情况下也不能重复;
  • 单据号每天都需要重置。

如果你到网络上查询解决方案,可能搜索到的大概方案如下:

  • 使用Redis或其他缓存系统中的原子操作;
  • 使用分布式ID生成器,如Snowflake算法;
  • 使用存储过程、触发器等
  • 等等。。。。。。

这些我都觉得要么太复杂要么太贵了,就比如说使用Redis和MQ的,完全没有必要因为这个小需求,把重量级的Redis和MQ引入进来,且买Redis和MQ示例也是需要钱的。

需要结合当时的业务实际情况和技术团队技术栈情况,使用合适的技术。

解决方案:使用JAVA+mysql+定时任务


由于公司的门店不多,几百家,流量不大,就算有并发,瞬间并发数也是非常低的。是可以直接使用mysql来实现的。

主要的设计思路如下:

  • 用一张mysql表,建立自增id,确保在【当天】不重复;
  • 使用定时任务,每天凌晨的时候清理掉(TRUNCATE)数据,确保第二天id又从1开始。

这套方案虽然简单粗暴,但它已稳定运行快一年了,暂时未出现过任何问题。算是一套价格便宜又实惠稳定的技术方案了。

具体的实操代码


目前团队用的技术是基于SpringCloud Alibaba +DDD的,会使用到阿里相关的技术组件和涉及到DDD相关的内容。

建立docs_day_id_generator表以及PO对象

CREATE TABLE `docs_day_id_generator` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `created_date` date NOT NULL COMMENT '创建日期',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_create_date` (`created_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单据id生成'

对应的po对象:

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * 单据id生成器PO
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("docs_day_id_generator")
public class DocsDayIdGeneratorPO {
    /**
     * 自增id
     */
    private Long id;

    /**
     * 创建日期
     */
    private Date createdDate;
}

DDD仓储层和领域层实现

剩下的只要在DDD的仓储层和domain层定义相关的接口和实现即可。

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

/**
 * 单据id生成器-仓储接口层实现类
 */
@RequiredArgsConstructor
@Repository
public class DocsDayIdGeneratorRepositoryImpl implements DocsDayIdGeneratorRepository {
   private final DocsDayIdGeneratorMapper docsDayIdGeneratorMapper;

   @Override
   public Long createDocsSequence(Date currentDate) {
      DocsDayIdGeneratorPO poInsert = DocsDayIdGeneratorPO.builder().createdDate(currentDate).build();
      
      docsDayIdGeneratorMapper.insert(poInsert);
      
      return poInsert.getId();
   }
}

仓储层的实现比较简单,只需要构建一个DocsDayIdGeneratorPO记录,插入到表里即可,生成的自增id会存储在po对象的id字段里,直接返回即可。

领域服务层则需要接收一个模块编码,并生成对应模块的单据号,比如订货单据号,盘点单据号,退货单据号等。

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 模块枚举
 */
@Getter
@AllArgsConstructor
public enum ModuleEnum {
    ORDER("order", "订货","DH"),
    CHECK("check", "盘点","PD"),
    REFUND("refund", "退货","TH");


    /**
     * 编码
     */
    private final String code;

    /**
     * 描述
     */
    private final String desc;

    /**
     * 单据前缀
     */
    private final String prefix;
}

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * id生成器-领域层接口实现类
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class DocsDayIdGeneratorDomainServiceImpl implements DocsDayIdGeneratorDomainService {
   public final DocsDayIdGeneratorRepository docsDayIdGeneratorRepository;

   @Override
   public String createDocsCode(ModuleEnum moduleEnum) {
      Long code = docsDayIdGeneratorRepository.createDocsSequence(new Date());
      /*
         单据号规则:前两位是模块编码,接下来六位是日期,后四位是自增id,不足4位,则在前面补零
       */
      return moduleEnum.getPrefix() + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + String.format("%04d", code);
   }
}

定时任务每天凌晨重置

市面上定时任务的产品非常多,出于统一性,我这边的团队用的是阿里的Schedulex2.0。只要阿里有相关的产品,都是优先选择阿里体系的。

定时任务处理器

import com.alibaba.schedulerx.worker.domain.JobContext;
import com.alibaba.schedulerx.worker.processor.JavaProcessor;
import com.alibaba.schedulerx.worker.processor.ProcessResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 重置单据号自增id,每天从1开始
 */

@RequiredArgsConstructor
@Component
@Slf4j
public class DocsIdResetJobProcessor extends JavaProcessor {
    private final DocsDayIdGeneratorDomainService docsDayIdGeneratorDomainService;

    @Override
    public ProcessResult process(JobContext context) {
        try {
            docsDayIdGeneratorDomainService.resetdDocsSequence();
            return new ProcessResult(true);
        }
        catch (Exception e){
            return new ProcessResult(false,e.getMessage());
        }
    }
}

resetdDocsSequence方法的实现超级简单,就是一个truncate一下table即可。

 <update id="resetdDocsSequence">
        truncate table docs_day_id_generator;
 </update>

定时任务的cron表达式如下:

0 0 0 * * ?

至于阿里Schedulex2.0界面上相关的配置,这个在网上很容易找到,这里就不赘述了。

总结


选择符合当前自己团队实际情况的技术即可,不一定要高大上的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值