Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

书目中立格式 #5

Open
renyh opened this issue May 5, 2018 · 8 comments
Open

书目中立格式 #5

renyh opened this issue May 5, 2018 · 8 comments

Comments

@renyh
Copy link
Collaborator

renyh commented May 5, 2018

书目中立格式设计思路

  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
Copy link
Collaborator Author

renyh commented May 9, 2018

书目中立格式(作废)

于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
Copy link
Collaborator Author

renyh commented May 9, 2018

最近一段时间尝试做书目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
Copy link
Collaborator Author

renyh commented May 9, 2018

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

@renyh
Copy link
Collaborator Author

renyh commented May 10, 2018

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

image


image
image


image
image

@renyh
Copy link
Collaborator Author

renyh commented May 22, 2018

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

类型 备注
_id ObjectId dp3成员,代替dp2的id。由MongoDB自动生成的文档唯一标识,例如 ObjectId("5aaceadd3b683f1598ceb894"), 12字节,每个字节两位16进制数字。
range String 资源的字节范围,资源不完整时值形态为#0~(当前长度-1)多个range以逗号分隔,完整时值为#在dp2系统中,资源可以分块多次保存,一块的尺寸前端自己定义,目前一块尺寸是500KB(500*1024),超过这个尺寸会分成多次保存,该字段主要用于存储资源未完整时的字节范围。
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
Copy link
Collaborator Author

renyh commented May 22, 2018

数字平台谢*回复:
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
Copy link
Collaborator Author

renyh commented May 22, 2018

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
Copy link
Collaborator Author

renyh commented May 22, 2018

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant