• Django中涉及金融的项目


    Django中涉及金融的项目

     

    在Django中,如果一个项目涉及了金融,他的要求是十分严格的。

    所以嘞,这里就有一些坑,很多坑,第一次开发的时候很容易出现一系列的错误

    在涉及金融计算的地方,不能使用float类型

    什么鬼,但事实就是如此,千万不要用float进行计算.....

    所以,Python中为我们提供了一个专门的模块计算,decimal

     而同样的,Django中也提供了相应的计算字段,DecimalField

    class DecimalField(max_digits=None, decimal_places=None[, **options])
    • max_digits:表示最大位数
    • decimal_places:表示小数点后面的位数
    • 假设你最多存999人民币,小数点要精确到2位,需要的max_digits就为6,decimal_places就为2

    下面是一些例子

     1.在models.py中定义:

    from django.db import models
    
    class UserProfile(models.Model):
        price = models.DecimalField(max_digits=16,decimal_places=2)

    2.添加一条记录:

    from decimal import Decimal
    
    obj = models.UserProfile.objects.create(price = Decimal("123.45"))

    3.更新记录:

    obj.price -= Decimal("1.00")
    
    obj.save()

    4.我们输出查看一下SQL语句:

    from django.db import connection
    
    print(connection.queries[-1])
    
    //UPDATE `table` SET `price` = '122.45' WHERE `id` = 1

    我们之前使用的update会有问题。在并发很高的时候,会遇到类似多线程的问题,因为加减操作都在客户端,某个线程写入price的时候,可能之前拿到的已经被别人更新过了,所以我们需要原子写入。

    UPDATE `table` SET `price` = 'price' - '1.00' WHERE `id` = 1

    在ORM及是使用F()

    obj.price = F("price") - Decimal("1.00")
    
    obj.save(update_fields = ["price"])

    注意:在调用save()方法的时候,我们可以用update_fields传入需要update的字段。否则Django可能会把所有的字段都放在SQL中,影响效率


    好吧,上面看似已经是把问题都解决了,但是只是看似。

    我们在数据库中,定义的字段的精确到小数点后两位,如果我减去一个3位的小数会怎样呢?

    obj.price = F("price") - Decimal("1.001")
    
    obj.save(update_field=["price"])

    好吧,结果不出意料的报错了

    会抛出一个Traceback的错误

    这个错误其实是MySQL抛出的一个异常,所以传递到了Django,使更新操作无法成功

    怎么解决呢?

    复制代码
    //我们手动写个方法进行一个精度的转换
    
    def to_decimal(s,precision=2):
        
        r = pow(10,precision+1)
        v = s if type(s) is Decimal else Decimal(str(s))
    
        try:
            return Decimal(round(int(v * r),-1))/r
        except:
            return Decimal(s)
    
    obj.price = F("price") - to_decimal("1.001",2)
    obj.save(update_fields=["price"])
    复制代码

     这样貌似有解决了一个问题。。。实际呢?

    这只是加或者减,如果是乘除呢?

    由于这是MySQL层次上出的错误,也就是说实在最后存储的上面出的错,我们是没有办法在Django的层面上对计算出来的结果在进行一次类型转换的。这时候怎么办,只有上raw sql了。

    复制代码
    //完整一点
    //这次带上事务
    
    from django.db import transaction,connection
    
    try:
        with transaction.atomic():
            cursor = connection.cursor()
            ret = cursor.execute(
                "UPDATE 'table' SET 'price'=CAST(('price'*%s) AS DECIMAL(16,2)) WHERE 'id' = %s ",
                [Decimal("1.001"),obj.id]
        )
    except:
        print("失败")
    复制代码

     注意:在MySQL层面上,我们使用CAST(%s AS DECIMAL(16,2))来把结果转化为price字段同样个是Decimal类型

    返回更新数据的行数,如果成功了,ret就是1


    你以为结束了?

     太天真了

    注意:如果是事务操作,一定要考虑到多线程并发的造成的数据冲突的问题

    即:假设连个线程获取了同意对象,进行了更改,怎么办?

    这里就要用到select_for_update()

    注意:这点很重要,金钱的操作,我们最好不要使用自增自减运算,而是使用select_for_update()的行级索来避免冲突。

    所以嘞,我们的例子又可以改进了。

    复制代码
    try:
        with transaction.atomic():
            locked_obj =UserProfile.objects.select_for_update().get(pk=obj.id)
    locked_obj.price -= to_decimal('11.11111', 2)
    assert locked_obj.price >= 0 #断言判断是不是合理
    locked_obj.save(update_fields=['price'])
    except:
    print 'save failed'
    复制代码

     到这里,才算是告一段落。

    还有,补充一点:

      网上都说使用select_for_update可能会产生死锁,具体可以看我的上一篇文章。

      Django中管理并发操作

  • 相关阅读:
    005 ES的文档一些控制
    004 REST风格中在ES中的约定
    003 接触elasticsearch的Restful Api【快速入门】
    002 elasticsearch中的一些概念
    001 centos7下安装kibana
    000 centos7下安装elasticsearch7的单节点安装
    006 DOM节点操作与元素的创建
    006 认识BeanNameAware
    005 Spring和SpringBoot中的@Component 和@ComponentScan注解
    004 JpaRepository,CrudRepository,PagingAndSortingRepository的区别
  • 原文地址:https://www.cnblogs.com/hanbowen/p/10065617.html
Copyright © 2020-2023  润新知