我们最近在产品中添加了一个类似于银行账户的功能。 在开发过程中,我们遇到了一些textbook的问题,我认为这是一个很好的机会,可以在 Django 模型中演练一些模式。 这篇文章是按照我们通常处理新问题的顺序写的:
- 定义业务需求
- 写一个初步的实现并定义模型
- 挑战解决方案
- 不断改进
银行账户的业务需求
- 每个用户只能有一个账户,或没有
- 用户可以存款并从账户中提取一定数量的钱
- 帐户余额不能是负数
- 用户的账户余额有一个最大额度
- 所有账户的总余额不能超过一定数量
- 账户上的每一个操作都必须有记录
- 用户可以从移动应用或web界面的个人管理页面执行操作
开始定义模型
帐户模型
# models.py
import uuid
from django.conf import settings
from django.db import models
class Account(models.Model):
class Meta:
verbose_name = 'Account'
verbose_name_plural = 'Accounts'
MAX_TOTAL_BALANCES = 10000000
MAX_BALANCE = 10000
MIN_BALANCE = 0
MAX_DEPOSIT = 1000
MIN_DEPOSIT = 1
MAX_WITHDRAW = 1000
MIN_WITHDRAW = 1
id = models.AutoField(
primary_key=True,
)
uid = models.UUIDField(
unique=True,
editable=False,
default=uuid.uuid4,
verbose_name='Public identifier',
)
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
)
created = models.DateTimeField(
blank=True,
)
modified = models.DateTimeField(
blank=True,
)
balance = models.PositiveIntegerField(
verbose_name='Current balance',
)
让我们来分析一下:
- 我们使用两个唯一的标识符:一个私有标识符,它是一个自动生成的数字(id),和一个公共 id (uid)。 保持自增主键的隐私是一个好主意,它们会暴露我们数据的重要信息,比如我们有多少账户,我们不希望这样。
- 我们为用户使用 OneToOneField。 这就像一个 ForeignKey,但是有一个唯一的约束。 这确保了用户不能有一个以上帐户
- 我们设置 on_delete=models.PROTECT。 从Django 2.0开始,这个参数将是强制性的。 默认值为CASCADE,当用户被删除时,相关的帐户也会被删除。在我们的情况下,这样做没有意义,想象一下当你关闭一个账户时,银行会"删除你的钱"。 设置成 on_delete=models.PROTECT时,当试图删除一个关联user时时,会引发 IntegrityError。
帐户操作模型
现在我们有了一个帐户模型,我们可以创建一个模型来记录对帐户的操作:
# models.py
class Action(models.Model):
class Meta:
verbose_name = 'Account Action'
verbose_name_plural = 'Account Actions'
ACTION_TYPE_CREATED = 'CREATED'
ACTION_TYPE_DEPOSITED = 'DEPOSITED'
ACTION_TYPE_WITHDRAWN = 'WITHDRAWN'
ACTION_TYPE_CHOICES = (
(ACTION_TYPE_CREATED, 'Created'),
(ACTION_TYPE_DEPOSITED, 'Deposited'),
(ACTION_TYPE_WITHDRAWN, 'Withdrawn'),
)
REFERENCE_TYPE_BANK_TRANSFER = 'BANK_TRANSFER'
REFERENCE_TYPE_CHECK = 'CHECK'
REFERENCE_TYPE_CASH = 'CASH'
REFERENCE_TYPE_NONE = 'NONE'
REFERENCE_TYPE_CHOICES = (
(REFERENCE_TYPE_BANK_TRANSFER, 'Bank Transfer'),
(REFERENCE_TYPE_CHECK, 'Check'),
(REFERENCE_TYPE_CASH, 'Cash'),
(REFERENCE_TYPE_NONE, 'None'),
)
id = models.AutoField(
primary_key=True,
)
user_friendly_id = models.CharField(
unique=True,
editable=False,
max_length=30,
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
help_text=’User who performed the action.’,
)
created = models.DateTimeField(
blank=True,
)
account = models.ForeignKey(
Account,
)
type = models.CharField(
max_length=30,
choices=ACTION_TYPE_CHOICES,
)
delta = models.IntegerField(
help_text='Balance delta.',
)
reference = models.TextField(
blank=True,
)
reference_type = models.CharField(
max_length=30,
choices=REFERENCE_TYPE_CHOICES,
default=REFERENCE_TYPE_NONE,
)
comment = models.TextField(
blank=True,
)
# Fields used solely for debugging purposes.
debug_balance = models.IntegerField(
help_text='Balance after the action.',
)
在这儿插入另一篇文章,关于微信红包设计的