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
方法进行了修改,使其能够判断当前时间是否进入了下一轮的尾缀循环。