第十五章 附件与二进制字段
所谓二进制字段,实际代表了odoo中的一类二进制文件,像普通的文件,图片、视频等。
而附件则是指odoo中上传的文件以及编译后生成的js、css等文件,对应附件(ir.attachement)对象。二进制字段的底层实现使用了附件,因此二进制字段的行为表现跟附件基本一致。本章将详细介绍二进制字段与附件有关的内容。
附件
我们可以在设置-技术设置-数据结构-附件中查看所有附件的详细信息,通常会包含附件的名称、类型、MIME类型、文件路径等信息。
默认情况下文件存储在应用服务器的上,用户可以通过在技术参数中新增一个参数ir_attachment.location,并将值设置为db,来告诉odoo将附件的存储位置更改为数据库存储(当然我们是不建议这么做的)。
附件文件的存储路径我们可以通过配置文件中的data_dir参数来指定。data_dir指定的路径下通常有三个文件夹,分别为addons、session和files,其中files就是附件存储的路径。
附件的基础特性
1. 附件的类型
默认的附件有两种类型可选:
- url: 以URL形式存储的附件
- binary: 以二进制文件存储的附件
这就是说,文件支持两种方式的上传,一种是直接上传二进制文件,另外一种是直接输入文件的URL地址。直接上传二进制的文件会被存放在前面提到过的存储文件夹中,URL一般是模块自带的静态文件。
相应的,如果附件是url类型,那么可以通过url字段获取附件的URL地址。如果附件是binary类型,则可以通过datas获取附件的二进制内容。
2. 多媒体文件类型
如果附件是多媒体类型的文件,那么系统在附件上传以后会自动检测其相应的文件类型,并存储在mimetype字段中。
常见的mimetype列表:
- text/css
- text/plain
- text/html
- image/jpeg
- image/png
- audio/mpeg
- audio/ogg
- audio/*
- video/mp4
- application/*
- application/json
- application/javascript
- application/ecmascript
- application/octet-stream
- ...
3. 其他附件的信息
- access_token: 用于公开访问的token
- checksum: 校验和/SHA1
- datas_fname: 附件的文件名
- file_size: 附件文件的大小
- public: 是否公开
- res_model: 关联的模型名称
- res_field: 关联的字段名称
- res_id: 关联的记录ID
其中res_field是与二进制字段相关的内容,将在后面介绍。res_model指明附件是在哪个模型下上传的,res_id指明该模型的记录信息,这样就能定位到具体是哪个模型的哪个记录附加的附件。
datas与db_datas
附件有两个字段datas与db_datas, datas是经过base64加密后的值, 而db_datas是存储于数据库中的二进制值.
@api.depends('store_fname', 'db_datas')
def _compute_datas(self):
bin_size = self._context.get('bin_size')
for attach in self:
if attach.store_fname:
attach.datas = self._file_read(attach.store_fname, bin_size)
else:
attach.datas = attach.db_datas
默认附件存储逻辑
系统默认的存储逻辑是,先查找当前用户的home目录,如果home目录存在,那么就是用用户文件夹,对于不同的操作系统,用户文件夹的定义不同。常见的操作系统及其用户文件夹举例如下:
- Mac OS X:~/Library/Application Support/
- Unix: ~/.local/share/
# or in $XDG_DATA_HOME if defined - WinXP(not roaming): C:\Documents and Settings\
\Application Data\ \ - WinXP(roaming): C:\Documents and Settings\
\Local Settings\Application Data\ \ - Win7(not roaming):C:\Users\
\AppData\Local\ \ - Win7(roaming):C:\Users\
\AppData\Roaming\ \
对于Unix, 遵循和支持XDG(X Desktop Group),也就意味着默认的路径 ~/.local/share/
附件的访问权限
附件的访问权限分为两种,一种是可以公开访问的,直接通过URL可以读取。另外一种是只能够拥有访问token的人才可以读取。
附件的存储机制
附件在写入硬盘之前,odoo会计算文件的sha1值,得到一个校验值。并以校验值的前两位作为索引文件夹,以加快搜索速度。因此如果你去看filestore文件夹下的文件,通常你应该会看到类似下图的结构:
上层文件夹名为sha1计算值的前两位。当我们进入到前面说过的文件夹中,会看到如下的类似的文件:
-rwxrwxrwx 1 kevin kevin 658 Sep 26 2019 01157b1c3257f89fcea410089fdb73c65a697a9e*
-rwxrwxrwx 1 kevin kevin 16865 Sep 26 2019 0142ddc716ead38a9aea64b1c6239c2fa6ad6a77*
-rwxrwxrwx 1 kevin kevin 20344 Nov 22 2019 014fc0f76d4e8de32b10e72e9853c51261138c5f*
这些文件的文件名即我们前面讲到过的二进制内容的sha1值,我们是无法直接从文件名看出这个文件具体是什么内容的。与文件有关的信息都存储在ir_attachment表中。
当odoo需要读取某个附件时,它会搜索附件表,找到对应的索引文件,然后去文件所属的文件夹下找到改文件,加载到内存中,以base64编码的方式将二进制文件转换为字节流,然后传给前端。
如果文件是图片等媒体文件,odoo会在base64的编码前加载对应的MIME文件头,以方便浏览器正确地加载解析。
同样的,当我们在odoo中上传了文件时,odoo会把我们的文件以base64编码的方式传给后台,后台然后使用base64解码,得到二进制字节流。然后按照相同的逻辑,计算出文件应该被存储的位置,将文件写入硬盘。
如果要删除文件,odoo的处理方式不是直接删除,而是将文件简单地加入到垃圾回收列表(checklist)。等待垃圾回收机制的回收处理。
经过源代码分析,ir.attachment对象的search方法会在domain为空时自动添加一个过滤条件:('res_field','=',False),也就是模型字段生成的附件会被默认过滤掉。若要搜索字段的附件,设置res_field不为空即可。
垃圾回收
附件的垃圾回收机制其实非常简单,即对比数据库ir_attachment表中的附件的文件名和硬盘checklist文件夹中的文件名,如果数据库中存在的加入白名单不进行删除,否则则在硬盘中删除,并清空checklist列表。
至于为什么要加入垃圾回收机制,而不是直接在硬盘上进行删除,是因为如果事务在创建或者删除过程中失败或者并发事务中,会导致文件的差异。而垃圾回收机制在运行过程中会将ir_attachemnt锁表,从而保证了事务的一致性。
附件的共享
有时候我们希望将附件共享给他人,包括且不限于内部用户,门户用户,甚至游客。但出于安全性的考虑,很显然我们不能生成一个公开的链接让所有拥有连接的人进行访问。
odoo给出的方案即在附件附加一个Access Token,凡是拥有这个access token的人可以访问,否则就不能访问。
生成Access Token方法即调用附件的generate_access_token方法,该方法会在附件对象上生成一个uuid的随机字符串,只有拥有正确的access token的人才可以正常访问附件。
附件共享可以将URL直接发送给用户,具体的拼接方式如下:
{host}/web/content?id={attachment.id}&download=true&access_token={attachment.generate_access_token()}
其中 host是服务器域名,attachment是附件对象。
对象存储
得益于云计算的流行,现在很多云服务器厂商都推出了对象存储服务。对象存储与传统存储方式相比,其优势有以下几点:
- 简单的HTTP API,包含所有主要操作系统和编程语言的客户端
- 对发布静态资产的内置支持允许您使用更少的服务器
- 一些对象存储提供内置的CDN集成,可以缓存资产以加快页面加载速度
- 可选的版本控制允许您检索旧版本的对象以从意外数据覆盖中恢复
- 轻松扩展对象存储服务,而无需额外的资源或体系结构更改
- 需要支持硬盘驱动器和RAID阵列,因为所有这些都由存储提供商处理
对象存储的缺点有以下几点:
- 对象存储不允许按片段更改数据。只能修改整个对象,这会影响性能。
- 操作系统无法像常规磁盘一样安装对象存储。
总结来说,对象存储比较适合存储静态的文件,像图片、视频等等。常见的云服务器都推出了自己的对象存储服务,像亚马逊的S3,腾讯阿里的OSS,七牛云等等,通常都有比较完善的API可供使用。
像笔者就写了一个对接阿里云对象存储的模块,这里简单介绍一下用法。有需要的同学可以到我的淘宝进行选购。
首先我们要去阿里云注册开通一个账号,然后得到一个Access Key和Access Secret,然后我们去开通对象存储功能,选择一个要接入的接入点(通常以区域命名,例如,笔者这里开通了青岛地区的OSS,因此接入点为xxx.oss-cn-qingdao.aliyuncs.com),然后创建一个bucket,这里的bucket是整个区域通用的,因此要保证唯一性。
然后我们把上面的参数添加到设置-阿里云OSS的设置中:
这样就完成了设置,之后当我们在单据中上传了附件之后,附件会自动被上传至阿里云OSS中。
二进制字段
我们在第一部分第三章的时候曾经简短地介绍过二进制字段的基本用法,但是我们并没有详细说明二进制字段是如何实现以及它是如何在后端进行存储的。
首先我们先看二进制字段的定义:
class Binary(Field):
type = 'binary'
_slots = {
'prefetch': False, # not prefetched by default
'context_dependent': True, # depends on context (content or size)
'attachment': True, # whether value is stored in attachment
}
...
可以看出,二进制字段默认被设置了不可预先获取数据,但使用附件存储技术存储二进制字段。
NOTE: 12.0及更早的版本中,默认的attachment的值是False,即在12.0及之前的版本默认不存储在附件中,从13.0版本才开始使用默认的附件存储。
存储机制
二进制字段有一个属性attachment,用来标识是否存储在附件中。默认情况下使用附件的存储技术,对于用户来说可以简单地把二进制字段等同于附件。所不同的是,二进制字段虽然存在了ir_attachement表中,但是它不会在附件中显示出来。
如果不使用附件存储,那么二进制数据将被存储到数据库中,对应到数据库中的字段类型为postgresql中的bytea类型。
图片
图片是二进制文件中的一类, 但是由于使用的比较频繁,因此odoo中同样定义了一个图片字段.
图片的放大与缩小
原生odoo中支持将鼠标放到图片上显示一个放大效果.但是不支持点击放大效果. 官方商城中有一个扩展模块可以支持单击放大效果.
分辨率
默认情况下图片的分辨率被限制在了4.5M, 如果上传的图片大于这个值, 系统就会提示:
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/odoo/http.py", line 643, in _handle_exception
return super(JsonRequest, self)._handle_exception(exception)
File "/usr/lib/python3/dist-packages/odoo/http.py", line 301, in _handle_exception
raise exception.with_traceback(None) from new_cause
ValueError: 图片尺寸过大,上传的图片必须小于 4.5 兆像素.
这时候,我们需要将该字段的verify_resolution属性设置为False.
图片自动裁剪
odoo在默认情况下会对于分辨率超过1920x1920的(png,jpeg,gif,bmp,tif)图片进行裁剪,以节省存储空间。如果想要停止这个特性,只需要在系统参数中将base.image_autoresize_max_px设置成0。