DigitalPlatform / dp3

Integrated Library System, 2018
Apache License 2.0
0 stars 3 forks source link

书目中立格式 #5

Open renyh opened 6 years ago

renyh commented 6 years ago

书目中立格式设计思路

  1. 从最简单的开始,逐步根据需要决定采用较为复杂的方式,中立格式可以满足图书馆业务即可。
  2. 中立格式要满足书目摘要和和各种表格显示的书目信息
  3. 单独用中立格式进行搜索,看看能否满足现在内务、dp2OPAC 等所有书目检索的需求。
  4. 如果中立格式在实现检索功能的时候,不足以实现现有 dp2 的所有功能,可以考虑再增设一个 keys 格式,keys格式最好是从中立格式转化而来,比如非用字的处理。也可以考虑把 keys 融入到中立格式中,比如 title 后面跟一个 title_key 字段。
  5. 书目中立格式靠近 DC 是为了取得标准化的某种合法性。但既然是中立格式,主要还是一个机内格式,一旦发现 DC带来的麻烦超过自己定义,或者暂时理解不透 DC 格式,随时可以转为用自己定义的私有格式。
  6. 中立格式包括MARC基本字段。为了方便某些处理的“工作字段”可以动态根据基本字段组合变换生成。

关于书目中立格式设计讨论: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

renyh commented 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; }

    }

ISBN

    // 国际标准书号 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; }
    }
renyh commented 6 years ago

最近一段时间尝试做书目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
renyh commented 6 years ago

20180509与谢老师还讨论了: 1)在MongoDB记录中缓存一些工作字段,例如浏览格式,馆藏地之类,不用每次需要的时候生成。 2)对于一些处理MARC的模块,将处理方法 与 配置内容 分开,方便分工。 3)对于底层数据库单独做一个模块,通过接口被上面业务层调用,接口要稳定。不同的开发人员分工和隔离。 4)在dp3的一个数据库支持存放不同格式的MARC记录,另外采用比dp2的MARCXML高的XML版本。 5)约定大于配置的原则,一些用于基础构造的后面一般不改的配置多用约定写到代码中。

renyh commented 6 years ago

dp2系统中的Record与Keys表的结构

image


image image


image image

renyh commented 6 years ago

书目记录及下级对象资源的文档结构

类型 备注
_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用本地文件存储大对象。

renyh commented 6 years ago

数字平台谢*回复: 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 更进步。

renyh commented 6 years ago

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

renyh commented 6 years ago

https://docs.mongodb.com/manual/core/gridfs/

{
  "_id" : <ObjectId>,
  "files_id" : <ObjectId>,
  "n" : <num>,
  "data" : <binary>
}