Odoo中如何生成唯一不重复的序列号详解
前言
最近在做的项目中有一个需求是要让某个字段值根据记录产生的日期和一定的组合规则按顺序生成一个序列号,这个序列号不可重复,这原本是一个很常见的需求,没有多想就写好了。由于没有考虑到并发的情况,到后面测试的时候才发现一个比较严重的问题,如果用户同时操作产生的记录,生成的序列号会出现重复。
经过讨论和思考后有几种解决方案,一是在数据库表层加锁,一是采用类似 redis 的消息队列,还有就是通过文件锁达到数据库排他锁的目的,鉴于时间和项目当前的情况,最后采用了通过文件锁实现这个需求。
其实除了以上几种方式,Odoo 本身就有一个模型(ir.sequence)是用于生成序列的,可以很方便地实现这个需求,因为之前一直没有接触过这个模块,还是在项目之后的阶段同事使用到了并且告诉我之后才知道原来有这么个好东西的存在。在这里我将会把我原本通过文件锁实现的方式和通过 Odoo 自带的ir.sequence实现的方式都记录下来。
给文件加锁 - fcntl
fcntl是 Python 标准库里的一个模块,用来对文件进行加锁的操作。在实现中主要用到的是下面这个函数:
def flock(fd, operation): """ flock(fd, operation) Perform the lock operation op on file descriptor fd. See the Unix manual page for flock(2) for details. (On some systems, this function is emulated using fcntl().) """ pass
其中fd是文件描述符,operation为锁的操作,总共有4种:
- fcntl.LOCK_EX - 排他锁
- fcntl.LOCK_NB - 非阻塞锁
- fcntl.LOCK_SH - 共享锁
- fcntl.LOCK_UN - 解锁
关于fcntl的其他具体内容请查看 官方标准库文档 。
下面来看一下具体的实现,在给出代码之前,先描述一下需求,假设模型中有一个字段sn用于存储按一定规则生成的序列号,序列号的组成规则如下:
- 固定的前缀SN
- 取记录生成的日期组成的6位数字%y%m%d,如2017年12月8日取值为171208
- 最后是3位的流水号,从001开始递增
- 生成的序列号不能有重复
- 最后的3位流水号每天自动重置,从001开始递增(这个需求涉及到一些扩展,故此文将不实现这一需求)
需求很简单,也很清楚了,下面就上代码开始具体的实现。首先创建一个模块demo_sequence:
./odoo-bin scaffold demo_sequence
然后在模块的目录下创建数据文件目录data/,在目录下创建一个data.xml文件,在后面会用到;继续在模块目录下创建静态文件目录static/,在目录下创建一个空文件SN.LOCK用作加锁的文件对象。完成之后的目录结构如下:
demo_sequence ├── __init__.py ├── __manifest__.py ├── controllers │ ├── __init__.py │ └── controllers.py ├── data │ └── data.xml ├── demo │ └── demo.xml ├── models │ ├── __init__.py │ └── models.py ├── security │ └── ir.model.access.csv ├── static │ └── SN.LOCK └── views ├── templates.xml └── views.xml
模型的创建和视图的编写,这里将跳过不说,具体的代码将在后面给出。先创建一个给文件加锁的函数:
def _file_lock(flag=fcntl.LOCK_EX): FILE_PATH = get_module_resource('demo_sequence', 'static/SN.LOCK') file = open(FILE_PATH) fcntl.flock(file.fileno(), flag) _logger.info('Acquire Lock') return file
然后重写模型的create()方法:
@api.model def create(self, vals): file = _file_lock() sn_prefix = 'SN' + datetime.date.today().strftime("%y%m%d") obj = self.env['demo_sequence.fcntl'].search_read([('sn', '=like', sn_prefix + '%')], limit=1, order='sn DESC') # 今天已经有序列号,在最新的序列号上递增 if obj and obj[0]['sn'].startswith(sn_prefix): sn_suffix = int(obj[0]['sn'][-3:]) + 1 vals['sn'] = sn_prefix + str(sn_suffix).zfill(3) # 补0 else: vals['sn'] = sn_prefix + '001' res = super(DemoSequence, self).create(vals) # 关闭文件将自动解锁 file.close() return res
利用fcntl给文件加锁后再生成序列号写入数据库,以达到序列号不重复的目的,就这么点代码就搞定了,不过还有更简单的方式,就是利用 Odoo 自带的ir.sequence模型产生序列号。
生成唯一标识 - ir.sequence
在模型ir.sequence中是这样描述的:
The sequence model allows to define and use so-called sequence objects. Such objects are used to generate unique identifiers in a transaction-safeway.
我们可以利用它生成唯一的标识,下面就看一下怎么用ir.sequence实现前面所说的需求。
打开data/data.xml并添加以下代码:
<?xml version="1.0" encoding="utf-8"?> <odoo> <data noupdate="1"> <record id="seq_demo_sequence_sn" model="ir.sequence"> <field name="name">Demo Sequence SN</field> <field name="code">demo_sequence.sequence</field> <field name="prefix">SN%(y)s%(month)s%(day)s</field> <field name="padding">3</field> </record> </data> </odoo>
这里的参数实际上不止这几个,在实现需求的前提下这几个就够用了,分别说明一下各个参数的作用:
- name - 名字,随便叫什么都行
- code - 调用生成编码的 Key,需保证唯一性
- prefix - 前缀,可以是固定的字面量也可以是组合参数
- padding - 序列递增的位数
注:记得将data/data.xml加入到__manifest__.py的data列表中
接下来就是调用得到按规则生成的序列号,同样重写模型的create()方法:
@api.model def create(self, vals): vals['sn'] = self.env['ir.sequence'].next_by_code('demo_sequence.sequence') return super(DemoSequence2, self).create(vals)
可以看到只需要一行代码就可以得到一个唯一的序列号,比前面用fcntl给文件加锁的方式简单了几个级别。这里的调用就用到了前面定义中所写的code。
在实际项目中所使用到的两种方式都已经在这里记录下来了,官方的东西确实是个好东西,回头看看自己写的东西,毕竟 too young,可惜官方的文档好像并没有相关的记录(抑或是我没找到?),多翻翻官方实现的功能模块源码,才是精进 Odoo 之道。
源码下载
以上出现的所有代码均可在仓库 ruter/TNK-Odoo-Demo 中 查看并下载 (大家也可以通过本地下载)。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。
- Python基础语法(2)
- 实践 Mysql Group Replication 组复制
- Python爬虫入门(二)解析源码
- iOS加密算法总结
- 【Python爬虫实战】——爬取今日头条美女图片
- Python-贝叶斯实战垃圾邮件过滤(大量数据)
- C# 操作 access 数据库
- iOS学习——tableview中带编辑功能的cell键盘弹出遮挡和收起问题解决
- 搭建GitLab版本控制系统
- Mysql 8.0 更好的支持了 UUID
- 理解SQL原理SQL调优你必须知道的10条铁律
- 解决Vmware克隆出来虚拟机的网络问题
- Nginx|Tengine的编译安装步骤
- centos下tomcat安装调试
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 微信公众平台开发教程⑥ 微信开发集成类的使用图文详解
- keras分类之二分类实例(Cat and dog)
- 详解Python 循环嵌套
- PHP中quotemeta()函数的用法讲解
- 微信公众号实现扫码获取微信用户信息(网页授权)
- 实例说明js脚本语言和php脚本语言的区别
- 在Ubuntu 18.04上安装PHP 7.3 7.2和7.0的方法
- PHP7匿名类的用法示例
- laravel配置Redis多个库的实现方法
- PHP中Static(静态)关键字功能与用法实例分析
- 详解PHP 二维数组排序保持键名不变
- 详解PHP的抽象类和抽象方法以及接口总结
- keras的ImageDataGenerator和flow()的用法说明
- python 识别登录验证码图片功能的实现代码(完整代码)
- Laravel事件监听器用法实例分析