FAS架构设计:稿件库
注:标题的FAS指本人即将开发的稿件管理器:Furry Art Studio的缩写,与FaaS或者其他相关的概念无任何关系。
对于一个稿件管理工具来说,我们先下个定义:稿件是什么?
稿件(Manuscript)或艺术品(Artworks)其实可以认定为一个概念,后者的范围更广。接触过Furry文化的都知道:一个稿件本质是一个JPEG/PNG/GIF等图片格式组成,并且JPEG与PNG格式占稿件的90%以上。同时一份“稿件”包含很多的差分:
差分,顾名思义,它是一种描述数据变化的趋势,例如在第一秒某物体速度是1m/s,而第二秒它的速度变为1.5m/s,二者的差分就是0.5,通常使用希腊字母德尔塔(Δ)表示
在稿件中,差分可以理解为一张图的不同版本,比如在保证主要部分没有变化的前提修改人物的动作,表情,滤镜等等。
这样,我们就清楚了稿件的本质:一个图像集合。
要想将稿件数字化并存储,我们需要使用数据库(Database),它不仅专业,更能更快的处理相当多的数据。在单机应用里,我们使用SQLite数据库,它相比于其他的数据库,本身简单(仅一个文件),不需要中央服务器(应用本身就是这个数据库的“服务器”),非常符合我们的设计。
对于一个稿件,我们在使用传统的修改文件名的方式存储时,通常需要记录:它是谁画的,他叫什么,这是两个最基本的信息。
我从MP3文件的元数据(Metadata)里得到灵感,使用如下的类来定义一个稿件(Artwork):
Public Class Artwork ' 定义稿件属性
Public Property ID As Integer ' 数据库自增主键
Public Property UUID As Guid ' 稿件UUID
Public Property Title As String ' 稿件标题
Public Property Author As String ' 稿件作者
Public Property Characters As String() ' 稿件内角色数组
Public Property CreateTime As DateTime ' 稿件绘制时间
Public Property ImportTime As DateTime ' 稿件导入数据库时间
Public Property UpdateTime As DateTime ' 稿件更新元数据时间
Public Property IsDeleted As Integer ' 稿件状态(正常/已删除)
Public Property Tags As String() ' 稿件标签数组
Public Property Notes As String ' 稿件备注
Public Property FileNames As String() ' 稿件文件名数组
End Class
这样,我们就可以成功地将稿件“数字化”了,这里的数字化指的是对稿件本身进行定义(图像集合),打标签(Tagging),建立索引(Index),最终目的是便于我们查询(Query)。
其实,在设计这套系统的时候,我有在想该怎么处理稿件本身的内容(数据),SQLite里有一个类型就是二进制(BLOB类型),我最初设计的是将稿件转换成二进制,存储在数据库里,但是这样有几个问题:
- 读写文件时,尤其是加载缩略图的时候,需要等待大量的时间,或者需要通过多线程读取数据库文件,这就导致对计算机性能要求很高。
- 如果数据库文件出现了损坏,那么所有的稿件都会被困在里面,无法读取,这对于画师来说是灾难性的,即使现代的数据库由于ACID的存在,能够尽最大努力保住数据,但仍然需要考虑这种可能性。相比之下文件系统会稳定得多。
- 所有数据存储在数据库里时,文件大小会变得很大。
详细对比如下:
| 维度 | 仅数据库存储 | 仅文件系统存储 | 混合存储 |
|---|---|---|---|
| 特点 | 文件易于管理,迁移时作为整体,速度会快很多。但数据易损坏,对计算机性能要求高,文件体积大。 | 对不同设备友好,不需要数据库组件。但是很难查询,需要借助第三方工具,难以标准化维护和管理 | 将元数据等字段通过数据库存储,通过UUID建立的文件夹来存放稿件本身原本的文件,分开管理稿件元数据与稿件本身。 |
在有了稿件之后,我们就可以定义一个新的类:稿件列表(稿件数据库),它的作用就是管理和抽象化那些对于稿件本身,以及元数据的操作的部分,比如我们可以这样定义:
Public Class ArtworkLibrary ' 定义稿件库实例类
Public Function AddArtwork(artwork As Artwork) As Integer
' 添加新的稿件
End Function
Public Sub UpdateArtwork(artwork As Artwork)
' 更新稿件
End Sub
Public Function GetArtworkByUUID(uuid As Guid) As Artwork
' 根据UUID获得稿件
End Function
' ...其他方法
End Class
这样,我们通过这个类,在实例化的时候将保存的文件夹与数据库文件位置作为参数,我们就可以直接对这个混合存储进行管理了。
但很快我意识到另一个问题:假设我们的系统可以连接多个稿件库(比如有些画师或者设主具有多个设定,或者想把个人工作与共享作品分开),我们就得对ArtworkLibrary这个实例类进行管理,于是我这样设计:
Public Class LibraryManager ' 定义稿件库管理器单实例类
Private Shared _instance As LibraryManager
Private _libraries As New Dictionary(Of String, ArtworkLibrary)
Private _currentLibrary As ArtworkLibrary
Public Shared ReadOnly Property Instance As LibraryManager
Get
If _instance Is Nothing Then
_instance = New LibraryManager()
End If
Return _instance
End Get
End Property
Private Sub New()
'单例模式
End Sub
Public Sub AddLibrary(name As String)
If Not _libraries.ContainsKey(name) Then
Dim library As New ArtworkLibrary(name)
_libraries.Add(name, library)
End If
End Sub
Public Function SwitchLibrary(name As String) As Boolean
If _libraries.ContainsKey(name) Then
_currentLibrary = _libraries(name)
Return True
End If
Return False
End Function
'...其他方法
End Class
通过这个方式,我们就可以方便地管理每个稿件库,而每个稿件库下面还有对应的稿件。
下一步就是UI与业务逻辑层了,先实现基本的增删改查(CRUD),至于PawUI那部分可能需要过段时间再绘制了。
而我的最终目标,就是尽可能让FAS的数据库与PawCore兼容,这样用户可以无缝衔接过去。