Open renyh opened 6 years ago
于20180509作废,详细原因参见后面跟帖
// 书目记录
public class BiblioItem
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string id { get; set; }
// ISBN 国际标准书号
public List<ISBNItem> isbnList { get; set; }
// 题名与责任者
public List<TitleItem> titleList { get; set; }
}
// 国际标准书号 International Standard Book Number
// 可重复
// UNIMARC: 010
// USMARC:020
public class ISBNItem
{
// 国际标准书号 International Standard Book Number
// UNIMARC: 010$a
// USMARC:020$a
// 有则必备,不可重复
public string isbn { get; set; }
// 文献获得方式和/或价格 Terms of availability
// UNIMARC: 010$d
// USMARC:020$c
// 有则必备,不可重复
public string terms { get; set; }
// 限定,一般为出版信息
// UNIMARC:010$b
// USMARC:没有此字段?
// 有则必备,不可重复
public string limit { get; set; }
// 错误或无效的ISBN Canceled/invalid ISBN (R)
// UNIMARC: 010$z
// USMARC:010$z
// 可重复
public List<string> invalidISBN { get; set; }
}
// 题名与责任者说明
// 不重复
// UNIMARC: 200
// USMARC:245
public class TitleItem
{
// 正题名 Title
// UNIMARC: 200$a
// USMARC:245$a
// 必备,200$a可重复,245$a不可重复
// UNIMARC可重复的时候,是单个子字段重复,还是与其它子字段要成对重复?
public List<string> title { get; set; }
// 一般资料标识,该用个什么单词作为字段名呢?
// UNIMARC: 200$b
// USMARC:没有此字段?
// 可重复
public List<string> 一般资料标识 { get; set; }
// 其它责任者的正题名,该用个什么单词作为字段名呢?
// UNIMARC: 200$c
// USMARC:怎么对应?
// 可重复
public List<string> 其它责任者的正题名 { get; set; }
// 并列正题名,该用个什么单词作为字段名呢?
// UNIMARC: 200$d
// USMARC:怎么对应?
// 可重复
public List<string> 并列正题名 { get; set; }
// 其它题名信息,Remainder of title
// UNIMARC: 200$e
// USMARC:245$b? 编目老师说不一同对应
// 200$e可重复,245$b不可重复
public List<string> remainderTitle { get; set; }
// 主要责任者说明,Statement of responsibility, etc.
// UNIMARC: 200$f
// USMARC:245$c?
// 200$f可重复,245$c不可重复
// 是单个重复,还是与其它子字段成对配套重复
public List<string> responsibility { get; set; }
// 其它责任者说明,Remainder of responsibility
// UNIMARC: 200$g
// USMARC:没有此字段?
// 可重复
public List<string> remainderResponsibility{ get; set; }
// 分辑(册)、章节号,Number of part/section of a work
// UNIMARC: 200$h
// USMARC:245$n
// 可重复
public List<string> volumeNo{ get; set; }
// 分辑(册)、章节名称,Name of part/section of a work
// UNIMARC: 200$i
// USMARC:245$p
// 可重复
public List<string> volumeName { get; set; }
}
最近一段时间尝试做书目MARC数据的中立格式,用MongoDB数据库存储。
试验下来发现MongoDB C# Driver的类结构,不足以表示MARC字段或子字段的顺序及内在关系。 从 mongodb 数据库结构设计角度,一般一条“记录”里面字段是确定的。可以灵活的是,每个字段可以本身是一个数组,包含类似“子字段”的重复信息。比如在MongoDB中表示200字段结构,转成Title这个类,如下:
// 题名与责任者说明
// 不重复
// UNIMARC: 200
// USMARC:245
public class TitleItem
{
// 正题名 Title
// UNIMARC: 200$a
// USMARC:245$a
// 必备,200$a可重复,245$a不可重复
// UNIMARC可重复的时候,是单个子字段重复,还是与其它子字段要成对重复?
public List<string> title { get; set; }
// 其它责任者的正题名
// UNIMARC: 200$c
// USMARC:怎么对应?
// 可重复
public List<string> 其它责任者的正题名 { get; set; }
// 并列正题名
// UNIMARC: 200$d
// USMARC:怎么对应?
// 可重复
public List<string> 并列正题名 { get; set; }
// 其它题名信息,Remainder of title
// UNIMARC: 200$e
// USMARC:245$b? 编目老师说不一同对应
// 200$e可重复,245$b不可重复
public List<string> remainderTitle { get; set; }
}
但实际情况是,200 字段的子字段数量较多,子字段之间也有复杂的内在关系,比如$a$f是成对出现的,不能简单把它的子字段打散了随便存储,一定要描述好子字段之间的内在逻辑联系才行。 这样看来用MongoDB的一条记录做一个MARC对应的中立格式的想法也就没法实现了。
当初提出设计书目中立格式主要是两个目的:一是创建输出格式(例如 ISBD) 方便; 二是检索方便。检索方便是指可以直接检索扫描中立格式的字段,比扫描 MARC 格式内容方便。
关于书目的检索换一个方案:退回用传统关系数据库的方法,突破“记录”边界,不试图用一个物理记录描述一个 MARC 记录,而是用若干物理 mongodb 记录从逻辑上组织起来描述一条 MARC 记录中的重复字段。dp2 的 keys 表方法就是一个典型的方法,用若干原本散乱平行的物理记录,用一个记录 ID 字段联络起来构成一个逻辑 MARC 记录。不过这个方法的缺点是,原本只需要操作一条物理记录的,现在需要操作若干条物理记录,才对应一条 MARC 记录,效率稍低。
记录三个结构的层次关系: 1)MARC 2)中立格式,与MARC是一对一的关系 3)Keys表,与MARC是多对一的关系,即一条MARC对应多个检索点表中的多个记录 在检索点表中,也可以记录相对位置,比如第几个200字段的第几个子字段;还可以记录绝对位置,比如全局第几个字段中的第几个子字段。这样检索点表可以还原MARC的片断记录(极端情况下如果对所有的字段子字段建立了检索点,可以还原整个MARC,但目前我们不需要这样做)。
另外为检索点表的父ID字段建立索引,因为要根据书目记录ID检索Keys表。
虽然MARC转中立格式比较困难,但册记录的结构非常适合存到MongoDB里,让我们感动很安慰。册记录的xml结构如下:
<?xml version="1.0" encoding="utf-8" ?>
- <root>
<parent>2</parent>
<refID>5649a18a-e3fa-4ec3-896e-4f6a20b06e42</refID>
<barcode>DPB000002</barcode>
<location>星洲学校/图书馆</location>
<price>CNY28.80</price>
<bookType>普通</bookType>
<batchNo>图书登记201710</batchNo>
<accessNo>I563.85/H022</accessNo>
- <operations>
<operation name="create" time="Sun, 22 Oct 2017 17:24:30 +0800" operator="supervisor" />
<operation name="lastModified" time="Thu, 04 Jan 2018 06:27:38 +0800" operator="renyh" />
</operations>
- <borrowHistory count="2">
<borrower returnDate="Thu, 22 Mar 2018 10:02:25 +0800" barcode="L120100000000000001" borrowDate="Tue, 20 Mar 2018 16:35:15 +0800" borrowPeriod="31day" denyPeriod="" returningDate="Fri, 20 Apr 2018 12:00:00 +0800" borrowOperator="zhiyan" operator="zhiyan" />
<borrower returnDate="Wed, 18 Apr 2018 10:32:16 +0800" barcode="L120100000000000001" borrowDate="Wed, 18 Apr 2018 10:26:11 +0800" borrowPeriod="31day" denyPeriod="" returningDate="Sat, 19 May 2018 12:00:00 +0800" borrowOperator="zhiyan" operator="zhiyan" />
</borrowHistory>
<state />
<reservations />
</root>
在MongoDB对应的结构如下,这样我们检索册记录时,直接从MongoDB中检索即可,其中不用再向dp2那样建keys表了。
todo
20180509与谢老师还讨论了: 1)在MongoDB记录中缓存一些工作字段,例如浏览格式,馆藏地之类,不用每次需要的时候生成。 2)对于一些处理MARC的模块,将处理方法 与 配置内容 分开,方便分工。 3)对于底层数据库单独做一个模块,通过接口被上面业务层调用,接口要稳定。不同的开发人员分工和隔离。 4)在dp3的一个数据库支持存放不同格式的MARC记录,另外采用比dp2的MARCXML高的XML版本。 5)约定大于配置的原则,一些用于基础构造的后面一般不改的配置多用约定写到代码中。
dp2系统中的Record与Keys表的结构
键 | 类型 | 备注 |
---|---|---|
_id | ObjectId | dp3成员,代替dp2的id。由MongoDB自动生成的文档唯一标识,例如ObjectId("5aaceadd3b683f1598ceb894") , 12字节,每个字节两位16进制数字。 |
range | String | 资源的字节范围,资源不完整时值形态为#0~(当前长度-1) 多个range以逗号分隔,完整时值为# 。*在dp2系统中,资源可以分块多次保存,一块的尺寸前端自己定义,目前一块尺寸是500KB(5001024),超过这个尺寸会分成多次保存,该字段主要用于存储资源未完整时的字节范围。**。 |
dptimestamp | String | 资源的时间戳,形态为十六进制文本字符串,从左到右每二个字符表示一个byte的值。例如568c4a13a4b8d5080000000000000018 ,当资源不完整时值为null 。 |
newdptimestamp | String | 资源的临时时间戳,当资源不完整时有值,完整后为null ,在dp2系统中,主要用于大的资源分块多次保存时,每次写后的时间戳。 |
metadata | String | 资源元数据内容,资源未完整时为null。对于书目记录,元数据格式为<file size="4145" lastmodified="2018/5/13 15:58:54" /> ;对于对象资源,元数据格式一般为。<file mimetype="application/octet-stream" localpath="C:\0-d\0-测试\大备份与恢复测试\l1.dp2bak" size="3298463" lastmodified="2018/5/13 15:35:18" /> ,dp2是这样设计的,在想dp3对于书目记录,是否就分成两个成员size和lastmodified;对于对象资源元数据字段多时再用xml表示或者也分成字段。 |
filename | String | 对于MySQL、SQLite数据库,资源本身存储在本地文件,当资源完整时,该字段存储文件名。如0000000\001 。如果资源不完整值为null。 |
newfilename | String | 当资源不完整时,该字段存储临时文件名。如0000000\001_0.temp 。资源完整值为null。 |
files | Collection | dp3新增的在员,用于存储对象资源,待讨论确认。 |
问题一:在MongoDB中,书目下的对象资源,是否用内嵌的文档来存储?
在dp2系统,书目记录与其下级的对象资源是作为1+n条记录存储在同一个数据表中,他们记录路径id有内在关联,例如书目记录的id是0000000001
,它的第1个对象的路径id为0000000001_0
,第2个对象的路径id为0000000001_1
。
外部访问资源的路径也有规则,书目记录路径为:数据库名/书目记录id
,例如中文图书/1
;
书目下的对象路径格式为数据库名/书目记录id/object/对象id
,例如中文图书/1/object/0
。
在MongoDB中,书目记录与其下级的对象,可以考虑用一条文档存储,书目文档中有个成员是files,files是个集合,用于存储下级对象。
目前觉得对象的结构与书目记录结构类似。
问题二:关于像书目Xml这类资源的结构,是否有些字段可以不要 如果将书目与对象资源分成不同的结构,对于书目记录结构,不知是否会超过500KB(一般不会吧),如果不超过就可以一次性保存,那么还需要这些range,newdptimestamp,newfilename字段吗?
另外考虑数据库中第一层的结构,可能会存除了书目xml之外的资源,比如新闻一类其它信息,尺寸比较大的情况。
问题三:在MongoDB中,考虑是用二进制类型,还是用本地文件 MongoDB的二进制类型,最大尺寸限制是16MB,对于XML一类是够了。 对于大的对象,有可能超过这个尺寸。MongoDB有关于大文件存储的GridFs,还没有学习。是否先考虑参考dp2用本地文件存储大对象。
数字平台谢*回复: dp2kernel 还有一个对象管理子系统。可以尝试用一下 mongodb 的 GridFS 试试: https://stackoverflow.com/questions/4988436/mongodb-gridfs-with-c-how-to-store-files-such-as-images
你需要去了解一下mongodb 的这个 GridFS。我可以说一些自己的体会和需求,如果 GridFS 能直接满足最好了,如果不能直接满足,也有可能通过基于它包裹一层处理来间接满足。
dp2kernel 在设计这个对象管理系统的时候,是允许对象尽可能大的,即操作系统文件允许多大,一个对象就可以多大。所以在上传和下载对象的时候,肯定是分片进行的。也可以提供类似 Stream 的 API,模仿文件指针,文件读写操作。比如前一段刚开始设计的从 pdf 文件中预览的功能,有关开源模块就要用到 Stream 接口,以便从对象中获取一部分内容创建 .jpg 文件。
在上传和下载对象内容的过程中,支持多前端并发操作。并发下载没有太大问题,比较简单。但并发上传就比较麻烦了,首先是不同的前端分别独立地上传去覆盖修改同一个位置的对象,这个需要建立不同的会话,而且最好要有时间戳保护,避免交替写入混乱的内容。
dp2kernel 的一个对象记录,利用了两套时间戳字段,意图是让这两个字段轮转,实现一边允许上传内容到临时区,一边同时允许下载进行。比如一个大的对象需要上传一天,这一天中你不可能禁止别人下载。那样系统就没法实用了。这也是初步的会话的概念,但不完满。如果这次 dp3 要做这样的功能,可以允许多个会话,每个会话都有独立的存储区,等对象上传完成后瞬间突然切换到正式位置。
dp2kernel 也利用了对象存储机制来存储 XML 记录。XML 记录可以理解为元数据 ,一般不会有那么大,但从存储角度来说和对象数据其实没有什么区别,所以 dp2kernel 就用同一套设施了。从长远来看,你也确实不清楚前端是不是要用一个一百兆的 XML 文件,对不对。XML 也不总是用 XmlDocument 处理,还可以用 XmlReader 处理,reader 就是允许文件无限大的。比如 dp2 系统里面的发票库的发票记录。很难说一张发票里面总共要包括多少条册记录详情。可能会很大。
dp3 设计时候的中立格式,是为了方便处理的一个中间格式,是可以限制它的大小的。但原始数据不好做出尺寸限制。所以原始数据存储问题必须得到彻底解决。这是不同的问题。
dp2kernel 直接用一个一个文件来存储对象数据,一个文件对应一个对象。这是过分使用了文件系统的能力。也不是没有缺点。缺点就是拷贝的时候,成千山万个文件会有某些问题。另外初始化数据库的时候,要一瞬间删除掉成千上万的物理文件,耗时很长。如果是用一个大文件,在里面划分小块,构成链表来存储对象信息,则可能更体面合理一些。如果 GridFS 能用,也可以看看它是怎么实现的,效果如何。
在开发和探索的时候,可以分阶段实现不同的模块。 刚开始为了集中精力解决对象存储以外的问题,可以把对象存储用一些简单的机制实现,先不考虑并发上载等复杂问题。 等后面有精力了再过来替换模块或者增补开发。对象存储这部分接口可以早期制定好,保持稳定,不会影响到其他模块的开发。
以后的正式产品中,对象存储引擎这个部分也可以提供不同的版本,让用户选用。在开发阶段多实现几个不同的版本,也有利于考验接口的合理性。
考虑到联合编目的情形,可能同一条 MARC 记录,要允许保存所有历史版本。所以存储这个模块还需要比 dp2kernel 更进步。
SQL反模式:SQL 建模与使用指南(分享自知乎网)https://zhuanlan.zhihu.com/p/36831350?utm_source=qq&utm_medium=social&utm_oi=30747138195456
这篇文字你参考一下: http://www.ukoln.ac.uk/metadata/interoperability/dc_unimarc.html 映射 DC 到 UNIMARC
https://docs.mongodb.com/manual/core/gridfs/
{
"_id" : <ObjectId>,
"files_id" : <ObjectId>,
"n" : <num>,
"data" : <binary>
}
书目中立格式设计思路
关于书目中立格式设计讨论:
https://github.com/DigitalPlatform/dp3/issues/1
CNMARC参考资料 书目摘要参考:http://dp2003.com:8088/doc/web/#/7?page_id=40 书目查询浏览格式:http://dp2003.com:8088/doc/web/#/7?page_id=41 书目表格格式 table_unimarc:http://dp2003.com:8088/doc/web/#/7?page_id=42 公共查询格式 opac_biblio:http://dp2003.com:8088/doc/web/#/7?page_id=43 检索点文件参考 keys:http://dp2003.com:8088/doc/web/#/7?page_id=44
其它参考资料 DC参考:http://dp2003.com:8088/doc/web/#/7?page_id=45 册记录Xml参考:http://dp2003.com:8088/doc/web/#/7?page_id=46
USMARC参考资料 书目摘要参考:http://dp2003.com:8088/doc/web/#/7?page_id=47 书目查询浏览格式:http://dp2003.com:8088/doc/web/#/7?page_id=48 公共查询格式 opac_biblio:http://dp2003.com:8088/doc/web/#/7?page_id=49 检索点文件参考 keys:http://dp2003.com:8088/doc/web/#/7?page_id=50
作废的一稿中立格式 :http://dp2003.com:8088/doc/web/#/7?page_id=62