// 一次操作循环 public override void Worker() { // 把系统挂起 this.App.HangupReason = HangupReason.LogRecover; try { string strError = ""; BatchTaskStartInfo startinfo = this.StartInfo; if (startinfo == null) startinfo = new BatchTaskStartInfo(); // 按照缺省值来 long lStartIndex = 0;// 开始位置 string strStartFileName = "";// 开始文件名 int nRet = ParseLogRecorverStart(startinfo.Start, out lStartIndex, out strStartFileName, out strError); if (nRet == -1) { this.AppendResultText("启动失败: " + strError + "\r\n"); return; } // string strRecoverLevel = ""; bool bClearFirst = false; nRet = ParseLogRecoverParam(startinfo.Param, out strRecoverLevel, out bClearFirst, out strError); if (nRet == -1) { this.AppendResultText("启动失败: " + strError + "\r\n"); return; } if (String.IsNullOrEmpty(strRecoverLevel) == true) strRecoverLevel = "Snapshot"; try { this.RecoverLevel = (RecoverLevel)Enum.Parse(typeof(RecoverLevel), strRecoverLevel, true); } catch (Exception ex) { this.AppendResultText("启动失败: 启动参数Param中的recoverLevel枚举值 '" + strRecoverLevel + "' 错误: " + ex.Message + "\r\n"); return; } this.App.WriteErrorLog("日志恢复 任务启动。"); if (bClearFirst == true) { nRet = this.App.ClearAllDbs(this.RmsChannels, out strError); if (nRet == -1) { this.AppendResultText("清除全部数据库记录时发生错误: " + strError + "\r\n"); return; } } bool bStart = false; if (String.IsNullOrEmpty(strStartFileName) == true) { // 做所有文件 bStart = true; } // 列出所有日志文件 DirectoryInfo di = new DirectoryInfo(this.App.OperLog.Directory); FileInfo[] fis = di.GetFiles("*.log"); // BUG!!! 以前缺乏排序。2008/2/1 Array.Sort(fis, new FileInfoCompare()); for (int i = 0; i < fis.Length; i++) { if (this.Stopped == true) break; string strFileName = fis[i].Name; this.AppendResultText("检查文件 " + strFileName + "\r\n"); if (bStart == false) { // 从特定文件开始做 if (string.CompareOrdinal(strStartFileName, strFileName) <= 0) // 2015/9/12 从等号修改为 Compare { bStart = true; if (lStartIndex < 0) lStartIndex = 0; // lStartIndex = Convert.ToInt64(startinfo.Param); } } if (bStart == true) { nRet = DoOneLogFile(strFileName, lStartIndex, out strError); if (nRet == -1) goto ERROR1; lStartIndex = 0; // 第一个文件以后的文件就全做了 } } this.AppendResultText("循环结束\r\n"); this.App.WriteErrorLog("日志恢复 任务结束。"); return; ERROR1: return; } finally { this.App.HangupReason = HangupReason.None; } }
// 一次操作循环 public override void Worker() { // 把系统挂起 // this.App.HangupReason = HangupReason.LogRecover; this.App.AddHangup("LogRecover"); try { string strError = ""; BatchTaskStartInfo startinfo = this.StartInfo; if (startinfo == null) startinfo = new BatchTaskStartInfo(); // 按照缺省值来 long lStartIndex = 0;// 开始位置 string strStartFileName = "";// 开始文件名 int nRet = ParseLogRecorverStart(startinfo.Start, out lStartIndex, out strStartFileName, out strError); if (nRet == -1) { this.AppendResultText("启动失败: " + strError + "\r\n"); return; } // string strRecoverLevel = ""; bool bClearFirst = false; bool bContinueWhenError = false; nRet = ParseLogRecoverParam(startinfo.Param, out strRecoverLevel, out bClearFirst, out bContinueWhenError, out strError); if (nRet == -1) { this.AppendResultText("启动失败: " + strError + "\r\n"); return; } if (String.IsNullOrEmpty(strRecoverLevel) == true) strRecoverLevel = "Snapshot"; try { this.RecoverLevel = (RecoverLevel)Enum.Parse(typeof(RecoverLevel), strRecoverLevel, true); } catch (Exception ex) { this.AppendResultText("启动失败: 启动参数Param中的recoverLevel枚举值 '" + strRecoverLevel + "' 错误: " + ex.Message + "\r\n"); return; } this.App.WriteErrorLog("日志恢复 任务启动。"); // 当为容错恢复级别时,检查当前全部读者库的检索点是否符合要求 if (this.RecoverLevel == LibraryServer.RecoverLevel.Robust) { // 检查全部读者库的检索途径,看是否满足都有“所借册条码号”这个检索途径的这个条件 // return: // -1 出错 // 0 不满足 // 1 满足 nRet = this.App.DetectReaderDbFroms(out strError); if (nRet == -1) { this.AppendResultText("检查读者库检索点时发生错误: " + strError + "\r\n"); return; } if (nRet == 0) { this.AppendResultText("在容错恢复级别下,当前读者库中有部分或全部读者库缺乏“所借册条码号”检索点,无法进行日志恢复。请按照日志恢复要求,刷新所有读者库的检索点配置,然后再进行日志恢复\r\n"); return; } } // TODO: 检查当前是否有 重建检索点 的后台任务正在运行,或者还有没有运行完的部分。 // 要求重建检索点的任务运行完以后才能执行日志恢复任务 if (bClearFirst == true) { nRet = this.App.ClearAllDbs(this.RmsChannels, out strError); if (nRet == -1) { this.AppendResultText("清除全部数据库记录时发生错误: " + strError + "\r\n"); return; } } bool bStart = false; if (String.IsNullOrEmpty(strStartFileName) == true) { // 做所有文件 bStart = true; } // 列出所有日志文件 DirectoryInfo di = new DirectoryInfo(this.App.OperLog.Directory); FileInfo[] fis = di.GetFiles("*.log"); // BUG!!! 以前缺乏排序。2008/2/1 Array.Sort(fis, new FileInfoCompare()); for (int i = 0; i < fis.Length; i++) { if (this.Stopped == true) break; string strFileName = fis[i].Name; this.AppendResultText("检查文件 " + strFileName + "\r\n"); if (bStart == false) { // 从特定文件开始做 if (string.CompareOrdinal(strStartFileName, strFileName) <= 0) // 2015/9/12 从等号修改为 Compare { bStart = true; if (lStartIndex < 0) lStartIndex = 0; // lStartIndex = Convert.ToInt64(startinfo.Param); } } if (bStart == true) { nRet = DoOneLogFile(strFileName, lStartIndex, bContinueWhenError, out strError); if (nRet == -1) goto ERROR1; lStartIndex = 0; // 第一个文件以后的文件就全做了 } } this.AppendResultText("循环结束\r\n"); this.App.WriteErrorLog("日志恢复 任务结束。"); return; ERROR1: return; } finally { // this.App.HangupReason = HangupReason.None; this.App.ClearHangup("LogRecover"); } }
/* <root> <operation>writeRes</operation> <requestResPath>...</requestResPath> 资源路径参数。也就是请求API是的strResPath参数值。可能在路径中的记录ID部分包含问号,表示要追加创建新的记录 <resPath>...</resPath> 资源路径。资源的确定路径。 <ranges>...</ranges> 字节范围 <totalLength>...</totalLength> 总长度 <metadata>...</metadata> 此元素的文本即是记录体,但注意为不透明的字符串(HtmlEncoding后的记录字符串)。 <style>...</style> 当 style 中包含 delete 子串时表示要删除这个资源 <operator>test</operator> <operTime>Fri, 08 Dec 2006 10:12:20 GMT</operTime> </root> * 可能会有一个attachment * * */ public int RecoverWriteRes( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, Stream attachmentLog, out string strError) { strError = ""; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; // int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } bool bReuse = false; // 是否能够不顾RecorverLevel状态而重用部分代码 DO_SNAPSHOT: // 快照恢复 if (level == RecoverLevel.Snapshot || bReuse == true) { string strResPath = DomUtil.GetElementText( domLog.DocumentElement, "resPath"); if (string.IsNullOrEmpty(strResPath) == true) { strError = "日志记录中缺<resPath>元素"; return -1; } string strRanges = DomUtil.GetElementText( domLog.DocumentElement, "ranges"); if (string.IsNullOrEmpty(strRanges) == true) { strError = "日志记录中缺<ranges>元素"; return -1; } string strTotalLength = DomUtil.GetElementText( domLog.DocumentElement, "totalLength"); if (string.IsNullOrEmpty(strTotalLength) == true) { strError = "日志记录中缺<totalLength>元素"; return -1; } long lTotalLength = 0; try { lTotalLength = Convert.ToInt64(strTotalLength); } catch { strError = "lTotalLength值 '"+strTotalLength+"' 格式不正确"; return -1; } string strMetadata = DomUtil.GetElementText( domLog.DocumentElement, "metadata"); string strStyle = DomUtil.GetElementText( domLog.DocumentElement, "style"); // 读入记录内容 byte[] baRecord = null; if (attachmentLog != null && attachmentLog.Length > 0) { baRecord = new byte[(int)attachmentLog.Length]; attachmentLog.Seek(0, SeekOrigin.Begin); attachmentLog.Read(baRecord, 0, (int)attachmentLog.Length); } strStyle += ",ignorechecktimestamp"; byte[] timestamp = null; string strOutputResPath = ""; byte[] output_timestamp = null; if (StringUtil.IsInList("delete", strStyle) == true) { // 2015/9/3 增加 lRet = channel.DoDeleteRes(strResPath, timestamp, strStyle, out output_timestamp, out strError); } else { lRet = channel.WriteRes(strResPath, strRanges, lTotalLength, baRecord, strMetadata, strStyle, timestamp, out strOutputResPath, out output_timestamp, out strError); } if (lRet == -1) { strError = "WriteRes() '" + strResPath + "' 时发生错误: " + strError; return -1; } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { // 和SnapShot方式相同 bReuse = true; goto DO_SNAPSHOT; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
/* <root> <operation>repairBorrowInfo</operation> <action>...</action> 具体动作 有 repairreaderside repairitemside <readerBarcode>...</readerBarcode> <itemBarcode>...</itemBarcode> <confirmItemRecPath>...</confirmItemRecPath> 辅助判断用的册记录路径 <operator>test</operator> <operTime>Fri, 08 Dec 2006 10:12:20 GMT</operTime> </root> * * * */ public int RecoverRepairBorrowInfo( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, Stream attachmentLog, out string strError) { strError = ""; int nRet = 0; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; // int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } bool bReuse = false; // 是否能够不顾RecorverLevel状态而重用部分代码 DO_SNAPSHOT: // 快照恢复 if (level == RecoverLevel.Snapshot || bReuse == true) { string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); string strReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "readerBarcode"); if (String.IsNullOrEmpty(strReaderBarcode) == true) { strError = "<readerBarcode>元素值为空"; goto ERROR1; } // 读入读者记录 string strReaderXml = ""; string strOutputReaderRecPath = ""; byte[] reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strReaderBarcode, out strReaderXml, out strOutputReaderRecPath, out reader_timestamp, out strError); if (nRet == 0) { if (strAction == "repairreaderside") { strError = "读者证条码号 '" + strReaderBarcode + "' 不存在"; goto ERROR1; } // 从实体侧恢复的时候,是允许读者记录不存在的 } if (nRet == -1) { strError = "读入证条码号为 '" + strReaderBarcode + "' 的读者记录时发生错误: " + strError; goto ERROR1; } XmlDocument readerdom = null; if (string.IsNullOrEmpty(strReaderXml) == false) { nRet = LibraryApplication.LoadToDom(strReaderXml, out readerdom, out strError); if (nRet == -1) { strError = "装载读者记录进入XML DOM时发生错误: " + strError; goto ERROR1; } } // 校验读者证条码号参数是否和XML记录中完全一致 if (readerdom != null) { string strTempBarcode = DomUtil.GetElementText(readerdom.DocumentElement, "barcode"); if (strReaderBarcode != strTempBarcode) { strError = "修复操作被拒绝。因读者证条码号参数 '" + strReaderBarcode + "' 和读者记录中<barcode>元素内的读者证条码号值 '" + strTempBarcode + "' 不一致。"; goto ERROR1; } } // 读入册记录 string strConfirmItemRecPath = DomUtil.GetElementText(domLog.DocumentElement, "confirmItemRecPath"); string strItemBarcode = DomUtil.GetElementText(domLog.DocumentElement, "itemBarcode"); if (String.IsNullOrEmpty(strItemBarcode) == true) { strError = "<strItemBarcode>元素值为空"; goto ERROR1; } string strItemXml = ""; string strOutputItemRecPath = ""; byte[] item_timestamp = null; // 如果已经有确定的册记录路径 if (String.IsNullOrEmpty(strConfirmItemRecPath) == false) { string strMetaData = ""; lRet = channel.GetRes(strConfirmItemRecPath, out strItemXml, out strMetaData, out item_timestamp, out strOutputItemRecPath, out strError); if (lRet == -1) { strError = "根据strConfirmItemRecPath '" + strConfirmItemRecPath + "' 获得册记录失败: " + strError; goto ERROR1; } // 需要检查记录中的<barcode>元素值是否匹配册条码号 // TODO: 如果记录路径所表达的记录不存在,或者其<barcode>元素值和要求的册条码号不匹配,那么都要改用逻辑方法,也就是利用册条码号来获得记录。 // 当然,这种情况下,非常要紧的是确保数据库的素质很好,本身没有重条码号的情况出现。 } else { // 从册条码号获得册记录 List<string> aPath = null; // 获得册记录 // return: // -1 error // 0 not found // 1 命中1条 // >1 命中多于1条 nRet = this.GetItemRecXml( // Channels, channel, strItemBarcode, out strItemXml, 100, out aPath, out item_timestamp, out strError); if (nRet == 0) { if (strAction == "repairitemside") { strError = "册条码号 '" + strItemBarcode + "' 不存在"; goto ERROR1; } // 从读者侧恢复的时候,册条码号不存在是允许的 goto CONTINUE_REPAIR; } if (nRet == -1) { strError = "读入册条码号为 '" + strItemBarcode + "' 的册记录时发生错误: " + strError; goto ERROR1; } if (aPath.Count > 1) { strError = "册条码号为 '" + strItemBarcode + "' 的册记录有 " + aPath.Count.ToString() + " 条,但此时comfirmItemRecPath却为空"; goto ERROR1; } else { Debug.Assert(nRet == 1, ""); Debug.Assert(aPath.Count == 1, ""); if (nRet == 1) { strOutputItemRecPath = aPath[0]; } } } CONTINUE_REPAIR: XmlDocument itemdom = null; if (string.IsNullOrEmpty(strItemXml) == false) { nRet = LibraryApplication.LoadToDom(strItemXml, out itemdom, out strError); if (nRet == -1) { strError = "装载册记录进入XML DOM时发生错误: " + strError; goto ERROR1; } // 校验册条码号参数是否和XML记录中完全一致 string strTempItemBarcode = DomUtil.GetElementText(itemdom.DocumentElement, "barcode"); if (strItemBarcode != strTempItemBarcode) { strError = "修复操作被拒绝。因册条码号参数 '" + strItemBarcode + "' 和册记录中<barcode>元素内的册条码号值 '" + strTempItemBarcode + "' 不一致。"; goto ERROR1; } } if (strAction == "repairreaderside") { XmlNode nodeBorrow = readerdom.DocumentElement.SelectSingleNode("borrows/borrow[@barcode='" + strItemBarcode + "']"); if (nodeBorrow == null) { strError = "修复操作被拒绝。读者记录 " + strReaderBarcode + " 中并不存在有关册 " + strItemBarcode + " 的借阅信息。"; goto ERROR1; } if (itemdom != null) { // 看看册记录中是否有指回读者记录的链 string strBorrower = DomUtil.GetElementText(itemdom.DocumentElement, "borrower"); if (strBorrower == strReaderBarcode) { strError = "修复操作被拒绝。您所请求要修复的链,本是一条完整正确的链。可直接进行普通还书操作。"; goto ERROR1; } } // 移除读者记录侧的链 nodeBorrow.ParentNode.RemoveChild(nodeBorrow); byte[] output_timestamp = null; string strOutputPath = ""; // 写回读者记录 lRet = channel.DoSaveTextRes(strOutputReaderRecPath, readerdom.OuterXml, false, "content,ignorechecktimestamp", reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } else if (strAction == "repairitemside") { // 看看册记录中是否有指向读者记录的链 string strBorrower = DomUtil.GetElementText(itemdom.DocumentElement, "borrower"); if (String.IsNullOrEmpty(strBorrower) == true) { strError = "修复操作被拒绝。您所请求要修复的册记录中,本来就没有借阅信息,因此谈不上修复。"; goto ERROR1; } if (strBorrower != strReaderBarcode) { strError = "修复操作被拒绝。您所请求要修复的册记录中,并没有指明借阅者是读者 " + strReaderBarcode + "。"; goto ERROR1; } // 看看读者记录中是否有指回链条。 if (readerdom != null) { XmlNode nodeBorrow = readerdom.DocumentElement.SelectSingleNode("borrows/borrow[@barcode='" + strItemBarcode + "']"); if (nodeBorrow != null) { strError = "修复操作被拒绝。您所请求要修复的链,本是一条完整正确的链。可直接进行普通还书操作。"; goto ERROR1; } } // 移除册记录侧的链 DomUtil.SetElementText(itemdom.DocumentElement, "borrower", ""); DomUtil.SetElementText(itemdom.DocumentElement, "borrowDate", ""); DomUtil.SetElementText(itemdom.DocumentElement, "borrowPeriod", ""); byte[] output_timestamp = null; string strOutputPath = ""; // 写回册记录 lRet = channel.DoSaveTextRes(strOutputItemRecPath, itemdom.OuterXml, false, "content,ignorechecktimestamp", item_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } else { strError = "不可识别的strAction值 '"+strAction+"'"; goto ERROR1; } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { // 和SnapShot方式相同 bReuse = true; goto DO_SNAPSHOT; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
/* foregift 创建押金记录 API: Foregift() <root> <operation>foregift</operation> 操作类型 <action>...</action> 具体动作 目前有foregift return (注: return操作时,overdue元素里面的price属性,可以使用宏 %return_foregift_price% 表示当前剩余的押金额) <readerBarcode>R0000002</readerBarcode> 读者证条码号 <operator>test</operator> 操作者 <operTime>Fri, 08 Dec 2006 04:17:45 GMT</operTime> 操作时间 <overdues>...</overdues> 押金信息 通常内容为一个字符串,为一个或多个<overdue>元素XML文本片断 <readerRecord recPath='...'>...</readerRecord> 最新读者记录 </root> * * */ public int RecoverForegift( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, out string strError) { strError = ""; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } DO_SNAPSHOT: // 快照恢复 if (level == RecoverLevel.Snapshot) { XmlNode node = null; string strReaderXml = DomUtil.GetElementText(domLog.DocumentElement, "readerRecord", out node); if (node == null) { strError = "日志记录中缺<readerRecord>元素"; return -1; } string strReaderRecPath = DomUtil.GetAttr(node, "recPath"); byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; // 写读者记录 lRet = channel.DoSaveTextRes(strReaderRecPath, strReaderXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入读者记录 '" + strReaderRecPath + "' 时发生错误: " + strError; return -1; } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { // string strRecoverComment = ""; string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); string strReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "readerBarcode"); /// if (String.IsNullOrEmpty(strReaderBarcode) == true) { strError = "日志记录中<readerBarcode>元素值为空"; goto ERROR1; } string strOperator = DomUtil.GetElementText(domLog.DocumentElement, "operator"); string strOperTime = DomUtil.GetElementText(domLog.DocumentElement, "operTime"); string strOverdues = DomUtil.GetElementText(domLog.DocumentElement, "overdues"); if (String.IsNullOrEmpty(strOverdues) == true) { strError = "日志记录中<overdues>元素值为空"; goto ERROR1; } // 从overdues字符串中分析出id XmlDocument tempdom = new XmlDocument(); tempdom.LoadXml("<root />"); XmlDocumentFragment fragment = tempdom.CreateDocumentFragment(); fragment.InnerXml = strOverdues; tempdom.DocumentElement.AppendChild(fragment); XmlNode tempnode = tempdom.DocumentElement.SelectSingleNode("overdue"); if (tempnode == null) { strError = "<overdues>元素内容有误,缺乏<overdue>元素"; goto ERROR1; } string strID = DomUtil.GetAttr(tempnode, "id"); if (String.IsNullOrEmpty(strID) == true) { strError = "日志记录中<overdues>内容中<overdue>元素中id属性值为空"; goto ERROR1; } // 读入读者记录 string strReaderXml = ""; string strOutputReaderRecPath = ""; byte[] reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strReaderBarcode, out strReaderXml, out strOutputReaderRecPath, out reader_timestamp, out strError); if (nRet == 0) { strError = "读者证条码号 '" + strReaderBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入证条码号为 '" + strReaderBarcode + "' 的读者记录时发生错误: " + strError; goto ERROR1; } XmlDocument readerdom = null; nRet = LibraryApplication.LoadToDom(strReaderXml, out readerdom, out strError); if (nRet == -1) { strError = "装载读者记录进入XML DOM时发生错误: " + strError; goto ERROR1; } // string strOverdueString = ""; // 根据Foregift() API要求,修改readerdom nRet = DoForegift(strAction, readerdom, ref strID, strOperator, strOperTime, out strOverdueString, out strError); if (nRet == -1) goto ERROR1; // 写回读者、册记录 byte[] output_timestamp = null; string strOutputPath = ""; // 写回读者记录 lRet = channel.DoSaveTextRes(strOutputReaderRecPath, readerdom.OuterXml, false, "content,ignorechecktimestamp", reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
/* settlement 结算违约金 API: Settlement() <root> <operation>settlement</operation> 操作类型 <action>...</action> 具体动作 有settlement undosettlement delete 3种 <id>1234567-1</id> ID <operator>test</operator> 操作者 <operTime>Fri, 08 Dec 2006 04:17:45 GMT</operTime> 操作时间 <oldAmerceRecord recPath='...'>...</oldAmerceRecord> 旧违约金记录 <amerceRecord recPath='...'>...</amerceRecord> 新违约金记录 delete操作无此元素 </root> * */ public int RecoverSettlement( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, out string strError) { strError = ""; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } DO_SNAPSHOT: // 快照恢复 if (level == RecoverLevel.Snapshot) { string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); if (strAction == "settlement" || strAction == "undosettlement") { XmlNode node = null; string strAmerceXml = DomUtil.GetElementText(domLog.DocumentElement, "amerceRecord", out node); if (node == null) { strError = "日志记录中缺<amerceRecord>元素"; return -1; } string strAmerceRecPath = DomUtil.GetAttr(node, "recPath"); byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; // 写违约金记录 lRet = channel.DoSaveTextRes(strAmerceRecPath, strAmerceXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入违约金记录 '" + strAmerceRecPath + "' 时发生错误: " + strError; return -1; } } else if (strAction == "delete") { XmlNode node = null; string strOldAmerceXml = DomUtil.GetElementText(domLog.DocumentElement, "oldAmerceRecord", out node); if (node == null) { strError = "日志记录中缺<oldAmerceRecord>元素"; return -1; } string strOldAmerceRecPath = DomUtil.GetAttr(node, "recPath"); // 删除违约金记录 int nRedoCount = 0; byte[] timestamp = null; byte[] output_timestamp = null; REDO_DELETE: lRet = channel.DoDeleteRes(strOldAmerceRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) return 0; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO_DELETE; } } strError = "删除违约金记录 '" + strOldAmerceRecPath + "' 时发生错误: " + strError; return -1; } } else { strError = "未能识别的action值 '" + strAction + "'"; } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); string strID = DomUtil.GetElementText(domLog.DocumentElement, "id"); /// if (String.IsNullOrEmpty(strID) == true) { strError = "日志记录中<id>元素值为空"; goto ERROR1; } string strOperator = DomUtil.GetElementText(domLog.DocumentElement, "operator"); string strOperTime = DomUtil.GetElementText(domLog.DocumentElement, "operTime"); // 通过id获得违约金记录的路径 string strText = ""; string strCount = ""; strCount = "<maxCount>100</maxCount>"; strText = "<item><word>" + StringUtil.GetXmlStringSimple(strID) + "</word>" + strCount + "<match>exact</match><relation>=</relation><dataType>string</dataType>" + "</item>"; string strQueryXml = "<target list='" + StringUtil.GetXmlStringSimple(this.AmerceDbName + ":" + "ID") // 2007/9/14 + "'>" + strText + "<lang>zh</lang></target>"; lRet = channel.DoSearch(strQueryXml, "amerced", "", // strOuputStyle out strError); if (lRet == -1) { strError = "检索ID为 '" + strID + "' 的违约金记录出错: " + strError; goto ERROR1; } if (lRet == 0) { strError = "没有找到id为 '" + strID + "' 的违约金记录"; goto ERROR1; } List<string> aPath = null; lRet = channel.DoGetSearchResult( "amerced", // strResultSetName 0, 1, "zh", null, // stop out aPath, out strError); if (lRet == -1) goto ERROR1; if (lRet == 0) { strError = "获取结果集未命中"; goto ERROR1; } if (aPath.Count != 1) { strError = "aPath.Count != 1"; goto ERROR1; } string strAmerceRecPath = aPath[0]; // 结算一个交费记录 // parameters: // bCreateOperLog 是否创建日志 // strOperTime 结算的操作时间 // strOperator 结算的操作者 // return: // -2 致命出错,不宜再继续循环调用本函数 // -1 一般出错,可以继续循环调用本函数 // 0 正常 nRet = SettlementOneRecord( "", // 确保可以执行 false, // 不创建日志 channel, strAction, strAmerceRecPath, strOperTime, strOperator, "", // 表示本机触发 out strError); if (nRet == -1 || nRet == -2) goto ERROR1; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
/* <root> <operation>devolveReaderInfo</operation> <sourceReaderBarcode>...</sourceReaderBarcode> 源读者证条码号 <targetReaderBarcode>...</targetReaderBarcode> 目标读者证条码号 <borrows>...</borrows> 移动过去的<borrows>内容,下级为<borrow>元素 <overdues>...</overdues> 移动过去的<overdue>内容,下级为<overdue>元素 <sourceReaderRecord recPath='...'>...</sourceReaderRecord> 最新源读者记录 <targetReaderRecord recPath='...'>...</targetReaderRecord> 最新目标读者记录 <changedEntityRecord recPath='...' attahchmentIndex='.'>...</changedEntityRecord> 所牵连到的发生了修改的实体记录。此元素的文本即是记录体,但注意为不透明的字符串(HtmlEncoding后的记录字符串)。如果存在attachmentIndex属性,则表明实体记录不在此元素文本中,而在日志记录的附件中 <operator>test</operator> <operTime>Fri, 08 Dec 2006 10:12:20 GMT</operTime> </root> * * */ public int RecoverDevolveReaderInfo( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, Stream attachmentLog, out string strError) { strError = ""; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } DO_SNAPSHOT: // 快照恢复 if (level == RecoverLevel.Snapshot) { /* // 观察是否有<warning>元素 XmlNode nodeWarning = domLog.SelectSingleNode("warning"); if (nodeWarning != null) { // 如果<warning元素存在,表明只能采用逻辑恢复> strError = nodeWarning.InnerText; return -1; } */ // 获源读者记录 XmlNode node = null; string strSourceReaderXml = DomUtil.GetElementText( domLog.DocumentElement, "sourceReaderRecord", out node); if (node == null) { strError = "日志记录中缺<sourceReaderRecord>元素"; return -1; } string strSourceReaderRecPath = DomUtil.GetAttr(node, "recPath"); byte[] timestamp = null; string strOutputPath = ""; byte[] output_timestamp = null; // 写源读者记录 lRet = channel.DoSaveTextRes(strSourceReaderRecPath, strSourceReaderXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入源读者记录 '" + strSourceReaderRecPath + "' 时发生错误: " + strError; return -1; } // 获目标读者记录 node = null; string strTargetReaderXml = DomUtil.GetElementText( domLog.DocumentElement, "targetReaderRecord", out node); if (node == null) { strError = "日志记录中缺<targetReaderRecord>元素"; return -1; } string strTargetReaderRecPath = DomUtil.GetAttr(node, "recPath"); // 写目标读者记录 lRet = channel.DoSaveTextRes(strTargetReaderRecPath, strTargetReaderXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入目标读者记录 '" + strSourceReaderRecPath + "' 时发生错误: " + strError; return -1; } // 循环,写入相关的若干实体记录 XmlNodeList nodeEntities = domLog.DocumentElement.SelectNodes("changedEntityRecord"); for (int i = 0; i < nodeEntities.Count; i++) { XmlNode nodeEntity = nodeEntities[i]; string strItemRecPath = DomUtil.GetAttr(nodeEntity, "recPath"); string strAttachmentIndex = DomUtil.GetAttr(nodeEntity, "attachmentIndex"); string strItemXml = ""; if (String.IsNullOrEmpty(strAttachmentIndex) == true) { strItemXml = nodeEntity.InnerText; if (String.IsNullOrEmpty(strItemXml) == true) { strError = "<changedEntityRecord>元素缺乏文本内容。"; return -1; } } else { // 实体记录在附件中 int nAttachmentIndex = 0; try { nAttachmentIndex = Convert.ToInt32(strAttachmentIndex); } catch { strError = "<changedEntityRecord>元素的attachmentIndex属性值'"+strAttachmentIndex+"'格式不正确,应当为>=0的纯数字"; return -1; } byte[] baItem = null; nRet = GetAttachmentRecord( attachmentLog, nAttachmentIndex, out baItem, out strError); if (nRet == -1) { strError = "获得 index 为 "+nAttachmentIndex.ToString()+" 的日志附件记录时出错:" + strError; return -1; } strItemXml = Encoding.UTF8.GetString(baItem); } // 写册记录 lRet = channel.DoSaveTextRes(strItemRecPath, strItemXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入册记录 '" + strItemRecPath + "' 时发生错误: " + strError; return -1; } } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { string strOperTimeString = DomUtil.GetElementText(domLog.DocumentElement, "operTime"); string strSourceReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "sourceReaderBarcode"); if (String.IsNullOrEmpty(strSourceReaderBarcode) == true) { strError = "<sourceReaderBarcode>元素值为空"; goto ERROR1; } string strTargetReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "targetReaderBarcode"); if (String.IsNullOrEmpty(strTargetReaderBarcode) == true) { strError = "<targetReaderBarcode>元素值为空"; goto ERROR1; } // 读入源读者记录 string strSourceReaderXml = ""; string strSourceOutputReaderRecPath = ""; byte[] source_reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strSourceReaderBarcode, out strSourceReaderXml, out strSourceOutputReaderRecPath, out source_reader_timestamp, out strError); if (nRet == 0) { strError = "源读者证条码号 '" + strSourceReaderBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入证条码号为 '" + strSourceReaderBarcode + "' 的源读者记录时发生错误: " + strError; goto ERROR1; } XmlDocument source_readerdom = null; nRet = LibraryApplication.LoadToDom(strSourceReaderXml, out source_readerdom, out strError); if (nRet == -1) { strError = "装载源读者记录进入XML DOM时发生错误: " + strError; goto ERROR1; } // // 读入目标读者记录 string strTargetReaderXml = ""; string strTargetOutputReaderRecPath = ""; byte[] target_reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strTargetReaderBarcode, out strTargetReaderXml, out strTargetOutputReaderRecPath, out target_reader_timestamp, out strError); if (nRet == 0) { strError = "目标读者证条码号 '" + strTargetReaderBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入证条码号为 '" + strTargetReaderBarcode + "' 的目标读者记录时发生错误: " + strError; goto ERROR1; } XmlDocument target_readerdom = null; nRet = LibraryApplication.LoadToDom(strTargetReaderXml, out target_readerdom, out strError); if (nRet == -1) { strError = "装载目标读者记录进入XML DOM时发生错误: " + strError; goto ERROR1; } Stream tempstream = null; // 移动信息 XmlDocument domTemp = null; // 移动借阅信息 -- <borrows>元素内容 // return: // -1 error // 0 not found brrowinfo // 1 found and moved nRet = DevolveBorrowInfo( // Channels, channel, strSourceReaderBarcode, strTargetReaderBarcode, strOperTimeString, ref source_readerdom, ref target_readerdom, ref domTemp, "", out tempstream, out strError); if (nRet == -1) goto ERROR1; // 移动超期违约金信息 -- <overdues>元素内容 // return: // -1 error // 0 not found overdueinfo // 1 found and moved nRet = DevolveOverdueInfo( strSourceReaderBarcode, strTargetReaderBarcode, strOperTimeString, ref source_readerdom, ref target_readerdom, ref domTemp, out strError); if (nRet == -1) goto ERROR1; // 写回读者记录 byte[] output_timestamp = null; string strOutputPath = ""; // 写回源读者记录 lRet = channel.DoSaveTextRes(strSourceOutputReaderRecPath, source_readerdom.OuterXml, false, "content,ignorechecktimestamp", source_reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; // 写回目标读者记录 lRet = channel.DoSaveTextRes(strTargetOutputReaderRecPath, target_readerdom.OuterXml, false, "content,ignorechecktimestamp", source_reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
// SetEntities() API 恢复动作 /* 日志记录格式 <root> <operation>setEntity</operation> 操作类型 <action>new</action> 具体动作。有new change delete 3种 <style>...</style> 风格。有force nocheckdup noeventlog 3种 <record recPath='中文图书实体/3'><root><parent>2</parent><barcode>0000003</barcode><state>状态2</state><location>阅览室</location><price></price><bookType>教学参考</bookType><registerNo></registerNo><comment>test</comment><mergeComment></mergeComment><batchNo>111</batchNo><borrower></borrower><borrowDate></borrowDate><borrowPeriod></borrowPeriod></root></record> 记录体 <oldRecord recPath='中文图书实体/3'>...</oldRecord> 被覆盖或者删除的记录 动作为change和delete时具备此元素 <operator>test</operator> 操作者 <operTime>Fri, 08 Dec 2006 08:41:46 GMT</operTime> 操作时间 </root> 注:1) 当<action>为delete时,没有<record>元素。为new时,没有<oldRecord>元素。 2) <record>中的内容, 涉及到流通的<borrower><borrowDate><borrowPeriod>等, 在日志恢复阶段, 都应当无效, 这几个内容应当从当前位置库中记录获取, 和<record>中其他内容合并后, 再写入数据库 3) 一次SetEntities()API调用, 可能创建多条日志记录。 * */ // TODO: 要兑现style中force nocheckdup功能 public int RecoverSetEntity( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, out string strError) { strError = ""; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } bool bReuse = false; // 是否能够不顾RecorverLevel状态而重用部分代码 DO_SNAPSHOT: string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); // 快照恢复 if (level == RecoverLevel.Snapshot || bReuse == true) { byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; if (strAction == "new" || strAction == "change" || strAction == "move") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; return -1; } string strNewRecPath = DomUtil.GetAttr(node, "recPath"); // string strOldRecord = ""; string strOldRecPath = ""; if (strAction == "move") { strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } strOldRecPath = DomUtil.GetAttr(node, "recPath"); } // 写册记录 lRet = channel.DoSaveTextRes(strNewRecPath, strRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入册记录 '" + strNewRecPath + "' 时发生错误: " + strError; return -1; } if (strAction == "move") { // 删除册记录 int nRedoCount = 0; REDO_DELETE: lRet = channel.DoDeleteRes(strOldRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) return 0; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO_DELETE; } } strError = "删除册记录 '" + strOldRecPath + "' 时发生错误: " + strError; return -1; } } } else if (strAction == "delete") { XmlNode node = null; string strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); int nRedoCount = 0; REDO: // 删除册记录 lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) return 0; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO; } } strError = "删除册记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } } else { strError = "无法识别的<action>内容 '" + strAction + "'"; return -1; } return 0; } bool bForce = false; bool bNoCheckDup = false; string strStyle = DomUtil.GetElementText(domLog.DocumentElement, "style"); if (StringUtil.IsInList("force", strStyle) == true) bForce = true; if (StringUtil.IsInList("nocheckdup", strStyle) == true) bNoCheckDup = true; // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { // 和数据库中已有记录合并,然后保存 if (strAction == "new" || strAction == "change" || strAction == "move") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; return -1; } string strNewRecPath = DomUtil.GetAttr(node, "recPath"); // string strOldRecord = ""; string strOldRecPath = ""; if (strAction == "move") { strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } strOldRecPath = DomUtil.GetAttr(node, "recPath"); } // 读出数据库中原有的记录 string strExistXml = ""; string strMetaData = ""; byte[] exist_timestamp = null; string strOutputPath = ""; if ((strAction == "change" || strAction == "move") && bForce == false) // 2008/10/6 { string strSourceRecPath = ""; if (strAction == "change") strSourceRecPath = strNewRecPath; if (strAction == "move") strSourceRecPath = strOldRecPath; lRet = channel.GetRes(strSourceRecPath, out strExistXml, out strMetaData, out exist_timestamp, out strOutputPath, out strError); if (lRet == -1) { // 容错 if (channel.ErrorCode == ChannelErrorCode.NotFound && level == RecoverLevel.LogicAndSnapshot) { // 如果记录不存在, 则构造一条空的记录 // bExist = false; strExistXml = "<root />"; exist_timestamp = null; } else { strError = "在读入原有记录 '"+strNewRecPath+"' 时失败: " + strError; goto ERROR1; } } } // // 把两个记录装入DOM XmlDocument domExist = new XmlDocument(); XmlDocument domNew = new XmlDocument(); try { // 防范空记录 if (String.IsNullOrEmpty(strExistXml) == true) strExistXml = "<root />"; domExist.LoadXml(strExistXml); } catch (Exception ex) { strError = "strExistXml装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } try { domNew.LoadXml(strRecord); } catch (Exception ex) { strError = "strRecord装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } // 合并新旧记录 string strNewXml = ""; if (bForce == false) { nRet = MergeTwoEntityXml(domExist, domNew, out strNewXml, out strError); if (nRet == -1) goto ERROR1; } else { strNewXml = domNew.OuterXml; } // 保存新记录 byte[] output_timestamp = null; if (strAction == "move") { // 复制源记录到目标位置,然后自动删除源记录 // 但是尚未在目标位置写入最新内容 lRet = channel.DoCopyRecord(strOldRecPath, strNewRecPath, true, // bDeleteSourceRecord out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; exist_timestamp = output_timestamp; // 及时更新时间戳 } lRet = channel.DoSaveTextRes(strNewRecPath, strNewXml, false, // include preamble? "content,ignorechecktimestamp", exist_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; /* if (strAction == "move") { // 删除册记录 int nRedoCount = 0; byte[] timestamp = null; REDO_DELETE: lRet = channel.DoDeleteRes(strOldRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) return 0; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO_DELETE; } } strError = "删除册记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } } * */ } else if (strAction == "delete") { // 和SnapShot方式相同 bReuse = true; goto DO_SNAPSHOT; } else { strError = "无法识别的<action>内容 '" + strAction + "'"; return -1; } } // 容错恢复 if (level == RecoverLevel.Robust) { if (strAction == "move") { strError = "暂不支持SetEntity的move恢复操作"; return -1; } // 和数据库中已有记录合并,然后保存 if (strAction == "change" || strAction == "new") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; return -1; } // 取得日志记录中声称的新记录路径。不能轻易相信这个路径。 string strNewRecPath = DomUtil.GetAttr(node, "recPath"); // string strOldRecord = ""; string strOldRecPath = ""; string strOldItemBarcode = ""; string strNewItemBarcode = ""; string strExistXml = ""; byte[] exist_timestamp = null; // 日志记录中记载的旧记录体 strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { if (strAction == "change") { strError = "日志记录中缺<oldRecord>元素"; return -1; } } // 日志记录中声称的旧记录路径。不能轻易相信这个路径。 if (node != null) strOldRecPath = DomUtil.GetAttr(node, "recPath"); // 从日志记录中记载的旧记录体中,获得旧记录册条码号 if (String.IsNullOrEmpty(strOldRecord) == false) { nRet = GetItemBarcode(strOldRecord, out strOldItemBarcode, out strError); } nRet = GetItemBarcode(strRecord, out strNewItemBarcode, out strError); // TODO: 需要检查新旧记录中,<barcode>是否一致?如果不一致,则需要对新条码号进行查重? if (strAction == "new" && strOldItemBarcode == "") { if (String.IsNullOrEmpty(strNewItemBarcode) == true) { strError = "因为拟新创建的记录内容中没有包含册条码号,所以new操作被放弃"; return -1; } strOldItemBarcode = strNewItemBarcode; } // 如果有旧记录的册条码号,则需要从数据库中提取最新鲜的旧记录 // (如果没有旧记录的册条码号,则依日志记录中的旧记录) if (String.IsNullOrEmpty(strOldItemBarcode) == false) { string strOutputItemRecPath = ""; // 从册条码号获得册记录 List<string> aPath = null; // 获得册记录 // return: // -1 error // 0 not found // 1 命中1条 // >1 命中多于1条 nRet = this.GetItemRecXml( // Channels, channel, strOldItemBarcode, out strExistXml, 100, out aPath, out exist_timestamp, out strError); if (nRet == 0 || nRet == -1) { if (strAction == "change") { /* // 从库中没有找到,只好依日志记录中记载的旧记录 strExistXml = strOldRecord; * */ strExistXml = ""; // 需要创建一条新记录。strOldRecPath中的路径似乎也可以用,但是要严格检查这个路径是否已经存在记录 -- 只能在这里位置不存在记录时才能用。既然如此麻烦,那就不如纯粹用一个新位置 strOutputItemRecPath = ResPath.GetDbName(strOldRecPath) + "/?"; } else { Debug.Assert(strAction == "new", ""); strExistXml = ""; strOutputItemRecPath = ResPath.GetDbName(strNewRecPath) + "/?"; } } else { // 找到一条或者多条旧记录 Debug.Assert(aPath != null && aPath.Count >= 1, ""); bool bNeedReload = false; if (aPath.Count == 1) { Debug.Assert(nRet == 1, ""); strOutputItemRecPath = aPath[0]; // 是否需要重新装载? bNeedReload = false; // 所取得的第一个路径,其记录已经装载 } else { // 多条 Debug.Assert(aPath.Count > 1, ""); /// // 建议根据strOldRecPath来进行挑选 if (String.IsNullOrEmpty(strOldRecPath) == true) { // 空,无法挑选 // 容错! strOutputItemRecPath = aPath[0]; // 是否需要重新装载? bNeedReload = false; // 所取得的第一个路径,其记录已经装载 } else { ///// nRet = aPath.IndexOf(strOldRecPath); if (nRet != -1) { // 选中 strOutputItemRecPath = aPath[nRet]; // 是否需要重新装载? if (nRet != 0) bNeedReload = true; // 第一个以外的路径才需要装载 } else { // 没有选中,只好依第一个 // 容错 strOutputItemRecPath = aPath[0]; // 是否需要重新装载? bNeedReload = false; // 所取得的第一个路径,其记录已经装载 } } /// } // 重新装载 if (bNeedReload == true) { string strMetaData = ""; lRet = channel.GetRes(strOutputItemRecPath, out strExistXml, out strMetaData, out exist_timestamp, out strOutputItemRecPath, out strError); if (lRet == -1) { strError = "根据strOutputItemRecPath '" + strOutputItemRecPath + "' 重新获得册记录失败: " + strError; return -1; } // 需要检查记录中的<barcode>元素值是否匹配册条码号 } } // 修正strOldRecPath if (strOutputItemRecPath != "") strOldRecPath = strOutputItemRecPath; else strOldRecPath = ""; // 破坏掉,以免后面被用 strNewRecPath = strOutputItemRecPath; } // end if 如果有旧记录的册条码号 else { // (如果没有旧记录的册条码号,则依日志记录中的旧记录) // 但无法确定旧记录的路径。也就无法确定覆盖位置。因此建议放弃这种特定的“修改操作”。 strError = "因为日志记录中没有记载旧记录条码号,因此无法确定记录位置,因此change操作被放弃"; return -1; } if (strAction == "change") { if (strNewItemBarcode != "" && strNewItemBarcode != strOldItemBarcode) { // 新旧记录的条码号不一致,需要对新条码号进行查重 List<string> aPath = null; string strTempXml = ""; byte[] temp_timestamp = null; // 获得册记录 // return: // -1 error // 0 not found // 1 命中1条 // >1 命中多于1条 nRet = this.GetItemRecXml( // Channels, channel, strNewItemBarcode, out strTempXml, 100, out aPath, out temp_timestamp, out strError); if (nRet > 0) { // 有重复,取其第一条,作为老记录进行合并,并保存回这条的位置 strNewRecPath = aPath[0]; exist_timestamp = temp_timestamp; strExistXml = strTempXml; } } } // 把两个记录装入DOM XmlDocument domExist = new XmlDocument(); XmlDocument domNew = new XmlDocument(); try { // 防范空记录 if (String.IsNullOrEmpty(strExistXml) == true) strExistXml = "<root />"; domExist.LoadXml(strExistXml); } catch (Exception ex) { strError = "strExistXml装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } try { domNew.LoadXml(strRecord); } catch (Exception ex) { strError = "strRecord装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } // 合并新旧记录 string strNewXml = ""; if (bForce == false) { nRet = MergeTwoEntityXml(domExist, domNew, out strNewXml, out strError); if (nRet == -1) goto ERROR1; } else { strNewXml = domNew.OuterXml; } // 保存新记录 byte[] output_timestamp = null; string strOutputPath = ""; if (strAction == "move") { // 复制源记录到目标位置,然后自动删除源记录 // 但是尚未在目标位置写入最新内容 lRet = channel.DoCopyRecord(strOldRecPath, strNewRecPath, true, // bDeleteSourceRecord out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; exist_timestamp = output_timestamp; // 及时更新时间戳 } /* // 测试 { string strRecID = ResPath.GetRecordId(strNewRecPath); if (strRecID != "?") { try { long id = Convert.ToInt64(strRecID); if (id > 150848) { Debug.Assert(false, "id超过尾部"); } } catch { } } } * */ lRet = channel.DoSaveTextRes(strNewRecPath, strNewXml, false, // include preamble? "content,ignorechecktimestamp", exist_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } else if (strAction == "delete") { XmlNode node = null; string strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); string strOldItemBarcode = ""; nRet = GetItemBarcode(strOldRecord, out strOldItemBarcode, out strError); if (String.IsNullOrEmpty(strOldItemBarcode) == true) { strError = "因为日志记录中的旧记录中缺乏非空的<barcode>内容,所以无法进行依据条码号定位的删除,delete操作被放弃"; return -1; } string strOutputItemRecPath = ""; string strExistXml = ""; byte[] exist_timestamp = null; // 从册条码号获得册记录 List<string> aPath = null; // 获得册记录 // return: // -1 error // 0 not found // 1 命中1条 // >1 命中多于1条 nRet = this.GetItemRecXml( // Channels, channel, strOldItemBarcode, out strExistXml, 100, out aPath, out exist_timestamp, out strError); if (nRet == -1) return -1; if (nRet == 0) { // 本来就不存在 return 0; } if (nRet >= 1) { /// // 找到一条或者多条旧记录 Debug.Assert(aPath != null && aPath.Count >= 1, ""); bool bNeedReload = false; if (aPath.Count == 1) { Debug.Assert(nRet == 1, ""); /* strOutputItemRecPath = aPath[0]; // 是否需要重新装载? bNeedReload = false; // 所取得的第一个路径,其记录已经装载 * */ strError = "册条码号 " + strOldItemBarcode + " 目前仅有唯一一条记录,放弃删除"; return -1; } else { // 多条 Debug.Assert(aPath.Count > 1, ""); /// // 建议根据strRecPath来进行挑选 if (String.IsNullOrEmpty(strRecPath) == true) { strError = "册条码号 '" + strOldItemBarcode + "' 命中 " + aPath.Count.ToString() + " 条记录,而<oldRecord>的recPath参数缺乏,因此无法进行精确删除,delete操作被放弃"; return -1; } else { ///// nRet = aPath.IndexOf(strRecPath); if (nRet != -1) { // 选中 strOutputItemRecPath = aPath[nRet]; // 是否需要重新装载? if (nRet != 0) bNeedReload = true; // 第一个以外的路径才需要装载 } else { strError = "册条码号 '" + strOldItemBarcode + "' 命中 " + aPath.Count.ToString() + " 条记录,虽用了(<oldRecord>元素中属性recPath的)确认路径 '" + strRecPath + "' 也无法确认出其中一条,无法精确删除,因此delete操作被放弃"; return -1; } } } /// // 重新装载 if (bNeedReload == true) { string strMetaData = ""; lRet = channel.GetRes(strOutputItemRecPath, out strExistXml, out strMetaData, out exist_timestamp, out strOutputItemRecPath, out strError); if (lRet == -1) { strError = "根据strOutputItemRecPath '" + strOutputItemRecPath + "' 重新获得册记录失败: " + strError; return -1; } // 需要检查记录中的<barcode>元素值是否匹配册条码号 } } // 把两个记录装入DOM XmlDocument domExist = new XmlDocument(); try { // 防范空记录 if (String.IsNullOrEmpty(strExistXml) == true) strExistXml = "<root />"; domExist.LoadXml(strExistXml); } catch (Exception ex) { strError = "strExistXml装载进入DOM时发生错误: " + ex.Message; return -1; } string strDetail = ""; bool bHasCirculationInfo = IsEntityHasCirculationInfo(domExist, out strDetail); // 观察已经存在的记录是否有流通信息 if (bHasCirculationInfo == true && bForce == false) { strError = "拟删除的册记录 '" + strOutputItemRecPath + "' 中包含有流通信息("+strDetail+"),不能删除。"; goto ERROR1; } int nRedoCount = 0; byte[] timestamp = exist_timestamp; byte[] output_timestamp = null; REDO: // 删除册记录 lRet = channel.DoDeleteRes(strOutputItemRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) return 0; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO; } } strError = "删除册记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } } else { strError = "无法识别的<action>内容 '" + strAction + "'"; return -1; } } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
// Borrow() API 恢复动作 /* 日志记录格式如下 <root> <operation>borrow</operation> 操作类型 <readerBarcode>R0000002</readerBarcode> 读者证条码号 <itemBarcode>0000001</itemBarcode> 册条码号 <borrowDate>Fri, 08 Dec 2006 04:17:31 GMT</borrowDate> 借阅日期 <borrowPeriod>30day</borrowPeriod> 借阅期限 <no>0</no> 续借次数。0为首次普通借阅,1开始为续借 <operator>test</operator> 操作者 <operTime>Fri, 08 Dec 2006 04:17:31 GMT</operTime> 操作时间 <confirmItemRecPath>...</confirmItemRecPath> 辅助判断用的册记录路径 <readerRecord recPath='...'>...</readerRecord> 最新读者记录 <itemRecord recPath='...'>...</itemRecord> 最新册记录 </root> * */ // parameters: // bForce 是否为容错状态。在容错状态下,如果遇到重复的册条码号,就算做第一条。 public int RecoverBorrow( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, bool bForce, out string strError) { strError = ""; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } DO_SNAPSHOT: // 快照恢复 if (level == RecoverLevel.Snapshot) { XmlNode node = null; string strReaderXml = DomUtil.GetElementText(domLog.DocumentElement, "readerRecord", out node); if (node == null) { strError = "日志记录中缺<readerRecord>元素"; return -1; } string strReaderRecPath = DomUtil.GetAttr(node, "recPath"); string strItemXml = DomUtil.GetElementText(domLog.DocumentElement, "itemRecord", out node); if (node == null) { strError = "日志记录中缺<itemRecord>元素"; return -1; } string strItemRecPath = DomUtil.GetAttr(node, "recPath"); byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; // 写读者记录 lRet = channel.DoSaveTextRes(strReaderRecPath, strReaderXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入读者记录 '" + strReaderRecPath + "' 时发生错误: " + strError; return -1; } // 写册记录 lRet = channel.DoSaveTextRes(strItemRecPath, strItemXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入册记录 '" + strItemRecPath + "' 时发生错误: " + strError; return -1; } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { string strRecoverComment = ""; string strReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "readerBarcode"); if (String.IsNullOrEmpty(strReaderBarcode) == true) { strError = "<readerBarcode>元素值为空"; goto ERROR1; } // 读入读者记录 string strReaderXml = ""; string strOutputReaderRecPath = ""; byte[] reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strReaderBarcode, out strReaderXml, out strOutputReaderRecPath, out reader_timestamp, out strError); if (nRet == 0) { strError = "读者证条码号 '" + strReaderBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入证条码号为 '" + strReaderBarcode + "' 的读者记录时发生错误: " + strError; goto ERROR1; } string strLibraryCode = ""; // 获得读者库的馆代码 // return: // -1 出错 // 0 成功 nRet = GetLibraryCode( strOutputReaderRecPath, out strLibraryCode, out strError); if (nRet == -1) goto ERROR1; XmlDocument readerdom = null; nRet = LibraryApplication.LoadToDom(strReaderXml, out readerdom, out strError); if (nRet == -1) { strError = "装载读者记录进入XML DOM时发生错误: " + strError; goto ERROR1; } // 读入册记录 string strConfirmItemRecPath = DomUtil.GetElementText(domLog.DocumentElement, "confirmItemRecPath"); string strItemBarcode = DomUtil.GetElementText(domLog.DocumentElement, "itemBarcode"); if (String.IsNullOrEmpty(strItemBarcode) == true) { strError = "<strItemBarcode>元素值为空"; goto ERROR1; } string strItemXml = ""; string strOutputItemRecPath = ""; byte[] item_timestamp = null; // 如果已经有确定的册记录路径 if (String.IsNullOrEmpty(strConfirmItemRecPath) == false) { string strMetaData = ""; lRet = channel.GetRes(strConfirmItemRecPath, out strItemXml, out strMetaData, out item_timestamp, out strOutputItemRecPath, out strError); if (lRet == -1) { strError = "根据strConfirmItemRecPath '" + strConfirmItemRecPath + "' 获得册记录失败: " + strError; goto ERROR1; } // 需要检查记录中的<barcode>元素值是否匹配册条码号 // TODO: 如果记录路径所表达的记录不存在,或者其<barcode>元素值和要求的册条码号不匹配,那么都要改用逻辑方法,也就是利用册条码号来获得记录。 // 当然,这种情况下,非常要紧的是确保数据库的素质很好,本身没有重条码号的情况出现。 } else { // 从册条码号获得册记录 List<string> aPath = null; // 获得册记录 // return: // -1 error // 0 not found // 1 命中1条 // >1 命中多于1条 nRet = this.GetItemRecXml( // Channels, channel, strItemBarcode, out strItemXml, 100, out aPath, out item_timestamp, out strError); if (nRet == 0) { strError = "册条码号 '" + strItemBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入册条码号为 '" + strItemBarcode + "' 的册记录时发生错误: " + strError; goto ERROR1; } if (aPath.Count > 1) { if (bForce == true) { // 容错! strOutputItemRecPath = aPath[0]; strRecoverComment += "册条码号 " + strItemBarcode + " 有 " + aPath.Count.ToString() + " 条重复记录,因受容错要求所迫,权且采用其中第一个记录 " + strOutputItemRecPath + " 来进行借阅操作。"; } else { strError = "册条码号为 '" + strItemBarcode + "' 的册记录有 " + aPath.Count.ToString() + " 条,但此时comfirmItemRecPath却为空"; goto ERROR1; } } else { Debug.Assert(nRet == 1, ""); Debug.Assert(aPath.Count == 1, ""); if (nRet == 1) { strOutputItemRecPath = aPath[0]; } } } XmlDocument itemdom = null; nRet = LibraryApplication.LoadToDom(strItemXml, out itemdom, out strError); if (nRet == -1) { strError = "装载册记录进入XML DOM时发生错误: " + strError; goto ERROR1; } // 修改读者记录 // 修改册记录 // TODO: 容错情况下如果遇到册条码号是重复的,要写入额外的日志。 nRet = BorrowChangeReaderAndItemRecord( // Channels, channel, strItemBarcode, strReaderBarcode, domLog, strRecoverComment, strLibraryCode, ref readerdom, ref itemdom, out strError); if (nRet == -1) goto ERROR1; // 写回读者、册记录 byte[] output_timestamp = null; string strOutputPath = ""; // 写回读者记录 lRet = channel.DoSaveTextRes(strOutputReaderRecPath, readerdom.OuterXml, false, "content,ignorechecktimestamp", reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; // 写回册记录 lRet = channel.DoSaveTextRes(strOutputItemRecPath, itemdom.OuterXml, false, "content,ignorechecktimestamp", item_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } // 容错恢复 if (level == RecoverLevel.Robust) { string strRecoverComment = ""; string strReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "readerBarcode"); if (String.IsNullOrEmpty(strReaderBarcode) == true) { strError = "<readerBarcode>元素值为空"; return -1; } // 读入读者记录 string strReaderXml = ""; string strOutputReaderRecPath = ""; byte[] reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strReaderBarcode, out strReaderXml, out strOutputReaderRecPath, out reader_timestamp, out strError); if (nRet == 0) { strError = "读者证条码号 '" + strReaderBarcode + "' 不存在"; // TODO: 记入信息文件 // 从日志记录中获得读者记录 XmlNode node = null; strReaderXml = DomUtil.GetElementText(domLog.DocumentElement, "readerRecord", out node); if (node == null) { strError = "日志记录中缺<readerRecord>元素"; return -1; } string strReaderRecPath = DomUtil.GetAttr(node, "recPath"); if (String.IsNullOrEmpty(strReaderRecPath) == true) { strError = "日志记录中<readerRecord>元素缺recPath属性"; return -1; } // 新增一条读者记录 strOutputReaderRecPath = ResPath.GetDbName(strReaderRecPath) + "/?"; reader_timestamp = null; } else { if (nRet == -1) { strError = "读入证条码号为 '" + strReaderBarcode + "' 的读者记录时发生错误: " + strError; return -1; } } string strLibraryCode = ""; // 获得读者库的馆代码 // return: // -1 出错 // 0 成功 nRet = GetLibraryCode( strOutputReaderRecPath, out strLibraryCode, out strError); if (nRet == -1) goto ERROR1; XmlDocument readerdom = null; nRet = LibraryApplication.LoadToDom(strReaderXml, out readerdom, out strError); if (nRet == -1) { strError = "装载读者记录进入XML DOM时发生错误: " + strError; return -1; } // 读入册记录 string strConfirmItemRecPath = DomUtil.GetElementText(domLog.DocumentElement, "confirmItemRecPath"); string strItemBarcode = DomUtil.GetElementText(domLog.DocumentElement, "itemBarcode"); if (String.IsNullOrEmpty(strItemBarcode) == true) { strError = "<strItemBarcode>元素值为空"; return -1; } string strItemXml = ""; string strOutputItemRecPath = ""; byte[] item_timestamp = null; // 从册条码号获得册记录 List<string> aPath = null; // 获得册记录 // return: // -1 error // 0 not found // 1 命中1条 // >1 命中多于1条 nRet = this.GetItemRecXml( // Channels, channel, strItemBarcode, out strItemXml, 100, out aPath, out item_timestamp, out strError); if (nRet == 0) { strError = "册条码号 '" + strItemBarcode + "' 不存在"; // TODO: 记入信息文件 XmlNode node = null; strItemXml = DomUtil.GetElementText(domLog.DocumentElement, "itemRecord", out node); if (node == null) { strError = "日志记录中缺<itemRecord>元素"; return -1; } string strItemRecPath = DomUtil.GetAttr(node, "recPath"); if (String.IsNullOrEmpty(strItemRecPath) == true) { strError = "日志记录中<itemRecord>元素缺recPath属性"; return -1; } // 新增一条册记录 strOutputItemRecPath = ResPath.GetDbName(strItemRecPath) + "/?"; item_timestamp = null; } else { if (nRet == -1) { strError = "读入册条码号为 '" + strItemBarcode + "' 的册记录时发生错误: " + strError; return -1; } Debug.Assert(aPath != null, ""); bool bNeedReload = false; if (aPath.Count > 1) { // 建议根据strConfirmItemRecPath来进行挑选 if (String.IsNullOrEmpty(strConfirmItemRecPath) == true) { // 容错! strOutputItemRecPath = aPath[0]; strRecoverComment += "册条码号 " + strItemBarcode + " 有 " + aPath.Count.ToString() + " 条重复记录,因受容错要求所迫,权且采用其中第一个记录 " + strOutputItemRecPath + " 来进行借阅操作。"; // 是否需要重新装载? bNeedReload = false; // 所取得的第一个路径,其记录已经装载 } else { ///// nRet = aPath.IndexOf(strConfirmItemRecPath); if (nRet != -1) { strOutputItemRecPath = aPath[nRet]; strRecoverComment += "册条码号 " + strItemBarcode + " 有 " + aPath.Count.ToString() + " 条重复记录,经过找到strConfirmItemRecPath=[" + strConfirmItemRecPath + "]" + "来进行借阅操作。"; // 是否需要重新装载? if (nRet != 0) bNeedReload = true; // 第一个以外的路径才需要装载 } else { // 容错 strOutputItemRecPath = aPath[0]; strRecoverComment += "册条码号 " + strItemBarcode + " 有 " + aPath.Count.ToString() + " 条重复记录,在其中无法找到strConfirmItemRecPath=[" + strConfirmItemRecPath + "]的记录" + "因受容错要求所迫,权且采用其中第一个记录 " + strOutputItemRecPath + " 来进行借阅操作。"; // 是否需要重新装载? bNeedReload = false; // 所取得的第一个路径,其记录已经装载 /* strError = "册条码号 " + strItemBarcode + " 有 " + aPath.Count.ToString() + " 条重复记录,在其中无法找到strConfirmItemRecPath=[" + strConfirmItemRecPath + "]的记录"; return -1; * */ } } } // if (aPath.Count > 1) else { Debug.Assert(nRet == 1, ""); Debug.Assert(aPath.Count == 1, ""); if (nRet == 1) { strOutputItemRecPath = aPath[0]; // 是否需要重新装载? bNeedReload = false; // 所取得的第一个路径,其记录已经装载 } } // 重新装载 if (bNeedReload == true) { string strMetaData = ""; lRet = channel.GetRes(strOutputItemRecPath, out strItemXml, out strMetaData, out item_timestamp, out strOutputItemRecPath, out strError); if (lRet == -1) { strError = "根据strOutputItemRecPath '" + strOutputItemRecPath + "' 重新获得册记录失败: " + strError; return -1; } // 需要检查记录中的<barcode>元素值是否匹配册条码号 } } //// XmlDocument itemdom = null; nRet = LibraryApplication.LoadToDom(strItemXml, out itemdom, out strError); if (nRet == -1) { strError = "装载册记录进入XML DOM时发生错误: " + strError; goto ERROR1; } // 修改读者记录 // 修改册记录 nRet = BorrowChangeReaderAndItemRecord( // Channels, channel, strItemBarcode, strReaderBarcode, domLog, strRecoverComment, strLibraryCode, ref readerdom, ref itemdom, out strError); if (nRet == -1) goto ERROR1; // 写回读者、册记录 byte[] output_timestamp = null; string strOutputPath = ""; // 写回读者记录 lRet = channel.DoSaveTextRes(strOutputReaderRecPath, readerdom.OuterXml, false, "content,ignorechecktimestamp", reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; // 写回册记录 lRet = channel.DoSaveTextRes(strOutputItemRecPath, itemdom.OuterXml, false, "content,ignorechecktimestamp", item_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
// Amerce() API 恢复动作 /* <root> <operation>amerce</operation> 操作类型 <action>amerce</action> 具体动作。有amerce undo modifyprice <readerBarcode>...</readerBarcode> 读者证条码号 <!-- <idList>...<idList> ID列表,逗号间隔 已废止 --> <amerceItems> <amerceItem id="..." newPrice="..." newComment="..." /> newComment中内容追加或替换原来的注释内容。到底是追加还是覆盖,取决于第一个字符是否为'>'还是'<',前者为追加(这时第一个字符不被当作内容)。如果第一个字符不是这两者之一,则默认为追加 ... </amerceItems> <amerceRecord recPath='...'><root><itemBarcode>0000001</itemBarcode><readerBarcode>R0000002</readerBarcode><state>amerced</state><id>632958375041543888-1</id><over>31day</over><borrowDate>Sat, 07 Oct 2006 09:04:28 GMT</borrowDate><borrowPeriod>30day</borrowPeriod><returnDate>Thu, 07 Dec 2006 09:04:27 GMT</returnDate><returnOperator>test</returnOperator></root></amerceRecord> 在罚款库中创建的新记录。注意<amerceRecord>元素可以重复。<amerceRecord>元素内容里面的<itemBarcode><readerBarcode><id>等具备了足够的信息。 <operator>test</operator> 操作者 <operTime>Fri, 08 Dec 2006 10:09:36 GMT</operTime> 操作时间 <readerRecord recPath='...'>...</readerRecord> 最新读者记录 </root> <root> <operation>amerce</operation> <action>undo</action> <readerBarcode>...</readerBarcode> 读者证条码号 <!-- <idList>...<idList> ID列表,逗号间隔 已废止 --> <amerceItems> <amerceItem id="..." newPrice="..."/> ... </amerceItems> <amerceRecord recPath='...'><root><itemBarcode>0000001</itemBarcode><readerBarcode>R0000002</readerBarcode><state>amerced</state><id>632958375041543888-1</id><over>31day</over><borrowDate>Sat, 07 Oct 2006 09:04:28 GMT</borrowDate><borrowPeriod>30day</borrowPeriod><returnDate>Thu, 07 Dec 2006 09:04:27 GMT</returnDate><returnOperator>test</returnOperator></root></amerceRecord> Undo所去掉的罚款库记录 <operator>test</operator> <operTime>Fri, 08 Dec 2006 10:12:20 GMT</operTime> <readerRecord recPath='...'>...</readerRecord> 最新读者记录 </root> <root> <operation>amerce</operation> <action>modifyprice</action> <readerBarcode>...</readerBarcode> 读者证条码号 <amerceItems> <amerceItem id="..." newPrice="..." newComment="..."/> newComment中内容追加或替换原来的注释内容。到底是追加还是覆盖,取决于第一个字符是否为'>'还是'<',前者为追加(这时第一个字符不被当作内容)。如果第一个字符不是这两者之一,则默认为追加 ... </amerceItems> <!-- modifyprice操作时不产生<amerceRecord>元素 --> <operator>test</operator> <operTime>Fri, 08 Dec 2006 10:12:20 GMT</operTime> <oldReaderRecord recPath='...'>...</oldReaderRecord> 操作前旧的读者记录。<oldReaderRecord>元素是modifyprice操作时特有的元素 <readerRecord recPath='...'>...</readerRecord> 最新读者记录 </root> 2007/12/18 <root> <operation>amerce</operation> 操作类型 <action>expire</action> 以停代金到期 <readerBarcode>...</readerBarcode> 读者证条码号 <expiredOverdues> 已经到期的若干<overdue>元素 <overdue ... /> ... </expiredOverdues> <operator>test</operator> 操作者 如果为#readersMonitor,表示为后台线程 <operTime>Fri, 08 Dec 2006 10:09:36 GMT</operTime> 操作时间 <readerRecord recPath='...'>...</readerRecord> 最新读者记录 </root> * 2008/6/20 <root> <operation>amerce</operation> <action>modifycomment</action> <readerBarcode>...</readerBarcode> 读者证条码号 <amerceItems> <amerceItem id="..." newComment="..."/> newComment中内容追加或替换原来的注释内容。到底是追加还是覆盖,取决于第一个字符是否为'>'还是'<',前者为追加(这时第一个字符不被当作内容)。如果第一个字符不是这两者之一,则默认为追加 ... </amerceItems> <!-- modifycomment操作时不产生<amerceRecord>元素 --> <operator>test</operator> <operTime>Fri, 08 Dec 2006 10:12:20 GMT</operTime> <oldReaderRecord recPath='...'>...</oldReaderRecord> 操作前旧的读者记录。<oldReaderRecord>元素是modifycomment操作时特有的元素 <readerRecord recPath='...'>...</readerRecord> 最新读者记录 </root> * * * */ public int RecoverAmerce( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, out string strError) { strError = ""; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } DO_SNAPSHOT: string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); // 快照恢复 if (level == RecoverLevel.Snapshot) { byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; if (strAction == "amerce") { XmlNodeList nodes = domLog.DocumentElement.SelectNodes("amerceRecord"); int nErrorCount = 0; for (int i = 0; i < nodes.Count; i++) { XmlNode node = nodes[i]; string strRecord = node.InnerText; string strRecPath = DomUtil.GetAttr(node, "recPath"); // 写违约金记录 string strError0 = ""; lRet = channel.DoSaveTextRes(strRecPath, strRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError0); if (lRet == -1) { // 继续循环 if (strError != "") strError += "\r\n"; strError += "写入违约金记录 '" + strRecPath + "' 时发生错误: " + strError0; nErrorCount++; } } if (nErrorCount > 0) return -1; } else if (strAction == "undo") { XmlNodeList nodes = domLog.DocumentElement.SelectNodes("amerceRecord"); int nErrorCount = 0; for (int i = 0; i < nodes.Count; i++) { XmlNode node = nodes[i]; string strRecPath = DomUtil.GetAttr(node, "recPath"); int nRedoCount = 0; string strError0 = ""; REDO: // 删除违约金记录 lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strError0); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) continue; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO; } } // 继续循环 if (strError != "") strError += "\r\n"; strError += "删除违约金记录 '" + strRecPath + "' 时发生错误: " + strError0; nErrorCount++; } } // end of for if (nErrorCount > 0) return -1; } else if (strAction == "modifyprice") { // 这里什么都不作,只等后面用快照的读者记录来恢复 } else if (strAction == "expire") { // 这里什么都不作,只等后面用快照的读者记录来恢复 } else if (strAction == "modifycomment") { // 这里什么都不作,只等后面用快照的读者记录来恢复 } else if (strAction == "appendcomment") { // 这里什么都不作,只等后面用快照的读者记录来恢复 } else { strError = "未知的<action>类型: " + strAction; return -1; } { XmlNode node = null; // 写入读者记录 string strReaderRecord = DomUtil.GetElementText(domLog.DocumentElement, "readerRecord", out node); string strReaderRecPath = DomUtil.GetAttr(node, "recPath"); // 写读者记录 lRet = channel.DoSaveTextRes(strReaderRecPath, strReaderRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入读者记录 '" + strReaderRecPath + "' 时发生错误: " + strError; return -1; } } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { string strReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "readerBarcode"); if (String.IsNullOrEmpty(strReaderBarcode) == true) { strError = "日志记录中缺乏<readerBarcode>元素"; return -1; } string strLibraryCode = DomUtil.GetElementText(domLog.DocumentElement, "libraryCode"); string strOperator = DomUtil.GetElementText(domLog.DocumentElement, "operator"); string strOperTime = DomUtil.GetElementText(domLog.DocumentElement, "operTime"); /* string strAmerceItemIdList = DomUtil.GetElementText(domLog.DocumentElement, "idList"); if (String.IsNullOrEmpty(strAmerceItemIdList) == true) { strError = "日志记录中缺乏<idList>元素"; return -1; } * */ AmerceItem[] amerce_items = ReadAmerceItemList(domLog); // 读入读者记录 string strReaderXml = ""; string strOutputReaderRecPath = ""; byte[] reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strReaderBarcode, out strReaderXml, out strOutputReaderRecPath, out reader_timestamp, out strError); if (nRet == 0) { strError = "读者证条码号 '" + strReaderBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入读者记录时发生错误: " + strError; goto ERROR1; } XmlDocument readerdom = null; nRet = LibraryApplication.LoadToDom(strReaderXml, out readerdom, out strError); if (nRet == -1) { strError = "装载读者记录进入XML DOM时发生错误: " + strError; goto ERROR1; } byte[] output_timestamp = null; string strOutputPath = ""; if (strAction == "amerce") { List<string> NotFoundIds = null; List<string> Ids = null; List<string> AmerceRecordXmls = null; // 交违约金:在读者记录中去除所选的<overdue>元素,并且构造一批新记录准备加入违约金库 // return: // -1 error // 0 读者dom没有变化 // 1 读者dom发生了变化 nRet = DoAmerceReaderXml( strLibraryCode, ref readerdom, amerce_items, strOperator, strOperTime, out AmerceRecordXmls, out NotFoundIds, out Ids, out strError); if (nRet == -1) { // 在错误信息后面增补每个id对应的amerce record if (NotFoundIds != null && NotFoundIds.Count > 0) { strError += "。读者证条码号为 " + strReaderBarcode + ",日志记录中相关的AmerceRecord如下:\r\n" + GetAmerceRecordStringByID(domLog, NotFoundIds); } goto ERROR1; } // 如果有精力,可以把AmerceRecordXmls和日志记录中的<amerceRecord>逐个进行核对 // 写入违约金记录 XmlNodeList nodes = domLog.DocumentElement.SelectNodes("amerceRecord"); for (int i = 0; i < nodes.Count; i++) { XmlNode node = nodes[i]; string strRecord = node.InnerText; string strRecPath = DomUtil.GetAttr(node, "recPath"); // 写违约金记录 lRet = channel.DoSaveTextRes(strRecPath, strRecord, false, "content,ignorechecktimestamp", null, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入违约金记录 '" + strRecPath + "' 时发生错误: " + strError; goto ERROR1; } } } if (strAction == "undo") { XmlNodeList nodes = domLog.DocumentElement.SelectNodes("amerceRecord"); // 看看根下面是否有overdues元素 XmlNode root = readerdom.DocumentElement.SelectSingleNode("overdues"); if (root == null) { root = readerdom.CreateElement("overdues"); readerdom.DocumentElement.AppendChild(root); } for (int i = 0; i < nodes.Count; i++) { XmlNode node = nodes[i]; string strRecord = node.InnerText; string strRecPath = DomUtil.GetAttr(node, "recPath"); // 如果有精力,可以把违约金记录中的id和日志记录<amerceItems>中的id对比检查 // 违约金信息加回读者记录 string strTempReaderBarcode = ""; string strOverdueString = ""; // 将违约金记录格式转换为读者记录中的<overdue>元素格式 nRet = ConvertAmerceRecordToOverdueString(strRecord, out strTempReaderBarcode, out strOverdueString, out strError); if (nRet == -1) goto ERROR1; if (strTempReaderBarcode != strReaderBarcode) { strError = "<amerceRecord>中的读者证条码号和日志记录中的<readerBarcode>读者证条码号不一致"; goto ERROR1; } XmlDocumentFragment fragment = readerdom.CreateDocumentFragment(); fragment.InnerXml = strOverdueString; // 2008/11/13 changed XmlNode node_added = root.AppendChild(fragment); Debug.Assert(node_added != null, ""); string strReason = DomUtil.GetAttr(node_added, "reason"); if (strReason == "押金。") { string strPrice = DomUtil.GetAttr(node_added, "price"); if (String.IsNullOrEmpty(strPrice) == false) { // 需要从<foregift>元素中减去这个价格 string strContent = DomUtil.GetElementText(readerdom.DocumentElement, "foregift"); string strNegativePrice = ""; // 将形如"-123.4+10.55-20.3"的价格字符串反转正负号 // parameters: // bSum 是否要顺便汇总? true表示要汇总 nRet = PriceUtil.NegativePrices(strPrice, false, out strNegativePrice, out strError); if (nRet == -1) { strError = "反转价格字符串 '" + strPrice + "时发生错误: " + strError; goto ERROR1; } strContent = PriceUtil.JoinPriceString(strContent, strNegativePrice); DomUtil.SetElementText(readerdom.DocumentElement, "foregift", strContent); // bReaderDomChanged = true; } } // 删除违约金记录 int nRedoCount = 0; byte[] timestamp = null; REDO: // 删除违约金记录 lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) continue; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO; } } // 是否需要继续循环? strError = "删除违约金记录 '" + strRecPath + "' 时发生错误: " + strError; goto ERROR1; } } } if (strAction == "modifyprice") { nRet = ModifyPrice(ref readerdom, amerce_items, out strError); if (nRet == -1) { strError = "ModifyPrice()时发生错误: " + strError; goto ERROR1; } } // 2008/6/20 if (strAction == "modifycomment") { nRet = ModifyComment( ref readerdom, amerce_items, out strError); if (nRet == -1) { strError = "ModifyComment()时发生错误: " + strError; goto ERROR1; } } if (strAction == "expire") { // 寻找<expiredOverdues/overdue>元素 XmlNodeList nodes = domLog.DocumentElement.SelectNodes("//expiredOverdues/overdue"); for (int i = 0; i < nodes.Count; i++) { XmlNode node = nodes[i]; string strID = DomUtil.GetAttr(node, "id"); if (String.IsNullOrEmpty(strID) == true) continue; // 从读者记录中去掉这个id的<overdue>元素 XmlNode nodeOverdue = readerdom.DocumentElement.SelectSingleNode("overdues/overdue[@id='"+strID+"']"); if (nodeOverdue != null) { if (nodeOverdue.ParentNode != null) nodeOverdue.ParentNode.RemoveChild(nodeOverdue); } } } // 写回读者记录 strReaderXml = readerdom.OuterXml; lRet = channel.DoSaveTextRes(strOutputReaderRecPath, strReaderXml, false, "content,ignorechecktimestamp", reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
// SetReaderInfo() API 恢复动作 /* <root> <operation>setReaderInfo</operation> 操作类型 <action>...</action> 具体动作。有new change delete move 4种 <record recPath='...'>...</record> 新记录 <oldRecord recPath='...'>...</oldRecord> 被覆盖或者删除的记录 动作为 change 和 delete 时具备此元素 <changedEntityRecord itemBarcode='...' recPath='...' oldBorrower='...' newBorrower='...' /> 若干个元素。表示连带发生修改的册记录 <operator>test</operator> 操作者 <operTime>Fri, 08 Dec 2006 09:01:38 GMT</operTime> 操作时间 </root> 注: new 的时候只有<record>元素,delete的时候只有<oldRecord>元素,change的时候两者都有 * */ public int RecoverSetReaderInfo( RmsChannelCollection Channels, RecoverLevel level_param, XmlDocument domLog, out string strError) { strError = ""; string[] element_names = reader_element_names; RecoverLevel level = level_param; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } bool bReuse = false; // 是否能够不顾RecorverLevel状态而重用部分代码 DO_SNAPSHOT: string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); // 快照恢复 if (level == RecoverLevel.Snapshot || bReuse == true) { byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; if (strAction == "new" || strAction == "change") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); // 写读者记录 lRet = channel.DoSaveTextRes(strRecPath, strRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入读者记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } // 2015/9/11 XmlNodeList nodes = domLog.DocumentElement.SelectNodes("changedEntityRecord"); foreach(XmlElement item in nodes) { string strItemBarcode = item.GetAttribute("itemBarcode"); string strItemRecPath = item.GetAttribute("recPath"); string strOldReaderBarcode = item.GetAttribute("oldBorrower"); string strNewReaderBarcode = item.GetAttribute("newBorrower"); // 修改一条册记录,的 borrower 元素内容 // parameters: // -2 保存记录时出错 // -1 一般性错误 // 0 成功 nRet = ChangeBorrower( channel, strItemBarcode, strItemRecPath, strOldReaderBarcode, strNewReaderBarcode, true, out strError); if (nRet == -1 || nRet == -2) { strError = "修改读者记录所关联的在借册记录时出错:" + strError; return -1; } } } else if (strAction == "delete") { XmlNode node = null; string strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); int nRedoCount = 0; REDO: // 删除读者记录 lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) return 0; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO; } } strError = "删除读者记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } } else if (strAction == "move") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); if (string.IsNullOrEmpty(strRecPath) == true) { strError = "日志记录中<record>元素内缺recPath属性值"; return -1; } string strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } string strOldRecPath = DomUtil.GetAttr(node, "recPath"); if (string.IsNullOrEmpty(strOldRecPath) == true) { strError = "日志记录中<oldRecord>元素内缺recPath属性值"; return -1; } /* int nRedoCount = 0; REDO: * */ // 移动读者记录 lRet = channel.DoCopyRecord( strOldRecPath, strRecPath, true, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { // 源记录本来就不存在。进行容错处理 if (channel.ErrorCode == ChannelErrorCode.NotFound && level_param == RecoverLevel.Robust) { // 优先用最新的记录内容复原。实在没有才用旧的记录内容 if (string.IsNullOrEmpty(strRecord) == true) strRecord = strOldRecord; if (string.IsNullOrEmpty(strRecord) == false) { // 写读者记录 lRet = channel.DoSaveTextRes(strRecPath, strRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "为容错,写入读者记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } return 0; } } strError = "移动读者记录 '" + strOldRecPath + "' 到 '" + strRecPath + "' 时发生错误: " + strError; return -1; } // <record>中如果有记录体,则还需要写入一次 // 所以这里需要注意,在创建日志记录的时候,如果没有在CopyRecord()后追加修改过记录,则不要创建<record>记录正文部分,以免引起多余的日志恢复时写入动作 if (string.IsNullOrEmpty(strRecord) == false) { lRet = channel.DoSaveTextRes(strRecPath, strRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入读者记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } } } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { // 和数据库中已有记录合并,然后保存 if (strAction == "new" || strAction == "change") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); // 读出数据库中原有的记录 string strExistXml = ""; string strMetaData = ""; byte[] exist_timestamp = null; string strOutputPath = ""; if (strAction == "change") { lRet = channel.GetRes(strRecPath, out strExistXml, out strMetaData, out exist_timestamp, out strOutputPath, out strError); if (lRet == -1) { // 容错 if (channel.ErrorCode == ChannelErrorCode.NotFound && level == RecoverLevel.LogicAndSnapshot) { // 如果记录不存在, 则构造一条空的记录 // bExist = false; strExistXml = "<root />"; exist_timestamp = null; } else { strError = "在读入原有记录 '" + strRecPath + "' 时失败: " + strError; goto ERROR1; } } } // // 把两个记录装入DOM XmlDocument domExist = new XmlDocument(); XmlDocument domNew = new XmlDocument(); try { // 防范空记录 if (String.IsNullOrEmpty(strExistXml) == true) strExistXml = "<root />"; domExist.LoadXml(strExistXml); } catch (Exception ex) { strError = "strExistXml装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } try { domNew.LoadXml(strRecord); } catch (Exception ex) { strError = "strRecord装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } // 合并新旧记录 string strNewXml = ""; nRet = MergeTwoReaderXml( element_names, "change", domExist, domNew, out strNewXml, out strError); if (nRet == -1) goto ERROR1; // 保存新记录 byte[] output_timestamp = null; lRet = channel.DoSaveTextRes(strRecPath, strNewXml, false, // include preamble? "content,ignorechecktimestamp", exist_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { goto ERROR1; } } else if (strAction == "delete") { // 和SnapShot方式相同 bReuse = true; goto DO_SNAPSHOT; } else if (strAction == "move") { // 和SnapShot方式相同 bReuse = true; goto DO_SNAPSHOT; } else { strError = "无法识别的<action>内容 '" + strAction + "'"; return -1; } } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
// ChangeReaderPassword() API 恢复动作 /* <root> <operation>changeReaderPassword</operation> <readerBarcode>...</readerBarcode> 读者证条码号 <newPassword>5npAUJ67/y3aOvdC0r+Dj7SeXGE=</newPassword> <operator>test</operator> <operTime>Fri, 08 Dec 2006 09:01:38 GMT</operTime> <readerRecord recPath='...'>...</readerRecord> 最新读者记录 </root> * */ public int RecoverChangeReaderPassword( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, out string strError) { strError = ""; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } DO_SNAPSHOT: // 快照恢复 if (level == RecoverLevel.Snapshot) { XmlNode node = null; string strReaderXml = DomUtil.GetElementText(domLog.DocumentElement, "readerRecord", out node); if (node == null) { strError = "日志记录中缺<readerRecord>元素"; return -1; } string strReaderRecPath = DomUtil.GetAttr(node, "recPath"); byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; // 写读者记录 lRet = channel.DoSaveTextRes(strReaderRecPath, strReaderXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入读者记录 '" + strReaderRecPath + "' 时发生错误: " + strError; return -1; } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { // 读出原有读者记录,修改密码后存回 string strReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "readerBarcode"); if (String.IsNullOrEmpty(strReaderBarcode) == true) { strError = "日志记录中缺乏<readerBarcode>元素"; goto ERROR1; } string strNewPassword = DomUtil.GetElementText(domLog.DocumentElement, "newPassword"); if (String.IsNullOrEmpty(strNewPassword) == true) { strError = "日志记录中缺乏<newPassword>元素"; goto ERROR1; } // 读入读者记录 string strReaderXml = ""; string strOutputReaderRecPath = ""; byte[] reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strReaderBarcode, out strReaderXml, out strOutputReaderRecPath, out reader_timestamp, out strError); if (nRet == 0) { strError = "读者证条码号 '" + strReaderBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入证条码号为 '" + strReaderBarcode + "' 的读者记录时发生错误: " + strError; goto ERROR1; } XmlDocument readerdom = null; nRet = LibraryApplication.LoadToDom(strReaderXml, out readerdom, out strError); if (nRet == -1) { strError = "装载读者记录进入XML DOM时发生错误: " + strError; goto ERROR1; } // strNewPassword中本来就是SHA1形态 DomUtil.SetElementText(readerdom.DocumentElement, "password", strNewPassword); byte[] output_timestamp = null; string strOutputPath = ""; // 写回读者记录 lRet = channel.DoSaveTextRes(strOutputReaderRecPath, readerdom.OuterXml, false, "content,ignorechecktimestamp", reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
// SetComments() API 恢复动作 /* 日志记录格式 <root> <operation>setComment</operation> 操作类型 <action>new</action> 具体动作。有new change delete 3种 <style>...</style> 风格。有force nocheckdup noeventlog 3种 <record recPath='中文图书评注/3'>...</record> 记录体 <oldRecord recPath='中文图书评注/3'>...</oldRecord> 被覆盖或者删除的记录 动作为change和delete时具备此元素 <operator>test</operator> 操作者 <operTime>Fri, 08 Dec 2006 08:41:46 GMT</operTime> 操作时间 </root> 注:1) 当<action>为delete时,没有<record>元素。为new时,没有<oldRecord>元素。 2) 一次SetComments()API调用, 可能创建多条日志记录。 * */ // TODO: 要兑现style中force nocheckdup功能 public int RecoverSetComment( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, out string strError) { strError = ""; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } bool bReuse = false; // 是否能够不顾RecorverLevel状态而重用部分代码 DO_SNAPSHOT: string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); // 快照恢复 if (level == RecoverLevel.Snapshot || bReuse == true) { byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; if (strAction == "new" || strAction == "change" || strAction == "move") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; return -1; } string strNewRecPath = DomUtil.GetAttr(node, "recPath"); // string strOldRecord = ""; string strOldRecPath = ""; if (strAction == "move") { strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } strOldRecPath = DomUtil.GetAttr(node, "recPath"); } // 写评注记录 lRet = channel.DoSaveTextRes(strNewRecPath, strRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入评注记录 '" + strNewRecPath + "' 时发生错误: " + strError; return -1; } if (strAction == "move") { // 删除评注记录 int nRedoCount = 0; REDO_DELETE: lRet = channel.DoDeleteRes(strOldRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) return 0; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO_DELETE; } } strError = "删除评注记录 '" + strOldRecPath + "' 时发生错误: " + strError; return -1; } } } else if (strAction == "delete") { XmlNode node = null; string strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); int nRedoCount = 0; REDO: // 删除评注记录 lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) return 0; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO; } } strError = "删除评注记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } } else { strError = "无法识别的<action>内容 '" + strAction + "'"; return -1; } return 0; } bool bForce = false; bool bNoCheckDup = false; string strStyle = DomUtil.GetElementText(domLog.DocumentElement, "style"); if (StringUtil.IsInList("force", strStyle) == true) bForce = true; if (StringUtil.IsInList("nocheckdup", strStyle) == true) bNoCheckDup = true; // 逻辑恢复或者混合恢复或者容错恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot || level == RecoverLevel.Robust) // 容错恢复没有单独实现 { // 和数据库中已有记录合并,然后保存 if (strAction == "new" || strAction == "change" || strAction == "move") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; return -1; } string strNewRecPath = DomUtil.GetAttr(node, "recPath"); // string strOldRecord = ""; string strOldRecPath = ""; if (strAction == "move") { strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } strOldRecPath = DomUtil.GetAttr(node, "recPath"); } // 读出数据库中原有的记录 string strExistXml = ""; string strMetaData = ""; byte[] exist_timestamp = null; string strOutputPath = ""; if ((strAction == "change" || strAction == "move") && bForce == false) { string strSourceRecPath = ""; if (strAction == "change") strSourceRecPath = strNewRecPath; if (strAction == "move") strSourceRecPath = strOldRecPath; lRet = channel.GetRes(strSourceRecPath, out strExistXml, out strMetaData, out exist_timestamp, out strOutputPath, out strError); if (lRet == -1) { // 容错 if (channel.ErrorCode == ChannelErrorCode.NotFound && level == RecoverLevel.LogicAndSnapshot) { // 如果记录不存在, 则构造一条空的记录 // bExist = false; strExistXml = "<root />"; exist_timestamp = null; } else { strError = "在读入原有记录 '" + strNewRecPath + "' 时失败: " + strError; goto ERROR1; } } } // // 把两个记录装入DOM XmlDocument domExist = new XmlDocument(); XmlDocument domNew = new XmlDocument(); try { // 防范空记录 if (String.IsNullOrEmpty(strExistXml) == true) strExistXml = "<root />"; domExist.LoadXml(strExistXml); } catch (Exception ex) { strError = "strExistXml装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } try { domNew.LoadXml(strRecord); } catch (Exception ex) { strError = "strRecord装载进入DOM时发生错误: " + ex.Message; goto ERROR1; } // 合并新旧记录 string strNewXml = ""; if (bForce == false) { nRet = this.CommentItemDatabase.MergeTwoItemXml( null, domExist, domNew, out strNewXml, out strError); if (nRet == -1) goto ERROR1; } else { strNewXml = domNew.OuterXml; } // 保存新记录 byte[] output_timestamp = null; if (strAction == "move") { // 复制源记录到目标位置,然后自动删除源记录 // 但是尚未在目标位置写入最新内容 lRet = channel.DoCopyRecord(strOldRecPath, strNewRecPath, true, // bDeleteSourceRecord out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; exist_timestamp = output_timestamp; // 及时更新时间戳 } lRet = channel.DoSaveTextRes(strNewRecPath, strNewXml, false, // include preamble? "content,ignorechecktimestamp", exist_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } else if (strAction == "delete") { // 和SnapShot方式相同 bReuse = true; goto DO_SNAPSHOT; } else { strError = "无法识别的<action>内容 '" + strAction + "'"; return -1; } } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
// 执行一个日志记录的恢复动作 // parameters: // attachment 附件流对象。注意文件指针在流的尾部 public int DoOperLogRecord( RecoverLevel level, string strXml, Stream attachment, string strStyle, out string strError) { strError = ""; int nRet = 0; XmlDocument dom = new XmlDocument(); try { dom.LoadXml(strXml); } catch (Exception ex) { strError = "日志记录装载到DOM时出错: " + ex.Message; return(-1); } string strOperation = DomUtil.GetElementText(dom.DocumentElement, "operation"); if (strOperation == "borrow") { nRet = this.App.RecoverBorrow(this.RmsChannels, level, dom, false, out strError); } else if (strOperation == "return") { nRet = this.App.RecoverReturn(this.RmsChannels, level, dom, false, out strError); } else if (strOperation == "setEntity") { nRet = this.App.RecoverSetEntity(this.RmsChannels, level, dom, out strError); } else if (strOperation == "setOrder") { nRet = this.App.RecoverSetOrder(this.RmsChannels, level, dom, out strError); } else if (strOperation == "setIssue") { nRet = this.App.RecoverSetIssue(this.RmsChannels, level, dom, out strError); } else if (strOperation == "setComment") { nRet = this.App.RecoverSetComment(this.RmsChannels, level, dom, out strError); } else if (strOperation == "changeReaderPassword") { nRet = this.App.RecoverChangeReaderPassword(this.RmsChannels, level, dom, out strError); } else if (strOperation == "changeReaderTempPassword") { // 2013/11/3 } else if (strOperation == "setReaderInfo") { nRet = this.App.RecoverSetReaderInfo(this.RmsChannels, level, dom, out strError); } else if (strOperation == "devolveReaderInfo") { nRet = this.App.RecoverDevolveReaderInfo(this.RmsChannels, level, dom, attachment, out strError); } else if (strOperation == "amerce") { nRet = this.App.RecoverAmerce(this.RmsChannels, level, dom, out strError); } else if (strOperation == "setBiblioInfo") { nRet = this.App.RecoverSetBiblioInfo(this.RmsChannels, level, dom, out strError); } else if (strOperation == "hire") { nRet = this.App.RecoverHire(this.RmsChannels, level, dom, out strError); } else if (strOperation == "foregift") { // 2008/11/11 nRet = this.App.RecoverForegift(this.RmsChannels, level, dom, out strError); } else if (strOperation == "settlement") { nRet = this.App.RecoverSettlement(this.RmsChannels, level, dom, out strError); } else if (strOperation == "writeRes") { // 2011/5/26 nRet = this.App.RecoverWriteRes(this.RmsChannels, level, dom, attachment, out strError); } else if (strOperation == "repairBorrowInfo") { // 2012/6/21 nRet = this.App.RecoverRepairBorrowInfo(this.RmsChannels, level, dom, attachment, out strError); } else if (strOperation == "reservation") { // 暂未实现 } else if (strOperation == "setUser") { // 暂未实现 } else if (strOperation == "passgate") { // 只读 } else if (strOperation == "getRes") { // 只读 2015/7/14 } else if (strOperation == "crashReport") { // 只读 2015/7/16 } else if (strOperation == "memo") { // 注记 2015/9/8 } else if (strOperation == "statis") { // 统计 2019/6/24 } else if (strOperation == "manageDatabase") { // 管理数据库 2017/5/23 // 2017/10/15 nRet = this.App.RecoverManageDatabase(this.RmsChannels, level, dom, attachment, strStyle, out strError); } else { strError = "不能识别的日志操作类型 '" + strOperation + "'"; return(-1); } if (nRet == -1) { string strAction = DomUtil.GetElementText(dom.DocumentElement, "action"); strError = "operation=" + strOperation + ";action=" + strAction + ": " + strError; return(-1); } return(0); }
// SetBiblioInfo() API 或CopyBiblioInfo() API 的恢复动作 // 函数内,使用return -1;还是goto ERROR1; 要看错误发生的时候,是否还有价值继续探索SnapShot重试。如果是,就用后者。 /* <root> <operation>setBiblioInfo</operation> <action>...</action> 具体动作 有 new/change/delete/onlydeletebiblio/onlydeletesubrecord 和 onlycopybiblio/onlymovebiblio/copy/move <record recPath='中文图书/3'>...</record> 记录体 动作为new/change/ *move* / *copy* 时具有此元素(即delete时没有此元素) <oldRecord recPath='中文图书/3'>...</oldRecord> 被覆盖、删除或者移动的记录 动作为change/ *delete* / *move* / *copy* 时具备此元素 <deletedEntityRecords> 被删除的实体记录(容器)。只有当<action>为delete时才有这个元素。 <record recPath='中文图书实体/100'>...</record> 这个元素可以重复。注意元素内文本内容目前为空。 ... </deletedEntityRecords> <copyEntityRecords> 被复制的实体记录(容器)。只有当<action>为*copy*时才有这个元素。 <record recPath='中文图书实体/100' targetRecPath='中文图书实体/110'>...</record> 这个元素可以重复。注意元素内文本内容目前为空。recPath属性为源记录路径,targetRecPath为目标记录路径 ... </copyEntityRecords> <moveEntityRecords> 被移动的实体记录(容器)。只有当<action>为*move*时才有这个元素。 <record recPath='中文图书实体/100' targetRecPath='中文图书实体/110'>...</record> 这个元素可以重复。注意元素内文本内容目前为空。recPath属性为源记录路径,targetRecPath为目标记录路径 ... </moveEntityRecords> <copyOrderRecords /> <moveOrderRecords /> <copyIssueRecords /> <moveIssueRecords /> <copyCommentRecords /> <moveCommentRecords /> <operator>test</operator> <operTime>Fri, 08 Dec 2006 10:12:20 GMT</operTime> </root> 逻辑恢复delete操作的时候,检索出全部下属的实体记录删除。 快照恢复的时候,可以根据operlogdom直接删除记录了path的那些实体记录 * */ public int RecoverSetBiblioInfo( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, out string strError) { strError = ""; // 暂时把Robust当作Logic处理 if (level == RecoverLevel.Robust) level = RecoverLevel.Logic; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } bool bReuse = false; // 是否能够不顾RecorverLevel状态而重用部分代码 DO_SNAPSHOT: string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); // 快照恢复 if (level == RecoverLevel.Snapshot || bReuse == true) { byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; if (strAction == "new" || strAction == "change") { XmlNode node = null; string strRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; goto ERROR1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); // 写书目记录 lRet = channel.DoSaveTextRes(strRecPath, strRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入书目记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } } else if (strAction == "onlymovebiblio" || strAction == "onlycopybiblio" || strAction == "move" || strAction == "copy") { XmlNode node = null; string strTargetRecord = DomUtil.GetElementText(domLog.DocumentElement, "record", out node); if (node == null) { strError = "日志记录中缺<record>元素"; goto ERROR1; } string strTargetRecPath = DomUtil.GetAttr(node, "recPath"); string strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } string strOldRecPath = DomUtil.GetAttr(node, "recPath"); string strMergeStyle = DomUtil.GetElementText(domLog.DocumentElement, "mergeStyle"); bool bSourceExist = true; // 观察源记录是否存在 { string strMetaData = ""; string strXml = ""; byte[] temp_timestamp = null; lRet = channel.GetRes(strOldRecPath, out strXml, out strMetaData, out temp_timestamp, out strOutputPath, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) { bSourceExist = false; } } } if (bSourceExist == true) { string strIdChangeList = ""; // 复制书目记录 lRet = channel.DoCopyRecord(strOldRecPath, strTargetRecPath, strAction == "onlymovebiblio" ? true : false, // bDeleteSourceRecord strMergeStyle, out strIdChangeList, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "DoCopyRecord() error :" + strError; goto ERROR1; } } /* // 写书目记录 lRet = channel.DoSaveTextRes(strRecPath, strRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "复制书目记录 '" + strOldRecPath + "' 到 '" + strTargetRecPath + "' 时发生错误: " + strError; return -1; } * */ if (bSourceExist == false) { if (String.IsNullOrEmpty(strTargetRecord) == true) { if (String.IsNullOrEmpty(strOldRecord) == true) { strError = "源记录 '" + strOldRecPath + "' 不存在,并且<record>元素无文本内容,这时<oldRecord>元素也无文本内容,无法获得要写入的记录内容"; return -1; } strTargetRecord = strOldRecord; } } // 如果有“新记录”内容 if (String.IsNullOrEmpty(strTargetRecord) == false) { // 写书目记录 lRet = channel.DoSaveTextRes(strTargetRecPath, strTargetRecord, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写书目记录 '" + strTargetRecPath + "' 时发生错误: " + strError; return -1; } } // 复制或者移动下级子纪录 if (strAction == "move" || strAction == "copy") { string[] element_names = new string[] { "copyEntityRecords", "moveEntityRecords", "copyOrderRecords", "moveOrderRecords", "copyIssueRecords", "moveIssueRecords", "copyCommentRecords", "moveCommentRecords" }; for (int i = 0; i < element_names.Length; i++) { XmlNode node_subrecords = domLog.DocumentElement.SelectSingleNode( element_names[i]); if (node_subrecords != null) { nRet = CopySubRecords( channel, node_subrecords, strTargetRecPath, out strError); if (nRet == -1) return -1; } } } // 2011/12/12 if (bSourceExist == true && (strAction == "move" || strAction == "onlymovebiblio") ) { int nRedoCount = 0; REDO_DELETE: // 删除源书目记录 lRet = channel.DoDeleteRes(strOldRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) { // 记录本来就不存在 } else if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO_DELETE; } } else { strError = "删除书目记录 '" + strOldRecPath + "' 时发生错误: " + strError; return -1; } } } } else if (strAction == "delete" || strAction == "onlydeletebiblio" || strAction == "onlydeletesubrecord") { XmlNode node = null; string strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); if (strAction != "onlydeletesubrecord") { int nRedoCount = 0; REDO: // 删除书目记录 lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) goto DO_DELETE_CHILD_ENTITYRECORDS; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO; } } strError = "删除书目记录 '" + strRecPath + "' 时发生错误: " + strError; return -1; } } DO_DELETE_CHILD_ENTITYRECORDS: if (strAction == "delete" || strAction == "onlydeletesubrecord") { XmlNodeList nodes = domLog.DocumentElement.SelectNodes("deletedEntityRecords/record"); for (int i = 0; i < nodes.Count; i++) { string strEntityRecPath = DomUtil.GetAttr(nodes[i], "recPath"); /* if (String.IsNullOrEmpty(strEntityRecPath) == true) continue; * */ int nRedoDeleteCount = 0; REDO_DELETE_ENTITY: // 删除实体记录 lRet = channel.DoDeleteRes(strEntityRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) continue; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoDeleteCount < 10) { timestamp = output_timestamp; nRedoDeleteCount++; goto REDO_DELETE_ENTITY; } } strError = "删除实体记录 '" + strEntityRecPath + "' 时发生错误: " + strError; return -1; } } nodes = domLog.DocumentElement.SelectNodes("deletedOrderRecords/record"); for (int i = 0; i < nodes.Count; i++) { string strOrderRecPath = DomUtil.GetAttr(nodes[i], "recPath"); if (String.IsNullOrEmpty(strOrderRecPath) == true) continue; int nRedoDeleteCount = 0; REDO_DELETE_ORDER: // 删除订购记录 lRet = channel.DoDeleteRes(strOrderRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) continue; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoDeleteCount < 10) { timestamp = output_timestamp; nRedoDeleteCount++; goto REDO_DELETE_ORDER; } } strError = "删除订购记录 '" + strOrderRecPath + "' 时发生错误: " + strError; return -1; } } nodes = domLog.DocumentElement.SelectNodes("deletedIssueRecords/record"); for (int i = 0; i < nodes.Count; i++) { string strIssueRecPath = DomUtil.GetAttr(nodes[i], "recPath"); if (String.IsNullOrEmpty(strIssueRecPath) == true) continue; int nRedoDeleteCount = 0; REDO_DELETE_ISSUE: // 删除期记录 lRet = channel.DoDeleteRes(strIssueRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) continue; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoDeleteCount < 10) { timestamp = output_timestamp; nRedoDeleteCount++; goto REDO_DELETE_ISSUE; } } strError = "删除期记录 '" + strIssueRecPath + "' 时发生错误: " + strError; return -1; } } } // end if } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { // 和数据库中已有记录合并,然后保存 if (strAction == "new" || strAction == "change") { // 和SnapShot方式相同 bReuse = true; goto DO_SNAPSHOT; } else if (strAction == "onlymovebiblio" || strAction == "onlycopybiblio" || strAction == "move" || strAction == "copy") { // 和SnapShot方式相同 bReuse = true; goto DO_SNAPSHOT; } else if (strAction == "delete" || strAction == "onlydeletebiblio" || strAction == "onlydeletesubrecord") { XmlNode node = null; string strOldRecord = DomUtil.GetElementText(domLog.DocumentElement, "oldRecord", out node); if (node == null) { strError = "日志记录中缺<oldRecord>元素"; return -1; } string strRecPath = DomUtil.GetAttr(node, "recPath"); if (strAction != "onlydeletesubrecord") { int nRedoCount = 0; byte[] timestamp = null; byte[] output_timestamp = null; REDO: // 删除书目记录 lRet = channel.DoDeleteRes(strRecPath, timestamp, out output_timestamp, out strError); if (lRet == -1) { if (channel.ErrorCode == ChannelErrorCode.NotFound) goto DO_DELETE_CHILD_ENTITYRECORDS; // 记录本来就不存在 if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch) { if (nRedoCount < 10) { timestamp = output_timestamp; nRedoCount++; goto REDO; } } strError = "删除书目记录 '" + strRecPath + "' 时发生错误: " + strError; goto ERROR1; } } DO_DELETE_CHILD_ENTITYRECORDS: if (strAction == "delete" || strAction == "onlydeletesubrecord") { // 删除属于同一书目记录的全部实体记录 // return: // -1 error // 0 没有找到属于书目记录的任何实体记录,因此也就无从删除 // >0 实际删除的实体记录数 nRet = DeleteBiblioChildEntities(channel, strRecPath, null, out strError); if (nRet == -1) { strError = "删除书目记录 '" + strRecPath + "' 下属的实体记录时出错: " + strError; goto ERROR1; } // return: // -1 error // 0 没有找到属于书目记录的任何实体记录,因此也就无从删除 // >0 实际删除的实体记录数 nRet = this.OrderItemDatabase.DeleteBiblioChildItems( // Channels, channel, strRecPath, null, out strError); if (nRet == -1) { strError = "删除书目记录 '" + strRecPath + "' 下属的订购记录时出错: " + strError; goto ERROR1; } // return: // -1 error // 0 没有找到属于书目记录的任何实体记录,因此也就无从删除 // >0 实际删除的实体记录数 nRet = this.IssueItemDatabase.DeleteBiblioChildItems( // Channels, channel, strRecPath, null, out strError); if (nRet == -1) { strError = "删除书目记录 '" + strRecPath + "' 下属的期记录时出错: " + strError; goto ERROR1; } } } else { strError = "无法识别的<action>内容 '" + strAction + "'"; return -1; } } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }
// 一次操作循环 public override void Worker() { // 把系统挂起 // this.App.HangupReason = HangupReason.LogRecover; this.App.HangupList.Add("LogRecover"); try { string strError = ""; BatchTaskStartInfo startinfo = this.StartInfo; if (startinfo == null) { startinfo = new BatchTaskStartInfo(); // 按照缺省值来 } long lStartIndex = 0; // 开始位置 string strStartFileName = ""; // 开始文件名 int nRet = ParseLogRecorverStart(startinfo.Start, out lStartIndex, out strStartFileName, out strError); if (nRet == -1) { this.AppendResultText("启动失败: " + strError + "\r\n"); return; } // string strRecoverLevel = ""; bool bClearFirst = false; bool bContinueWhenError = false; nRet = ParseLogRecoverParam(startinfo.Param, out strRecoverLevel, out bClearFirst, out bContinueWhenError, out strError); if (nRet == -1) { this.AppendResultText("启动失败: " + strError + "\r\n"); return; } if (String.IsNullOrEmpty(strRecoverLevel) == true) { strRecoverLevel = "Snapshot"; } try { this.RecoverLevel = (RecoverLevel)Enum.Parse(typeof(RecoverLevel), strRecoverLevel, true); } catch (Exception ex) { this.AppendResultText("启动失败: 启动参数Param中的recoverLevel枚举值 '" + strRecoverLevel + "' 错误: " + ex.Message + "\r\n"); return; } this.App.WriteErrorLog("日志恢复 任务启动。"); // 当为容错恢复级别时,检查当前全部读者库的检索点是否符合要求 if (this.RecoverLevel == LibraryServer.RecoverLevel.Robust) { // 检查全部读者库的检索途径,看是否满足都有“所借册条码号”这个检索途径的这个条件 // return: // -1 出错 // 0 不满足 // 1 满足 nRet = this.App.DetectReaderDbFroms(out strError); if (nRet == -1) { this.AppendResultText("检查读者库检索点时发生错误: " + strError + "\r\n"); return; } if (nRet == 0) { this.AppendResultText("在容错恢复级别下,当前读者库中有部分或全部读者库缺乏“所借册条码号”检索点,无法进行日志恢复。请按照日志恢复要求,刷新所有读者库的检索点配置,然后再进行日志恢复\r\n"); return; } } // TODO: 检查当前是否有 重建检索点 的后台任务正在运行,或者还有没有运行完的部分。 // 要求重建检索点的任务运行完以后才能执行日志恢复任务 if (bClearFirst == true) { nRet = this.App.ClearAllDbs(this.RmsChannels, out strError); if (nRet == -1) { this.AppendResultText("清除全部数据库记录时发生错误: " + strError + "\r\n"); return; } } bool bStart = false; if (String.IsNullOrEmpty(strStartFileName) == true) { // 做所有文件 bStart = true; } // 列出所有日志文件 DirectoryInfo di = new DirectoryInfo(this.App.OperLog.Directory); FileInfo[] fis = di.GetFiles("*.log"); // BUG!!! 以前缺乏排序。2008/2/1 Array.Sort(fis, new FileInfoCompare()); for (int i = 0; i < fis.Length; i++) { if (this.Stopped == true) { break; } string strFileName = fis[i].Name; this.AppendResultText("检查文件 " + strFileName + "\r\n"); if (bStart == false) { // 从特定文件开始做 if (string.CompareOrdinal(strStartFileName, strFileName) <= 0) // 2015/9/12 从等号修改为 Compare { bStart = true; if (lStartIndex < 0) { lStartIndex = 0; } // lStartIndex = Convert.ToInt64(startinfo.Param); } } if (bStart == true) { nRet = DoOneLogFile(strFileName, lStartIndex, bContinueWhenError, out strError); if (nRet == -1) { goto ERROR1; } lStartIndex = 0; // 第一个文件以后的文件就全做了 } } this.AppendResultText("循环结束\r\n"); this.App.WriteErrorLog("日志恢复 任务结束。"); return; ERROR1: return; } finally { // this.App.HangupReason = HangupReason.None; this.App.ClearHangup("LogRecover"); } }
// Return() API 恢复动作 /* 日志记录格式 <root> <operation>return</operation> 操作类型 <itemBarcode>0000001</itemBarcode> 册条码号 <readerBarcode>R0000002</readerBarcode> 读者证条码号 <operator>test</operator> 操作者 <operTime>Fri, 08 Dec 2006 04:17:45 GMT</operTime> 操作时间 <overdues>...</overdues> 超期信息 通常内容为一个字符串,为一个<overdue>元素XML文本片断 <confirmItemRecPath>...</confirmItemRecPath> 辅助判断用的册记录路径 <readerRecord recPath='...'>...</readerRecord> 最新读者记录 <itemRecord recPath='...'>...</itemRecord> 最新册记录 </root> * * */ // parameters: // bForce 是否为容错状态。在容错状态下,如果遇到重复的册条码号,就算做第一条。 public int RecoverReturn( RmsChannelCollection Channels, RecoverLevel level, XmlDocument domLog, bool bForce, out string strError) { strError = ""; long lRet = 0; int nRet = 0; RmsChannel channel = Channels.GetChannel(this.WsUrl); if (channel == null) { strError = "get channel error"; return -1; } DO_SNAPSHOT: // 快照恢复 if (level == RecoverLevel.Snapshot) { XmlNode node = null; string strReaderXml = DomUtil.GetElementText(domLog.DocumentElement, "readerRecord", out node); if (node == null) { strError = "日志记录中缺<readerRecord>元素"; return -1; } string strReaderRecPath = DomUtil.GetAttr(node, "recPath"); string strItemXml = DomUtil.GetElementText(domLog.DocumentElement, "itemRecord", out node); if (node == null) { strError = "日志记录中缺<itemRecord>元素"; return -1; } string strItemRecPath = DomUtil.GetAttr(node, "recPath"); byte[] timestamp = null; byte[] output_timestamp = null; string strOutputPath = ""; // 写读者记录 lRet = channel.DoSaveTextRes(strReaderRecPath, strReaderXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入读者记录 '" + strReaderRecPath + "' 时发生错误: " + strError; return -1; } // 写册记录 lRet = channel.DoSaveTextRes(strItemRecPath, strItemXml, false, "content,ignorechecktimestamp", timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) { strError = "写入读者记录 '" + strReaderRecPath + "' 时发生错误: " + strError; return -1; } return 0; } // 逻辑恢复或者混合恢复 if (level == RecoverLevel.Logic || level == RecoverLevel.LogicAndSnapshot) { string strRecoverComment = ""; string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); string strReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "readerBarcode"); // 读入册记录 string strConfirmItemRecPath = DomUtil.GetElementText(domLog.DocumentElement, "confirmItemRecPath"); string strItemBarcode = DomUtil.GetElementText(domLog.DocumentElement, "itemBarcode"); if (String.IsNullOrEmpty(strItemBarcode) == true) { strError = "<strItemBarcode>元素值为空"; goto ERROR1; } string strItemXml = ""; string strOutputItemRecPath = ""; byte[] item_timestamp = null; // 如果已经有确定的册记录路径 if (String.IsNullOrEmpty(strConfirmItemRecPath) == false) { string strMetaData = ""; lRet = channel.GetRes(strConfirmItemRecPath, out strItemXml, out strMetaData, out item_timestamp, out strOutputItemRecPath, out strError); if (lRet == -1) { strError = "根据strConfirmItemRecPath '" + strConfirmItemRecPath + "' 获得册记录失败: " + strError; goto ERROR1; } // 需要检查记录中的<barcode>元素值是否匹配册条码号 } else { // 从册条码号获得册记录 List<string> aPath = null; // 获得册记录 // return: // -1 error // 0 not found // 1 命中1条 // >1 命中多于1条 nRet = this.GetItemRecXml( // Channels, channel, strItemBarcode, out strItemXml, 100, out aPath, out item_timestamp, out strError); if (nRet == 0) { strError = "册条码号 '" + strItemBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入册条码号为 '" + strItemBarcode + "' 的册记录时发生错误: " + strError; goto ERROR1; } if (aPath.Count > 1) { if (string.IsNullOrEmpty(strReaderBarcode) == true) { // 发生重条码号的时候,又没有读者证条码号辅助判断 if (bForce == false) { strError = "册条码号为 '" + strItemBarcode + "' 的册记录有 " + aPath.Count.ToString() + " 条,但此时日志记录中没有提供读者证条码号辅助判断,无法进行还书操作。"; goto ERROR1; } // TODO: 那就至少看看这些册中,哪些表明被人借阅着?如果正巧只有一个人借过,那就...。 strRecoverComment += "册条码号 " + strItemBarcode + "有 " + aPath.Count.ToString() + " 条重复记录,而且没有读者证条码号进行辅助选择。"; } /* strError = "册条码号为 '" + strItemBarcode + "' 的册记录有 " + aPath.Count.ToString() + " 条,但此时comfirmItemRecPath却为空"; goto ERROR1; * */ // bItemBarcodeDup = true; // 此时已经需要设置状态。虽然后面可以进一步识别出真正的册记录 /* // 构造strDupBarcodeList string[] pathlist = new string[aPath.Count]; aPath.CopyTo(pathlist); strDupBarcodeList = String.Join(",", pathlist); * */ List<string> aFoundPath = null; List<byte[]> aTimestamp = null; List<string> aItemXml = null; // 从若干重复条码号的册记录中,选出其中符合当前读者证条码号的 // return: // -1 出错 // 其他 选出的数量 nRet = FindItem( channel, strReaderBarcode, aPath, true, // 优化 out aFoundPath, out aItemXml, out aTimestamp, out strError); if (nRet == -1) { strError = "选择重复条码号的册记录时发生错误: " + strError; goto ERROR1; } if (nRet == 0) { strError = "册条码号 '" + strItemBarcode + "' 检索出的 " + aPath.Count + " 条记录中,没有任何一条其<borrower>元素表明了被读者 '" + strReaderBarcode + "' 借阅。"; goto ERROR1; } if (nRet > 1) { if (bForce == true) { // 容错情况下,选择第一个册条码号 strOutputItemRecPath = aFoundPath[0]; item_timestamp = aTimestamp[0]; strItemXml = aItemXml[0]; // TODO: 不过,应当在记录中记载注释,表示这是容错处理方式 if (string.IsNullOrEmpty(strReaderBarcode) == true) { strRecoverComment += "经过筛选,仍然有 " + aFoundPath.Count.ToString() + " 条册记录含有借阅者信息(无论什么读者证条码号),那么就只好选择其中第一个册记录 " + strOutputItemRecPath + " 进行还书操作。"; } else { strRecoverComment += "经过筛选,仍然有 " + aFoundPath.Count.ToString() + " 条册记录含有借阅者 '"+strReaderBarcode+"' 信息,那么就只好选择其中第一个册记录 " + strOutputItemRecPath + " 进行还书操作。"; } } else { strError = "册条码号为 '" + strItemBarcode + "' 并且<borrower>元素表明为读者 '" + strReaderBarcode + "' 借阅的册记录有 " + aFoundPath.Count.ToString() + " 条,无法进行还书操作。"; /* aDupPath = new string[aFoundPath.Count]; aFoundPath.CopyTo(aDupPath); * */ goto ERROR1; } } Debug.Assert(nRet == 1, ""); strOutputItemRecPath = aFoundPath[0]; item_timestamp = aTimestamp[0]; strItemXml = aItemXml[0]; } else { Debug.Assert(nRet == 1, ""); Debug.Assert(aPath.Count == 1, ""); if (nRet == 1) { strOutputItemRecPath = aPath[0]; } } } XmlDocument itemdom = null; nRet = LibraryApplication.LoadToDom(strItemXml, out itemdom, out strError); if (nRet == -1) { strError = "装载册记录进入XML DOM时发生错误: " + strError; goto ERROR1; } /// if (String.IsNullOrEmpty(strReaderBarcode) == true) { if (bForce == true) { // 容错的情况下,从册记录中获得借者证条码号 strReaderBarcode = DomUtil.GetElementText(itemdom.DocumentElement, "borrower"); if (String.IsNullOrEmpty(strReaderBarcode) == true) { strError = "在不知道读者证条码号的情况下,册记录中的<borrower>元素值为空。无法进行还书操作。"; goto ERROR1; } } else { strError = "日志记录中<readerBarcode>元素值为空"; goto ERROR1; } } // 读入读者记录 string strReaderXml = ""; string strOutputReaderRecPath = ""; byte[] reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strReaderBarcode, out strReaderXml, out strOutputReaderRecPath, out reader_timestamp, out strError); if (nRet == 0) { strError = "读者证条码号 '" + strReaderBarcode + "' 不存在"; goto ERROR1; } if (nRet == -1) { strError = "读入证条码号为 '" + strReaderBarcode + "' 的读者记录时发生错误: " + strError; goto ERROR1; } XmlDocument readerdom = null; nRet = LibraryApplication.LoadToDom(strReaderXml, out readerdom, out strError); if (nRet == -1) { strError = "装载读者记录进入XML DOM时发生错误: " + strError; goto ERROR1; } // 修改读者记录 // 修改册记录 nRet = ReturnChangeReaderAndItemRecord( // Channels, channel, strAction, strItemBarcode, strReaderBarcode, domLog, strRecoverComment, ref readerdom, ref itemdom, out strError); if (nRet == -1) goto ERROR1; // 写回读者、册记录 byte[] output_timestamp = null; string strOutputPath = ""; // 写回读者记录 lRet = channel.DoSaveTextRes(strOutputReaderRecPath, readerdom.OuterXml, false, "content,ignorechecktimestamp", reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; // 写回册记录 lRet = channel.DoSaveTextRes(strOutputItemRecPath, itemdom.OuterXml, false, "content,ignorechecktimestamp", item_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) goto ERROR1; } // 容错恢复 if (level == RecoverLevel.Robust) { string strRecoverComment = ""; string strAction = DomUtil.GetElementText(domLog.DocumentElement, "action"); string strReaderBarcode = DomUtil.GetElementText(domLog.DocumentElement, "readerBarcode"); // 读入册记录 string strConfirmItemRecPath = DomUtil.GetElementText(domLog.DocumentElement, "confirmItemRecPath"); string strItemBarcode = DomUtil.GetElementText(domLog.DocumentElement, "itemBarcode"); if (String.IsNullOrEmpty(strItemBarcode) == true) { strError = "<strItemBarcode>元素值为空"; goto ERROR1; } string strItemXml = ""; string strOutputItemRecPath = ""; byte[] item_timestamp = null; // 从册条码号获得册记录 List<string> aPath = null; bool bDupItemBarcode = false; // 册条码号是否发生了重复 // 获得册记录 // return: // -1 error // 0 not found // 1 命中1条 // >1 命中多于1条 nRet = this.GetItemRecXml( // Channels, channel, strItemBarcode, out strItemXml, 100, out aPath, out item_timestamp, out strError); if (nRet == 0) { strError = "册条码号 '" + strItemBarcode + "' 不存在"; // TODO: 记入信息文件 XmlNode node = null; strItemXml = DomUtil.GetElementText(domLog.DocumentElement, "itemRecord", out node); if (node == null) { strError = "日志记录中缺<itemRecord>元素"; return -1; } string strItemRecPath = DomUtil.GetAttr(node, "recPath"); if (String.IsNullOrEmpty(strItemRecPath) == true) { strError = "日志记录中<itemRecord>元素缺recPath属性"; return -1; } // 新增一条册记录 strOutputItemRecPath = ResPath.GetDbName(strItemRecPath) + "/?"; item_timestamp = null; } else { if (nRet == -1) { strError = "读入册条码号为 '" + strItemBarcode + "' 的册记录时发生错误: " + strError; return -1; } if (aPath.Count > 1) { bDupItemBarcode = true; if (string.IsNullOrEmpty(strReaderBarcode) == true) { // 发生重条码号的时候,又没有读者证条码号辅助判断 if (bForce == false) { strError = "册条码号为 '" + strItemBarcode + "' 的册记录有 " + aPath.Count.ToString() + " 条,但此时日志记录中没有提供读者证条码号辅助判断,无法进行还书操作。"; return -1; } // TODO: 那就至少看看这些册中,哪些表明被人借阅着?如果正巧只有一个人借过,那就...。 strRecoverComment += "册条码号 " + strItemBarcode + " 有 " + aPath.Count.ToString() + " 条重复记录,而且没有读者证条码号进行辅助选择。"; } List<string> aFoundPath = null; List<byte[]> aTimestamp = null; List<string> aItemXml = null; // 从若干重复条码号的册记录中,选出其中符合当前读者证条码号的 // return: // -1 出错 // 其他 选出的数量 nRet = FindItem( channel, strReaderBarcode, aPath, true, // 优化 out aFoundPath, out aItemXml, out aTimestamp, out strError); if (nRet == -1) { strError = "选择重复条码号的册记录时发生错误: " + strError; return -1; } if (nRet == 0) { if (bDupItemBarcode == false) { // 没有重复册条码号的情况下才作 // 需要把根据“所借册条码号”清除读者记录中借阅信息的动作提前进行? 这样遇到特殊情况范围时,至少读者记录中的信息是被清除了的,这是容错的需要 string strError_1 = ""; nRet = ReturnAllReader( // Channels, channel, strItemBarcode, "", out strError_1); if (nRet == -1) { // 故意不报,继续处理 } } strError = "册条码号 '" + strItemBarcode + "' 检索出的 " + aPath.Count + " 条记录中,没有任何一条其<borrower>元素表明了被读者 '" + strReaderBarcode + "' 借阅。"; return -1; } if (nRet > 1) { if (bForce == true) { // 容错情况下,选择第一个册条码号 strOutputItemRecPath = aFoundPath[0]; item_timestamp = aTimestamp[0]; strItemXml = aItemXml[0]; // TODO: 不过,应当在记录中记载注释,表示这是容错处理方式 if (string.IsNullOrEmpty(strReaderBarcode) == true) { strRecoverComment += "经过筛选,仍然有 " + aFoundPath.Count.ToString() + " 条册记录含有借阅者信息(无论什么读者证条码号),那么就只好选择其中第一个册记录 " + strOutputItemRecPath + " 进行还书操作。"; } else { strRecoverComment += "经过筛选,仍然有 " + aFoundPath.Count.ToString() + " 条册记录含有借阅者 '" + strReaderBarcode + "' 信息,那么就只好选择其中第一个册记录 " + strOutputItemRecPath + " 进行还书操作。"; } } else { strError = "册条码号为 '" + strItemBarcode + "' 并且<borrower>元素表明为读者 '" + strReaderBarcode + "' 借阅的册记录有 " + aFoundPath.Count.ToString() + " 条,无法进行还书操作。"; return -1; } } Debug.Assert(nRet == 1, ""); strOutputItemRecPath = aFoundPath[0]; item_timestamp = aTimestamp[0]; strItemXml = aItemXml[0]; } else { Debug.Assert(nRet == 1, ""); Debug.Assert(aPath.Count == 1, ""); if (nRet == 1) { strOutputItemRecPath = aPath[0]; } } } //// XmlDocument itemdom = null; nRet = LibraryApplication.LoadToDom(strItemXml, out itemdom, out strError); if (nRet == -1) { strError = "装载册记录进入XML DOM时发生错误: " + strError; goto ERROR1; } /// if (String.IsNullOrEmpty(strReaderBarcode) == true) { if (bForce == true) { // 容错的情况下,从册记录中获得借者证条码号 strReaderBarcode = DomUtil.GetElementText(itemdom.DocumentElement, "borrower"); if (String.IsNullOrEmpty(strReaderBarcode) == true) { strError = "在不知道读者证条码号的情况下,册记录中的<borrower>元素值为空。无法进行还书操作。"; return -1; } } else { strError = "日志记录中<readerBarcode>元素值为空"; return -1; } } // 读入读者记录 string strReaderXml = ""; string strOutputReaderRecPath = ""; byte[] reader_timestamp = null; nRet = this.GetReaderRecXml( // Channels, channel, strReaderBarcode, out strReaderXml, out strOutputReaderRecPath, out reader_timestamp, out strError); if (nRet == 0) { strError = "读者证条码号 '" + strReaderBarcode + "' 不存在"; // TODO: 记入信息文件 // 从日志记录中获得读者记录 XmlNode node = null; strReaderXml = DomUtil.GetElementText(domLog.DocumentElement, "readerRecord", out node); if (node == null) { strError = "日志记录中缺<readerRecord>元素"; return -1; } string strReaderRecPath = DomUtil.GetAttr(node, "recPath"); if (String.IsNullOrEmpty(strReaderRecPath) == true) { strError = "日志记录中<readerRecord>元素缺recPath属性"; return -1; } // 新增一条读者记录 strOutputReaderRecPath = ResPath.GetDbName(strReaderRecPath) + "/?"; reader_timestamp = null; } else { if (nRet == -1) { strError = "读入证条码号为 '" + strReaderBarcode + "' 的读者记录时发生错误: " + strError; return -1; } } XmlDocument readerdom = null; nRet = LibraryApplication.LoadToDom(strReaderXml, out readerdom, out strError); if (nRet == -1) { strError = "装载读者记录进入XML DOM时发生错误: " + strError; return -1; } // 修改读者记录 // 修改册记录 nRet = ReturnChangeReaderAndItemRecord( // Channels, channel, strAction, strItemBarcode, strReaderBarcode, domLog, strRecoverComment, ref readerdom, ref itemdom, out strError); if (nRet == -1) return -1; // 在容错(并且没有重复册条码号的情况下)的情况下,需要利用读者库的“所借册条码号”检索途径,把除了当前关注的读者记录以外的潜在相关读者记录调出, // 把它们中的相关<borrows/borrow>抹除,以免造成多头的借阅信息。 if (bDupItemBarcode == false) { nRet = ReturnAllReader( // Channels, channel, strItemBarcode, strOutputReaderRecPath, out strError); if (nRet == -1) { // 故意不报,继续处理 } } //// // 写回读者、册记录 byte[] output_timestamp = null; string strOutputPath = ""; // 写回读者记录 lRet = channel.DoSaveTextRes(strOutputReaderRecPath, readerdom.OuterXml, false, "content,ignorechecktimestamp", reader_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) return -1; // 写回册记录 lRet = channel.DoSaveTextRes(strOutputItemRecPath, itemdom.OuterXml, false, "content,ignorechecktimestamp", item_timestamp, out output_timestamp, out strOutputPath, out strError); if (lRet == -1) return -1; } return 0; ERROR1: if (level == RecoverLevel.LogicAndSnapshot) { level = RecoverLevel.Snapshot; goto DO_SNAPSHOT; } return -1; }