【Java】单号创建服务

 

需求:ERP项目存在若干个业务功能,每个业务的单子的单号创建规则需要被统一规划

1、每个业务有自己对应的标识

2、业务单号必须以英文字母为前缀,长度在2 - 4个字符

3、单号的组成 = [ 前缀 ] + [ 日期单位(8) ] + [ 当前序列 ]

4、日期单位可以灵活设置,按年,月,日为单位,即中间的8位精确到什么日期单位

5、结尾对【当前序列】自增后进行补位填充,拼接后就是新的单号

 

设计的表结构:

1、业务标识 + 前缀 + 日期单位(年,月,日) 组合为唯一键

2、表的维护全靠建单逻辑执行(只有新增和更新操作)

CREATE TABLE `sys_co_servcode` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '系统编码 主键,初始值为“100001”',
  `sc_serv_ident` varchar(32) NOT NULL COMMENT '业务标识 参见业务标识对照表<类:ServiceIdentEnum>',
  `sc_prifix` varchar(4) NOT NULL COMMENT '编码前缀 默认用两位,最长四位;',
  `sc_year` char(4) NOT NULL COMMENT '所属年',
  `sc_month` char(2) NOT NULL COMMENT '所属月',
  `sc_day` char(2) NOT NULL COMMENT '所属日',
  `sc_num` int(11) NOT NULL COMMENT '当前编号',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100012 DEFAULT CHARSET=utf8 COMMENT='业务编码表';

  

设计的接口服务:

1、接口设定了单号长度设置,需要校验单号长度是否不足

2、日期根据字段信息,需要自己计算年月日,所以这里弄成枚举类型作为入参

package cn.hyite.amerp.system.common.servcode.service;

import cn.hyite.amerp.system.common.servcode.dto.DateUnit;

/**
 * sys_co_servcode 业务编码表 服务类
 *
 * @author OnCloud9
 * @version 1.0
 * @project server
 * @date 2022-12-15
 */
public interface ISysCoServcodeService {

    /**
     * 根据 [业务标识] + [前缀定义] + [日期精度] + [单号总长度] 创建最新的单号
     * @param servIdent 业务标识
     * @param codePrefix 编号前缀定义(2 ~ 4)字符长度
     * @param dateUnit 日期单位 [year, month, day]
     * @param length 单号总长度
     * @return String 最新业务编号
     * @author OnCloud9
     * @date 2022/12/15 14:08
     *
     */
    String createServiceCode(String servIdent, String codePrefix, DateUnit dateUnit, Integer length);
}

 

考虑到单号可变长度,每个日期枚举对应设置了长度限制,方便接口实现做长度计算校验

package cn.hyite.amerp.system.common.servcode.dto;

import lombok.Getter;

/**
 * 日期单位枚举类
 *
 * @author OnCloud9
 * @version 1.0
 * @project server
 * @date 2022年12月15日 14:36
 */
@Getter
public enum DateUnit {
    YEAR(4),
    MONTH(6),
    DAY(8)
    ;
    public static final int TOTAL_LENGTH = 8;
    private final int length;
    DateUnit(int length) {
        this.length = length;
    }
}

  

接口实现:

1、一个新的业务建单规则没有使用时,调用后记录规则,返回单号

2、再次调用时,根据记录获取最新单号,更新记录的单号后,返回单号

package cn.hyite.amerp.system.common.servcode.service.impl;

import cn.hyite.amerp.common.ResultMessage;
import cn.hyite.amerp.common.service.BaseService;
import cn.hyite.amerp.common.util.Assert;
import cn.hyite.amerp.system.common.servcode.dao.SysCoServcodeDAO;
import cn.hyite.amerp.system.common.servcode.dto.DateUnit;
import cn.hyite.amerp.system.common.servcode.dto.SysCoServcodeDTO;
import cn.hyite.amerp.system.common.servcode.service.ISysCoServcodeService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.text.NumberFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;

/**
 * sys_co_servcode 业务编码表 服务实现类
 *
 * @project server
 * @author OnCloud9
 * @date 2022-12-15
 * @version 1.0
 */
@Service("sysCoServcodeService")
public class SysCoServcodeServiceImpl extends BaseService<SysCoServcodeDAO, SysCoServcodeDTO> implements ISysCoServcodeService {

    private static final Integer PREFIX_LIMIT = 4;
    private static final String SPACER = "00";
    private static final String FIELD_YEAR = "sc_year";
    private static final String FIELD_MONTH = "sc_month";
    private static final String FIELD_DAY = "sc_day";

    @Override
    public String createServiceCode(String servIdent, String codePrefix, DateUnit dateUnit, Integer length) {
        /* Step1 校验入参合法性 */
        Assert.isTrue(StringUtils.isBlank(servIdent), ResultMessage.NULL_ERROR, "业务标识");
        Assert.isTrue(StringUtils.isBlank(codePrefix), ResultMessage.NULL_ERROR, "业务前缀");
        final int prefixLen = codePrefix.length();
        Assert.isTrue(prefixLen > PREFIX_LIMIT, ResultMessage.CUSTOM_ERROR, "业务前缀长度超出限制!" + prefixLen);
        final int dateUnitLength = DateUnit.TOTAL_LENGTH;
        Assert.isTrue(prefixLen + dateUnitLength + 1 > length, ResultMessage.CUSTOM_ERROR, "单号总长溢出!");

        /* Step2 组建查询条件, 封装日期单位条件 */
        QueryWrapper<SysCoServcodeDTO> condition = Wrappers.query();
        condition.eq("sc_serv_ident", servIdent);
        condition.eq("sc_prifix", codePrefix);

        LocalDateTime nowTime = LocalDateTime.now();
        String year = String.valueOf(nowTime.getYear());
        String month = String.valueOf(nowTime.getMonth().getValue());
        String day = String.valueOf(nowTime.getDayOfMonth());
        switch (dateUnit) {
            case YEAR:
                condition.eq(FIELD_YEAR, year);
                condition.eq(FIELD_MONTH, SPACER);
                condition.eq(FIELD_DAY, SPACER);
                break;
            case MONTH:
                condition.eq(FIELD_YEAR, year);
                condition.eq(FIELD_MONTH, month);
                condition.eq(FIELD_DAY, SPACER);
                break;
            case DAY:
                condition.eq(FIELD_YEAR, year);
                condition.eq(FIELD_MONTH, month);
                condition.eq(FIELD_DAY, day);
                break;
        }

        /* Step3 查询是否存在此规则的建单记录 */
        SysCoServcodeDTO serviceCode = baseMapper.selectOne(condition);
        boolean isEmpty = Objects.isNull(serviceCode);

        if (isEmpty) {
            /* 二次校验总长度 */
            int totalLength = prefixLen + dateUnitLength + 1;
            Assert.isTrue(totalLength > length, ResultMessage.CUSTOM_ERROR, "单号总长溢出!(新建时)");

            /* 新建记录,新记录时,设置新的日期单位 */
            int newNum = 1;
            String dateCode = "";
            SysCoServcodeDTO newCode = new SysCoServcodeDTO();
            newCode.setScServIdent(servIdent);
            newCode.setScPrifix(codePrefix);
            newCode.setCreateTime(new Date());
            newCode.setScNum(newNum);
            newCode.setScYear(year);
            switch (dateUnit) {
                case YEAR:
                    newCode.setScMonth(SPACER);
                    newCode.setScDay(SPACER);
                    dateCode += (year + SPACER + SPACER);
                    break;
                case MONTH:
                    newCode.setScMonth(month);
                    newCode.setScDay(SPACER);
                    dateCode += (year + month + SPACER);
                    break;
                case DAY:
                    newCode.setScMonth(month);
                    newCode.setScDay(day);
                    dateCode += (year + month + day);
                    break;
            }
            baseMapper.insert(newCode);

            int remainLen = length - (prefixLen + dateUnitLength);

            /* 返回单号 */
            return codePrefix + dateCode + formatNumDigit(remainLen, newNum);
        } else {
            /* 取当前单号 */
            Integer currentNum = serviceCode.getScNum();
            ++ currentNum;

            /* 更新最新单号 */
            serviceCode.setScNum(currentNum);
            baseMapper.updateById(serviceCode);

            String numStr = String.valueOf(currentNum);
            final int numLength = numStr.length();

            /* 二次校验总长度 */
            int totalLength = prefixLen + dateUnitLength + numLength;
            Assert.isTrue(totalLength > length, ResultMessage.CUSTOM_ERROR, "单号总长溢出!(更新时)");

            int remainLen = length - (prefixLen + dateUnitLength);
            /* 返回单号 */
            return codePrefix + serviceCode.getScYear() + serviceCode.getScMonth() + serviceCode.getScDay() + formatNumDigit(remainLen, currentNum);
        }
    }

    /**
     * 计数值补位
     * @param remainLen 剩余长度
     * @param serial 序列计数值
     * @return java.lang.String 补位后的序列值
     * @author OnCloud9
     * @date 2022/12/15 16:49
     *
     */
    private String formatNumDigit(int remainLen, int serial) {
        NumberFormat formatter = NumberFormat.getNumberInstance();
        formatter.setMinimumIntegerDigits(remainLen);
        formatter.setGroupingUsed(false);
        return formatter.format(serial);
    }

}

  

2023年01月10日 更新

业务调整,追加了如下几个需求:

- 年月日 不再占位, 年4位,月6位(年 + 月),日8位 (年 + 月 + 日)
- 支持空项,不填写年月日,
- 年可以支持2位
- 业务标识前缀支持1 - 4位

  

原来的日期单位枚举类需要扩展枚举项:

package cn.hyite.amerp.system.common.servcode.dto;

import lombok.Getter;

/**
 * 日期单位枚举类
 *
 * @author OnCloud9
 * @version 1.0
 * @project amerp-server
 * @date 2022年12月15日 14:36
 */
@Getter
public enum DateUnit {
    EMPTY(0), /* 空日期,不占用 */

    YEAR_DIGIT2(2), /* 2位年份 */
    MONTH_DIGIT2(4), /* 2位年份 + 2位月份  */
    DAY_DIGIT2(6), /* 2位年份 + 2位月份 + 2日期  */

    YEAR(4), /* 4位完整年份 */
    MONTH(6), /* 4位完整年份 + 2位月份  */
    DAY(8), /* 4位完整年份 + 2位月份 + 2日期 */
    ;

    public static final int TOTAL_LENGTH = 8;

    private final int length;

    DateUnit(int length) {
        this.length = length;
    }
}

  

同样,需要对应这些新的枚举项追加扩展支持:

package cn.hyite.amerp.system.common.servcode.service.impl;

import cn.hyite.amerp.common.ResultMessage;
import cn.hyite.amerp.common.service.BaseService;
import cn.hyite.amerp.common.util.Assert;
import cn.hyite.amerp.system.common.servcode.dao.SysCoServcodeDAO;
import cn.hyite.amerp.system.common.servcode.dto.DateUnit;
import cn.hyite.amerp.system.common.servcode.dto.SysCoServcodeDTO;
import cn.hyite.amerp.system.common.servcode.service.ISysCoServcodeService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.text.NumberFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;

/**
 * sys_co_servcode 业务编码表 服务实现类
 *
 * @project amerp-server
 * @author OnCloud9
 * @date 2022-12-15
 * @version 1.0
 */
@Service("sysCoServcodeService")
public class SysCoServcodeServiceImpl extends BaseService<SysCoServcodeDAO, SysCoServcodeDTO> implements ISysCoServcodeService {

    private static final Integer PREFIX_LIMIT = 4;
    private static final String SPACER = "00";
    private static final String FIELD_YEAR = "sc_year";
    private static final String FIELD_MONTH = "sc_month";
    private static final String FIELD_DAY = "sc_day";

    @Override
    public String createServiceCode(String servIdent, String codePrefix, DateUnit dateUnit, Integer length, int initialNum) {
        /* Step1 校验入参合法性 */
        Assert.isTrue(StringUtils.isBlank(servIdent), ResultMessage.NULL_ERROR, "业务标识");
        Assert.isTrue(StringUtils.isBlank(codePrefix), ResultMessage.NULL_ERROR, "业务前缀");
        final int prefixLen = codePrefix.length();
        Assert.isTrue(prefixLen > PREFIX_LIMIT, ResultMessage.CUSTOM_ERROR, "业务前缀长度超出限制!限制:" + prefixLen);
        final int requireLength = prefixLen + dateUnit.getLength() + 2;
        Assert.isTrue(requireLength > length, ResultMessage.CUSTOM_ERROR, "单号总长不足! 需要:" + requireLength + ", 提供:" + length);

        /* Step2 组建查询条件, 封装日期单位条件 */
        QueryWrapper<SysCoServcodeDTO> condition = buildQueryWrapper(servIdent, codePrefix, dateUnit);

        /* Step3 查询是否存在此规则的建单记录 */
        SysCoServcodeDTO serviceCode = baseMapper.selectOne(condition);
        boolean isEmpty = Objects.isNull(serviceCode);

        if (isEmpty) {
            /* 新建记录,新记录时,设置新的日期单位 */
            SysCoServcodeDTO dto = buildNewServCodeDTO(servIdent, codePrefix, dateUnit, initialNum);
            baseMapper.insert(dto);
            /* 返回单号 */
            int remainLen = length - (prefixLen + dateUnit.getLength());
            return codePrefix + dto.getDateCode() + formatNumDigit(remainLen, dto.getScNum());
        } else {
            return doServCodeUpdate(serviceCode, dateUnit, length);
        }
    }

    /**
     * 计数值补位
     * @param remainLen 剩余长度
     * @param serial 序列计数值
     * @return java.lang.String 补位后的序列值
     * @author OnCloud9
     * @date 2022/12/15 16:49
     *
     */
    private String formatNumDigit(int remainLen, int serial) {
        NumberFormat formatter = NumberFormat.getNumberInstance();
        formatter.setMinimumIntegerDigits(remainLen);
        formatter.setGroupingUsed(false);
        return formatter.format(serial);
    }

    /**
     * 构建查询条件
     * @param servIdent 业务标识
     * @param codePrefix 单号前缀
     * @param dateUnit 日期单位枚举
     * @return QueryWrapper<SysCoServcodeDTO> 查询条件
     * @author OnCloud9
     * @date 2023/1/9 16:01
     *
     */
    private QueryWrapper<SysCoServcodeDTO> buildQueryWrapper(String servIdent, String codePrefix, DateUnit dateUnit) {
        QueryWrapper<SysCoServcodeDTO> condition = Wrappers.query();
        condition.eq("sc_serv_ident", servIdent);
        condition.eq("sc_prifix", codePrefix);

        final LocalDateTime nowTime = LocalDateTime.now();
        final String year = String.valueOf(nowTime.getYear());
        final String yearDigit2 = year.substring(2);
        final String month = formatNumDigit(2, nowTime.getMonth().getValue());
        final String day = formatNumDigit(2, nowTime.getDayOfMonth());

        switch (dateUnit) {
            /* 空选项 */
            case EMPTY:
                condition.eq(FIELD_YEAR, SPACER).eq(FIELD_MONTH, SPACER).eq(FIELD_DAY, SPACER);
                break;
            /* 完整年月日选项 */
            case YEAR:
                condition.eq(FIELD_YEAR, year).eq(FIELD_MONTH, SPACER).eq(FIELD_DAY, SPACER);
                break;
            case MONTH:
                condition.eq(FIELD_YEAR, year).eq(FIELD_MONTH, month).eq(FIELD_DAY, SPACER);
                break;
            case DAY:
                condition.eq(FIELD_YEAR, year).eq(FIELD_MONTH, month).eq(FIELD_DAY, day);
                break;
            /* 2数位年月日选项 */
            case YEAR_DIGIT2:
                condition.eq(FIELD_YEAR, yearDigit2).eq(FIELD_MONTH, SPACER).eq(FIELD_DAY, SPACER);
                break;
            case MONTH_DIGIT2:
                condition.eq(FIELD_YEAR, yearDigit2).eq(FIELD_MONTH, month).eq(FIELD_DAY, SPACER);
                break;
            case DAY_DIGIT2:
                condition.eq(FIELD_YEAR, yearDigit2).eq(FIELD_MONTH, month).eq(FIELD_DAY, day);
                break;
        }

        return condition;
    }


    /**
     * 构建一个新的单号规则记录
     * @param servIdent 业务标识
     * @param codePrefix 单号前缀
     * @param dateUnit 日期单位枚举
     * @return cn.hyite.amerp.system.common.servcode.dto.SysCoServcodeDTO
     * @author OnCloud9
     * @date 2023/1/9 16:37
     *
     */
    private SysCoServcodeDTO buildNewServCodeDTO(String servIdent, String codePrefix, DateUnit dateUnit, int initialNum) {
        final LocalDateTime nowTime = LocalDateTime.now();
        final String year = String.valueOf(nowTime.getYear());
        final String yearDigit2 = year.substring(2);
        final String month = formatNumDigit(2, nowTime.getMonth().getValue());
        final String day = formatNumDigit(2, nowTime.getDayOfMonth());

        String dateCode = null;
        SysCoServcodeDTO newCode = new SysCoServcodeDTO();
        newCode.setScServIdent(servIdent);
        newCode.setScPrifix(codePrefix);
        newCode.setCreateTime(new Date());
        newCode.setScNum(initialNum);

        switch (dateUnit) {
            case EMPTY: /* 空选项 日期部分不填充 */
                newCode.setScYear(SPACER).setScMonth(SPACER).setScDay(SPACER);
                dateCode = "";
                break;
            case YEAR: /* 完整年份 日期部分 2023 */
                newCode.setScYear(year).setScMonth(SPACER).setScDay(SPACER);
                dateCode = year;
                break;
            case MONTH: /* 完整月份 日期部分 202301 */
                newCode.setScYear(year).setScMonth(month).setScDay(SPACER);
                dateCode = year + month;
                break;
            case DAY: /* 完整日期 日期部分 20230109 */
                newCode.setScYear(year).setScMonth(month).setScDay(day);
                dateCode = year + month + day;
                break;
            case YEAR_DIGIT2: /* 位数2年份 日期部分 23 */
                newCode.setScYear(yearDigit2).setScMonth(SPACER).setScDay(SPACER);
                dateCode = yearDigit2;
                break;
            case MONTH_DIGIT2: /* 位数2月份 日期部分 2301 */
                newCode.setScYear(yearDigit2).setScMonth(month).setScDay(SPACER);
                dateCode = yearDigit2 + month;
                break;
            case DAY_DIGIT2: /* 位数2日期 日期部分 230109 */
                newCode.setScYear(yearDigit2).setScMonth(month).setScDay(day);
                dateCode = yearDigit2 + month + day;
                break;
        }
        newCode.setDateCode(dateCode);
        return newCode;
    }

    /**
     * 更新单号记录操作
     * @param serviceCode 单号记录实体
     * @param dateUnit 日期枚举
     * @param length 总长度
     * @return java.lang.String 单号
     * @author OnCloud9
     * @date 2023/1/9 16:44
     *
     */
    private String doServCodeUpdate(SysCoServcodeDTO serviceCode, DateUnit dateUnit, int length) {
        int prefixLen = serviceCode.getScPrifix().length();

        /* 取当前单号 */
        Integer currentNum = serviceCode.getScNum();
        ++ currentNum;

        /* 取单号位数值 */
        int numDigits = String.valueOf(currentNum).length();

        /* 二次校验总长度 */
        int estimateLength = prefixLen + dateUnit.getLength() + numDigits;
        Assert.isTrue(estimateLength > length, ResultMessage.CUSTOM_ERROR, "单号总长溢出!(更新时) 需要:" + estimateLength + ", 提供:" + length);

        /* 更新最新单号 */
        serviceCode.setScNum(currentNum);
        baseMapper.updateById(serviceCode);

        /* 返回单号 */
        int remainLen = length - (prefixLen + dateUnit.getLength());
        String dateCode = null;
        switch (dateUnit) {
            case EMPTY:
                dateCode = "";
                break;
            case YEAR:
            case YEAR_DIGIT2:
                dateCode = serviceCode.getScYear();
                break;
            case MONTH:
            case MONTH_DIGIT2:
                dateCode = serviceCode.getScYear() + serviceCode.getScMonth();
                break;
            case DAY:
            case DAY_DIGIT2:
                dateCode = serviceCode.getScYear() + serviceCode.getScMonth() + serviceCode.getScDay();
                break;
        }
        return serviceCode.getScPrifix() + dateCode + formatNumDigit(remainLen, currentNum);
    }

}

  

 

posted @ 2022-12-16 23:34  emdzz  阅读(147)  评论(0编辑  收藏  举报