// 第二阶段:根据操作日志进行同步 // 注:中途遇到异常(例如 Loader 抛出异常),可能会丢失 INSERT_BATCH 条以内的日志记录写入 operlog 表 // parameters: // strLastDate 处理中断或者结束时返回最后处理过的日期 // last_index 处理或中断返回时最后处理过的位置。以后继续处理的时候可以从这个偏移开始 // return: // -1 出错 // 0 中断 // 1 完成 public static async Task <ReplicationResult> DoReplication( string strStartDate, string strEndDate, LogType logType, // string serverVersion, CancellationToken token) { string strLastDate = ""; long last_index = -1; // -1 表示尚未处理 // bool bUserChanged = false; ProcessInfo info = new ProcessInfo(); // strStartDate 里面可能会包含 ":1-100" 这样的附加成分 StringUtil.ParseTwoPart(strStartDate, ":", out string strLeft, out string strRight); strStartDate = strLeft; if (string.IsNullOrEmpty(strStartDate) == true) { return(new ReplicationResult { Value = -1, ErrorInfo = "DoReplication() 出错: strStartDate 参数值不应为空" }); } LibraryChannel channel = App.CurrentApp.GetChannel(); var old_timeout = channel.Timeout; channel.Timeout = TimeSpan.FromSeconds(10); try { int nRet = OperLogLoader.MakeLogFileNames(strStartDate, strEndDate, true, // 是否包含扩展名 ".log" out List <string> dates, out string strWarning, out string strError); if (nRet == -1) { return(new ReplicationResult { Value = -1, ErrorInfo = strError }); } if (dates.Count > 0 && string.IsNullOrEmpty(strRight) == false) { dates[0] = dates[0] + ":" + strRight; } using (BiblioCacheContext context = new BiblioCacheContext()) { context.Database.EnsureCreated(); ProgressEstimate estimate = new ProgressEstimate(); OperLogLoader loader = new OperLogLoader { Channel = channel, Stop = null, // this.Progress; // loader.owner = this; Estimate = estimate, Dates = dates, Level = 0, AutoCache = false, CacheDir = "", LogType = logType, Filter = "setReaderInfo,borrow,return,setSystemParameter,writeRes", // 借书还书时候都会修改读者记录 // ServerVersion = serverVersion }; //TimeSpan old_timeout = channel.Timeout; //channel.Timeout = new TimeSpan(0, 2, 0); // 二分钟 // loader.Prompt += Loader_Prompt; try { // int nRecCount = 0; string strLastItemDate = ""; long lLastItemIndex = -1; // TODO: 计算遍历耗费的时间。如果太短了,要想办法让调主知道这一点,放缓重新调用的节奏,以避免 CPU 和网络资源太高 foreach (OperLogItem item in loader) { token.ThrowIfCancellationRequested(); //if (stop != null) // stop.SetMessage("正在同步 " + item.Date + " " + item.Index.ToString() + " " + estimate.Text + "..."); if (string.IsNullOrEmpty(item.Xml) == true) { goto CONTINUE; } XmlDocument dom = new XmlDocument(); try { dom.LoadXml(item.Xml); } catch (Exception ex) { /* * if (this.HasLoaderPrompt()) * { * strError = logType.ToString() + "日志记录 " + item.Date + " " + item.Index.ToString() + " XML 装入 DOM 的时候发生错误: " + ex.Message; * MessagePromptEventArgs e = new MessagePromptEventArgs * { * MessageText = strError + "\r\n\r\n是否跳过此条继续处理?\r\n\r\n(确定: 跳过; 取消: 停止全部操作)", * IncludeOperText = true, * // + "\r\n\r\n是否跳过此条继续处理?", * Actions = "yes,cancel" * }; * Loader_Prompt(channel, e); * if (e.ResultAction == "cancel") * throw new ChannelException(channel.ErrorCode, strError); * else if (e.ResultAction == "yes") * continue; * else * { * // no 也是抛出异常。因为继续下一批代价太大 * throw new ChannelException(channel.ErrorCode, strError); * } * } * else */ throw new ChannelException(channel.ErrorCode, strError); } string strOperation = DomUtil.GetElementText(dom.DocumentElement, "operation"); if (strOperation == "setReaderInfo") { var trace_result = TraceSetReaderInfo( dom, info); if (trace_result.Value == -1) { WpfClientInfo.WriteErrorLog("同步 " + item.Date + " " + item.Index.ToString() + " 时出错: " + trace_result.ErrorInfo); } } else if (strOperation == "borrow" || strOperation == "return") { var trace_result = await TraceBorrowOrReturn( dom, info); if (trace_result.Value == -1) { WpfClientInfo.WriteErrorLog("同步 " + item.Date + " " + item.Index.ToString() + " 时出错: " + trace_result.ErrorInfo); } } else if (strOperation == "setSystemParameter") { var trace_result = TraceSetSystemParameter( dom, info); if (trace_result.Value == -1) { WpfClientInfo.WriteErrorLog("同步 " + item.Date + " " + item.Index.ToString() + " 时出错: " + trace_result.ErrorInfo); } } else if (strOperation == "writeRes") { var trace_result = TraceWriteRes( dom, info); if (trace_result.Value == -1) { WpfClientInfo.WriteErrorLog("同步 " + item.Date + " " + item.Index.ToString() + " 时出错: " + trace_result.ErrorInfo); } } else { continue; } #if NO if (nRet == -1) { strError = "同步 " + item.Date + " " + item.Index.ToString() + " 时出错: " + strError; /* * if (this.HasLoaderPrompt()) * { * MessagePromptEventArgs e = new MessagePromptEventArgs * { * MessageText = strError + "\r\n\r\n是否跳过此条继续处理?\r\n\r\n(确定: 跳过; 取消: 停止全部操作)", * IncludeOperText = true, * // + "\r\n\r\n是否跳过此条继续处理?", * Actions = "yes,cancel" * }; * Loader_Prompt(channel, e); * if (e.ResultAction == "cancel") * throw new Exception(strError); * else if (e.ResultAction == "yes") * continue; * else * { * // no 也是抛出异常。因为继续下一批代价太大 * throw new Exception(strError); * } * } * else */ throw new ChannelException(channel.ErrorCode, strError); } #endif // lProcessCount++; CONTINUE: // 便于循环外获得这些值 strLastItemDate = item.Date; lLastItemIndex = item.Index + 1; // index = 0; // 第一个日志文件后面的,都从头开始了 } // 记忆 strLastDate = strLastItemDate; last_index = lLastItemIndex; } finally { // loader.Prompt -= Loader_Prompt; channel.Timeout = old_timeout; } } return(new ReplicationResult { Value = last_index == -1 ? 0 : 1, LastDate = strLastDate, LastIndex = last_index, ProcessInfo = info }); } catch (ChannelException ex) { return(new ReplicationResult { Value = -1, ErrorInfo = ex.Message, ProcessInfo = info }); } catch (InterruptException ex) { // 2019/7/4 return(new ReplicationResult { Value = -1, ErrorInfo = ex.Message, ProcessInfo = info }); } catch (Exception ex) { string strError = "DoReplication() exception: " + ExceptionUtil.GetDebugText(ex); return(new ReplicationResult { Value = -1, ErrorInfo = strError, ProcessInfo = info }); } finally { channel.Timeout = old_timeout; App.CurrentApp.ReturnChannel(channel); } }
// 同步 // 注:中途遇到异常(例如 Loader 抛出异常),可能会丢失 INSERT_BATCH 条以内的日志记录写入 operlog 表 // parameters: // strLastDate 处理中断或者结束时返回最后处理过的日期 // last_index 处理或中断返回时最后处理过的位置。以后继续处理的时候可以从这个偏移开始 // return: // -1 出错 // 0 中断 // 1 完成 public static ReplicationResult DoReplication( LibraryChannel channel, string strStartDate, string strEndDate, LogType logType, CancellationToken token) { string strLastDate = ""; long last_index = -1; // -1 表示尚未处理 // bool bUserChanged = false; // strStartDate 里面可能会包含 ":1-100" 这样的附加成分 StringUtil.ParseTwoPart(strStartDate, ":", out string strLeft, out string strRight); strStartDate = strLeft; if (string.IsNullOrEmpty(strStartDate) == true) { return(new ReplicationResult { Value = -1, ErrorInfo = "DoReplication() 出错: strStartDate 参数值不应为空" }); } try { List <string> dates = null; int nRet = OperLogLoader.MakeLogFileNames(strStartDate, strEndDate, true, // 是否包含扩展名 ".log" out dates, out string strWarning, out string strError); if (nRet == -1) { return(new ReplicationResult { Value = -1, ErrorInfo = strError }); } if (dates.Count > 0 && string.IsNullOrEmpty(strRight) == false) { dates[0] = dates[0] + ":" + strRight; } channel.Timeout = new TimeSpan(0, 1, 0); // 一分钟 // using (SQLiteConnection connection = new SQLiteConnection(this._connectionString)) { ProgressEstimate estimate = new ProgressEstimate(); OperLogLoader loader = new OperLogLoader { Channel = channel, Stop = null, // this.Progress; // loader.owner = this; Estimate = estimate, Dates = dates, Level = 2, // Program.MainForm.OperLogLevel; AutoCache = false, CacheDir = "", LogType = logType, Filter = "setReaderInfo" }; loader.Prompt += Loader_Prompt; try { // int nRecCount = 0; string strLastItemDate = ""; long lLastItemIndex = -1; foreach (OperLogItem item in loader) { token.ThrowIfCancellationRequested(); //if (stop != null) // stop.SetMessage("正在同步 " + item.Date + " " + item.Index.ToString() + " " + estimate.Text + "..."); if (string.IsNullOrEmpty(item.Xml) == true) { goto CONTINUE; } XmlDocument dom = new XmlDocument(); try { dom.LoadXml(item.Xml); } catch (Exception ex) { #if NO DialogResult result = System.Windows.Forms.DialogResult.No; strError = logType.ToString() + "日志记录 " + item.Date + " " + item.Index.ToString() + " XML 装入 DOM 的时候发生错误: " + ex.Message; string strText = strError; this.Invoke((Action)(() => { result = MessageBox.Show(this, strText + "\r\n\r\n是否跳过此条记录继续处理?", "ReportForm", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1); })); if (result == System.Windows.Forms.DialogResult.No) { return(-1); } // 记入日志,继续处理 this.GetErrorInfoForm().WriteHtml(strError + "\r\n"); continue; #endif if (Prompt != null) { strError = logType.ToString() + "日志记录 " + item.Date + " " + item.Index.ToString() + " XML 装入 DOM 的时候发生错误: " + ex.Message; MessagePromptEventArgs e = new MessagePromptEventArgs { MessageText = strError + "\r\n\r\n是否跳过此条继续处理?\r\n\r\n(确定: 跳过; 取消: 停止全部操作)", IncludeOperText = true, // + "\r\n\r\n是否跳过此条继续处理?", Actions = "yes,cancel" }; Prompt(channel, e); if (e.ResultAction == "cancel") { throw new ChannelException(channel.ErrorCode, strError); } else if (e.ResultAction == "yes") { continue; } else { // no 也是抛出异常。因为继续下一批代价太大 throw new ChannelException(channel.ErrorCode, strError); } } else { throw new ChannelException(channel.ErrorCode, strError); } } string strOperation = DomUtil.GetElementText(dom.DocumentElement, "operation"); if (strOperation == "setReaderInfo") { nRet = TraceSetReaderInfo( dom, out strError); } else { continue; } if (nRet == -1) { strError = "同步 " + item.Date + " " + item.Index.ToString() + " 时出错: " + strError; if (Prompt != null) { MessagePromptEventArgs e = new MessagePromptEventArgs { MessageText = strError + "\r\n\r\n是否跳过此条继续处理?\r\n\r\n(确定: 跳过; 取消: 停止全部操作)", IncludeOperText = true, // + "\r\n\r\n是否跳过此条继续处理?", Actions = "yes,cancel" }; Prompt(channel, e); if (e.ResultAction == "cancel") { throw new Exception(strError); } else if (e.ResultAction == "yes") { continue; } else { // no 也是抛出异常。因为继续下一批代价太大 throw new Exception(strError); } } else { throw new ChannelException(channel.ErrorCode, strError); } } // lProcessCount++; CONTINUE: // 便于循环外获得这些值 strLastItemDate = item.Date; lLastItemIndex = item.Index + 1; // index = 0; // 第一个日志文件后面的,都从头开始了 } // 记忆 strLastDate = strLastItemDate; last_index = lLastItemIndex; } finally { loader.Prompt -= Loader_Prompt; } } return(new ReplicationResult { Value = last_index == -1 ? 0 : 1, LastDate = strLastDate, LastIndex = last_index }); } catch (Exception ex) { string strError = "ReportForm DoReplication() exception: " + ExceptionUtil.GetDebugText(ex); return(new ReplicationResult { Value = -1, ErrorInfo = strError }); } }
// return: // -1 出错 // 0 中断 // 1 完成 int ProcessOperLogs(ServerReplicationStart breakpoint, bool bContinueWhenError, Delegate_saveBreakPoint func_saveBreakPoint, string strStyle, out string strError) { strError = ""; DateTime now = DateTime.Now; string strStartDate = breakpoint.Date; // +":" + breakpoint.Offset.ToString(); string strEndDate = DateTimeUtil.DateTimeToString8(now); List <string> filenames = null; string strWarning = ""; // 根据日期范围,发生日志文件名 // parameters: // strStartDate 起始日期。8字符 // strEndDate 结束日期。8字符 // return: // -1 错误 // 0 成功 int nRet = OperLogLoader.MakeLogFileNames(strStartDate, strEndDate, true, // true, out filenames, out strWarning, out strError); if (nRet == -1) { return(-1); } if (String.IsNullOrEmpty(strWarning) == false) { // 可能有超过当天日期的被舍弃 } #if NO if (filenames.Count > 0 && string.IsNullOrEmpty(strEndRange) == false) { filenames[filenames.Count - 1] = filenames[filenames.Count - 1] + ":" + strEndRange; } if (filenames.Count > 0 && string.IsNullOrEmpty(strStartRange) == false) { filenames[0] = filenames[0] + ":" + strStartRange; } #endif if (filenames.Count > 0 && breakpoint.Index > 0) { filenames[0] = filenames[0] + ":" + breakpoint.Index.ToString() + "-"; } string strTempFileName = ""; strTempFileName = this.App.GetTempFileName("attach"); #if NO LibraryChannel channel = new LibraryChannel(); channel.Timeout = TimeSpan.FromSeconds(30); channel.Url = this.m_strUrl; channel.BeforeLogin += new BeforeLoginEventHandle(Channel_BeforeLogin); #endif LibraryChannel channel = this.GetChannel(); _stop.BeginLoop(); try { DateTime lastSaveTime = DateTime.Now; TimeSpan delta = TimeSpan.FromMinutes(1); OperLogLoader loader = new OperLogLoader(); loader.Channel = channel; loader.Stop = this._stop; // loader.estimate = estimate; loader.Dates = filenames; loader.Level = 0; // 0 完整级别 loader.ReplicationLevel = true; loader.AutoCache = false; loader.CacheDir = ""; // loader.Filter = "borrow,return,setReaderInfo,setBiblioInfo,setEntity,setOrder,setIssue,setComment,amerce,passgate,getRes"; loader.LogType = LogType.OperLog; foreach (OperLogItem item in loader) { if (this.Stopped) { strError = "用户中断"; return(0); } string date = item.Date; // 处理 // this.AppendResultText("--" + date + "\r\n"); this.SetProgressText(date + ":" + item.Index.ToString()); Stream attachment = null; try { if (item.AttachmentLength != 0) { // return: // -1 出错 // 0 没有找到日志记录 // >0 附件总长度 long lRet = loader.DownloadAttachment(item, strTempFileName, out strError); if (lRet == -1 || lRet == 0) { strError = "做日志记录 " + item.Date + " " + (item.Index).ToString() + " 时,下载附件部分发生错误:" + strError; this.AppendResultText("*** " + strError + "\r\n"); if (// this.RecoverLevel == RecoverLevel.Logic && bContinueWhenError == false) { return(-1); } goto CONTINUE; } attachment = File.Open(strTempFileName, FileMode.Open); } nRet = this.DoOperLogRecord( this.RecoverLevel, item.Xml, attachment, strStyle, out strError); if (nRet == -1) { strError = "做日志记录 " + item.Date + " " + (item.Index).ToString() + " 时发生错误:" + strError; this.AppendResultText("*** " + strError + "\r\n"); // 2007/6/25 // 如果为纯逻辑恢复(并且 bContinueWhenError 为 false),遇到错误就停下来。这便于进行测试。 // 若不想停下来,可以选择“逻辑+快照”型,或者设置 bContinueWhenError 为 true if (// this.RecoverLevel == RecoverLevel.Logic && bContinueWhenError == false) { return(-1); } } } finally { if (attachment != null) { attachment.Close(); attachment = null; } } CONTINUE: breakpoint.Date = date; breakpoint.Index = item.Index + 1; // 中途记忆断点 if (DateTime.Now - lastSaveTime >= delta) { if (func_saveBreakPoint != null) { func_saveBreakPoint(breakpoint); } lastSaveTime = DateTime.Now; } } return(1); } catch (InterruptException) { strError = "用户中断"; return(0); } catch (ChannelException ex) { strError = "处理过程出错: " + ex.Message; return(-1); } catch (Exception ex) { strError = "ProcessOperLogs() 出现异常: " + ExceptionUtil.GetDebugText(ex); return(-1); } finally { if (File.Exists(strTempFileName)) { File.Delete(strTempFileName); } _stop.EndLoop(); #if NO channel.BeforeLogin -= new BeforeLoginEventHandle(Channel_BeforeLogin); channel.Close(); #endif this.ReturnChannel(channel); } }