跳转至

Django订单编号生成

迄今为止在项目中遇到三种订单编号生成需求,分别用于不同的环境。

需求一

订单号规则为前缀+尾缀,前缀可以动态修改,尾缀为3到5位递增数字。这种规则常用于员工、设备等范围较小的编号。

需要借助一个序号生成器来实现这个功能,推荐使用django-sequences。本文并未直接使用django-sequences,而是对其进行改造,代码如下:

models.py

"""
 参考 库django-sequences
 地址:https://github.com/aaugustin/django-sequences
"""
from django.db import models


# 序列器
class Sequence(models.Model):
    # 名称
    name = models.CharField(max_length=255,
                            primary_key=True,
                            verbose_name='名称')
    # 最近值
    last = models.PositiveIntegerField(verbose_name='最近值')
    # 时间
    time = models.DateTimeField(verbose_name='时间')

    class Meta:
        verbose_name = '序列器'
        verbose_name_plural = '序列器'

    def __str__(self):
        return '[{}] name={} last={}'.format(self.time, self.name, self.last)

utils.py:

def get_next_value(sequence_name):
    now = timezone.now()
    sequence, created = Sequence.objects.get_or_create(name=sequence_name,
                                                       defaults={'last': 0, 'time': now})
    # 获取下一个最新值
    sequence.last += 1
    sequence.time = now
    sequence.save()
    return sequence.last


def get_next_no(sequence_name, suffix_format, prefix=None):
    now = timezone.now()
    sequence, created = Sequence.objects.get_or_create(name=sequence_name,
                                                       defaults={'last': 0, 'time': now})

    # 获取下一个最新值
    next_value = sequence.last + 1
    # 尾缀
    if suffix_format == 1:
        suffix = '{:01}'.format(next_value)
    elif suffix_format == 2:
        suffix = '{:02}'.format(next_value)
    elif suffix_format == 3:
        suffix = '{:03}'.format(next_value)
    elif suffix_format == 4:
        suffix = '{:04}'.format(next_value)
    elif suffix_format == 5:
        suffix = '{:05}'.format(next_value)
    else:
        suffix = '{}'.format(next_value)

    # 生成编号
    if prefix is not None:
        no = '{}{}'.format(prefix, suffix)
    else:
        no = suffix
    # 更新序列器
    sequence.last += 1
    sequence.time = now
    sequence.save()
    return no

使用DRF新增时,序号产生方法如下:

constance.py:

STAFF_PREFIX = 'staff_prefix'
STAFF_SUFFIX = 'staff_suffix'
CONSTANCE_CONFIG = OrderedDict([
    # 工号
    (STAFF_PREFIX, ('Staff', '工号固定代码')),
    (STAFF_SUFFIX, (3, '工号尾缀', 'suffix_select')),
])
CONSTANCE_CONFIG_FIELDSETS = OrderedDict([
    ('员工', (STAFF_PREFIX, STAFF_SUFFIX)),
])

models.py

# 员工
class Staff(models.Model):
    # 工号
    no = models.CharField(max_length=255,
                          unique=True,
                          verbose_name='工号')

serializers.py

# 创建员工
class StaffCreateSerializer(ModelSerializer):
    no = None

    def validate(self, data):
        suffix_format = int(get_value(settings.STAFF_SUFFIX))
        self.no = get_next_no('staff', suffix_format, prefix=get_value(settings.STAFF_PREFIX))
        while self.Meta.model.objects.filter(no=self.no).exists():
            self.no = get_next_no('staff', suffix_format, prefix=get_value(settings.STAFF_PREFIX))
        return data

    class Meta:
        model = Staff
        fields = ('job_state', 'bank_no', 'social_security_no', 'on_job')

views.py:

# 员工
class StaffViewSet(ModelViewSet):
    ...

    def perform_create(self, serializer):
        return serializer.save(no=serializer.no)

整体思路为使用序号生成器记录当前生成到的编号,之后每次生成编号时,都让序号生成器继续生成下一个编号,若下一个编号未被使用,则使用该序号作为当前实例的编号。序号生成需要在validate中最后进行,以免发生序号生成成功而validate未通过导致的编号浪费问题。编号生成成功时,在serializer.save()时保存至实例中。

需求二

订单号规则为 前缀+日期+时间+尾缀,前缀要求为可动态修改,日期选项有("年+月+日", "年+月", "年", "忽略"),时间选项有("时+分+秒", "时+分", "时", "忽略"),尾缀为3到5位数字并且每天从1开始回滚。

constance.py:

CONSTANCE_ADDITIONAL_FIELDS = {
    'date_select': ['django.forms.fields.ChoiceField', {
        'widget': 'django.forms.Select',
        'choices': ((0, "年+月+日"), (1, "年+月"), (2, "年"), (3, "忽略"))
    }],
    'time_select': ['django.forms.fields.ChoiceField', {
        'widget': 'django.forms.Select',
        'choices': ((0, "时+分+秒"), (1, "时+分"), (2, "时"), (3, "忽略"))
    }],
    'suffix_select': ['django.forms.fields.ChoiceField', {
        'widget': 'django.forms.Select',
        'choices': ((0, "3位数字"), (1, "4位数字"), (2, "5位数字"))
    }],
}

BALANCE_RECHARGE_FIX_CODE = 'balance_recharge_fix_code'
BALANCE_RECHARGE_DATE_FORMAT = 'balance_recharge_date_format'
BALANCE_RECHARGE_TIME_FORMAT = 'balance_recharge_time_format'
BALANCE_RECHARGE_SUFFIX = 'balance_recharge_time_suffix'

CONSTANCE_CONFIG = OrderedDict([
    # 余额充值
    (BALANCE_RECHARGE_FIX_CODE, ('YE-CZ', BALANCE_RECHARGE_FIX_CODE)),
    (BALANCE_RECHARGE_DATE_FORMAT, (0, BALANCE_RECHARGE_DATE_FORMAT, 'date_select')),
    (BALANCE_RECHARGE_TIME_FORMAT, (0, BALANCE_RECHARGE_TIME_FORMAT, 'time_select')),
    (BALANCE_RECHARGE_SUFFIX, (0, BALANCE_RECHARGE_SUFFIX, 'suffix_select')),
])

CONSTANCE_CONFIG_FIELDSETS = OrderedDict([
    ('财务', (BALANCE_RECHARGE_FIX_CODE, BALANCE_RECHARGE_DATE_FORMAT,
                 BALANCE_RECHARGE_TIME_FORMAT, BALANCE_RECHARGE_SUFFIX)),
])

models.py

from datetime import datetime

# 余额充值
class BalanceRecharge(models.Model):
    # 编号
    no = models.CharField(unique=True,
                          max_length=255,
                          null=True,
                          blank=True,
                          verbose_name=u'编号')
    # 编号尾缀
    no_suffix = models.PositiveIntegerField(default=0,
                                            null=True,
                                            blank=True,
                                            verbose_name=u'编号尾缀')
    def save(self, *args, **kwargs):
        if not self.pk:
            # 根据固定代码-时间格式生成编号前缀
            date_format = int(get_value(settings.BALANCE_RECHARGE_DATE_FORMAT))
            time_format = int(get_value(settings.BALANCE_RECHARGE_TIME_FORMAT))
            formatted_datetime = no_format_datetime(date_format, time_format)
            no = '{}{}'.format(get_value(settings.BALANCE_RECHARGE_FIX_CODE),
                                         formatted_datetime)
            # 生成当天的递增编号尾缀
            first = BalanceRecharge.objects.filter(create_time__day=datetime.now().day).order_by('-no_suffix').first()
            if first:
                current_no_suffix = first.no_suffix if first.no_suffix else 0
            else:
                current_no_suffix = 0
            self.no_suffix = current_no_suffix + 1
            # 根据前缀和后缀数字显示个数生成编号
            suffix_format = int(get_value(settings.BALANCE_RECHARGE_SUFFIX))
            no_suffix = no_format_suffix(self.no_suffix, suffix_format)
            self.no = '{}{}'.format(self.no, no_suffix)
        return super(BalanceRecharge, self).save(*args, **kwargs)

utils.py:

def no_format_datetime(date_format, time_format):
    """
    :param date_format: 0 年月日 1 年月 2 年
    :param time_format: 0 时分秒 2 时分 3 时
    :return: 对应时间格式的编号片段
    """
    now = datetime.now()
    # 日期
    if date_format == 0:
        time_data = now.strftime('%Y%m%d')
    elif date_format == 1:
        time_data = now.strftime('%Y%m')
    elif date_format == 2:
        time_data = now.strftime('%Y')
    else:
        time_data = ''
    # 时间
    if time_format == 0:
        time_time = now.strftime('%H%M%S')
    elif time_format == 1:
        time_time = now.strftime('%H%M')
    elif time_format == 2:
        time_time = now.strftime('%H')
    else:
        time_time = ''
    return '{}{}'.format(time_data, time_time)

def no_format_suffix(no_suffix, suffix_format):
    """
    :param no_suffix: 原编号尾缀
    :param suffix_format: 尾缀格式 0-3位数字 1-4位数字 2-5位数字
    :return: 对应格式的编号尾缀
    """
    if suffix_format == 0:
        no_suffix = '{:03}'.format(no_suffix)
    elif suffix_format == 1:
        no_suffix = '{:04}'.format(no_suffix)
    elif suffix_format == 2:
        no_suffix = '{:05}'.format(no_suffix)
    else:
        no_suffix = '{:04}'.format(no_suffix)
    return no_suffix

整体思路为设置尾缀为int类型,用于记录当前生成的为当天第多少个订单。在实例被创建时,先获取前缀和时间格式进行拼接,再查询当天生成的尾缀最大值,将尾缀加一作为本实例的尾缀。再格式化尾缀到相应的格式,用前缀+日期时间+尾缀即可得到当前订单号。

需求三

订单号规则为 前缀+日期时间+尾缀,前缀要求为可动态修改,日期时间选项有("年", "月", "日", "时", "分", "秒"),尾缀为3到5位数字,尾缀根据日期时间选项的精度来判断时候回滚。如日期时间选择了日,则尾缀每天从1回滚;若日期时间选择了分,则尾缀每分钟从1回滚,若当前分钟尾缀生成至5,下一分钟尾缀依旧重新从1开始计算。

constances.py

CONSTANCE_ADDITIONAL_FIELDS = {
    'datetime_select': ['django.forms.fields.ChoiceField', {
        'widget': 'django.forms.Select',
        'choices': ((0, "年"), (1, "月"), (2, "日"), (3, "时"), (4, "分"), (5, "秒"))
    }],
    'suffix_select': ['django.forms.fields.ChoiceField', {
        'widget': 'django.forms.Select',
        'choices': ((3, "3位数字"), (4, "4位数字"), (5, "5位数字"))
    }],
}

ALLOT_PREFIX = 'allot_prefix'
ALLOT_DATETIME = 'allot_datetime'
ALLOT_SUFFIX = 'allot_suffix'

CONSTANCE_CONFIG = OrderedDict([
    # 调拨管理
    (ALLOT_PREFIX, ('Allot', '调拨管理固定代码')),
    (ALLOT_DATETIME, (4, '调拨管理时间', 'datetime_select')),
    (ALLOT_SUFFIX, (3, '调拨管理尾缀', 'suffix_select')),
])
CONSTANCE_CONFIG_FIELDSETS = OrderedDict([
    ('调拨申请', (ALLOT_APPLY_PREFIX, ALLOT_APPLY_DATETIME, ALLOT_APPLY_SUFFIX)),
])

models.py

# 调拨申请
class AllotApply(models.Model):
    # 业务单号
    no = models.CharField(max_length=255,
                          unique=True,
                          verbose_name='业务单号')

serializers.py

# 创建调拨申请
class AllotApplyCreateSerializer(ModelSerializer):
    no = None

    def validate(self, data):
        prefix = get_value(settings.ALLOT_APPLY_PREFIX)
        datetime_format = int(get_value(settings.ALLOT_APPLY_DATETIME))
        suffix_format = int(get_value(settings.ALLOT_APPLY_SUFFIX))
        self.no = get_next_no('allot_apply', suffix_format, prefix=prefix, datetime_format=datetime_format)
        return data

    class Meta:
        model = AllotApply

views.py:

# 调拨申请
class AllotApplyViewSet(ModelViewSet):
    ...
    # 执行新增
    def perform_create(self, serializer):
        return serializer.save(no=serializer.no)

utils.py

def get_next_value(sequence_name):
    now = timezone.now()
    sequence, created = Sequence.objects.get_or_create(name=sequence_name,
                                                       defaults={'last': 0, 'time': now})
    # 获取下一个最新值
    sequence.last += 1
    sequence.time = now
    sequence.save()
    return sequence.last


def get_next_no(sequence_name, suffix_format, prefix=None, datetime_format=None):
    now = timezone.now()
    new_period = False
    datetime_str = None
    sequence, created = Sequence.objects.get_or_create(name=sequence_name,
                                                       defaults={'last': 0, 'time': now})
    if datetime_format is not None:
        # 是否在同一时间周期
        if datetime_format == 0:
            datetime_str = now.strftime('%Y')
            if now.year != sequence.time.year:
                new_period = True
        elif datetime_format == 1:
            datetime_str = now.strftime('%Y%m')
            if (now.year != sequence.time.year) or (now.month != sequence.time.month):
                new_period = True
        elif datetime_format == 2:
            datetime_str = now.strftime('%Y%m%d')
            if now.date() != sequence.time.date():
                new_period = True
        elif datetime_format == 3:
            datetime_str = now.strftime('%Y%m%d%H')
            if (now.date() != sequence.time.date()) or (now.hour != sequence.time.hour):
                new_period = True
        elif datetime_format == 4:
            datetime_str = now.strftime('%Y%m%d%H%M')
            if (now.date() != sequence.time.date()) or (now.hour != sequence.time.hour) or \
                    (now.minute != sequence.time.minute):
                new_period = True
        elif datetime_format == 5:
            datetime_str = now.strftime('%Y%m%d%H%M%S')
            if (now.date() != sequence.time.date()) or (now.hour != sequence.time.hour) or \
                    (now.minute != sequence.time.minute) or (now.second != sequence.time.second):
                new_period = True

    # 获取下一个最新值
    next_value = 1 if new_period else sequence.last + 1
    # 尾缀
    if suffix_format == 1:
        suffix = '{:01}'.format(next_value)
    elif suffix_format == 2:
        suffix = '{:02}'.format(next_value)
    elif suffix_format == 3:
        suffix = '{:03}'.format(next_value)
    elif suffix_format == 4:
        suffix = '{:04}'.format(next_value)
    elif suffix_format == 5:
        suffix = '{:05}'.format(next_value)
    else:
        suffix = '{}'.format(next_value)

    # 生成编号
    if prefix is not None:
        if datetime_format is not None:
            no = '{}{}{}'.format(prefix, datetime_str, suffix)
        else:
            no = '{}{}'.format(prefix, suffix)
    else:
        if datetime_format is not None:
            no = '{}{}'.format(datetime_str, suffix)
        else:
            no = suffix
    # 更新序列器
    if new_period:
        sequence.last = 1
    else:
        sequence.last += 1
    sequence.time = now
    sequence.save()
    return no

整体思路同需求一,对get_next_no方法进行了修改,使其能够判断当前时间是否进入了下一轮的尾缀循环。