MultiReportExcelMerger.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. 
  2. using System;
  3. using System.IO;
  4. using System.Net;
  5. using System.Collections.Generic;
  6. using Microsoft.Office.Interop.Excel;
  7. public class MultiReportExcelMerger
  8. {
  9. public string MergeReportsUsingInterop(Dictionary<string, string> reportUrls, string outputPath)
  10. {
  11. // 创建临时目录存放下载的文件
  12. string tempDir = Path.Combine(Path.GetTempPath(), "ExcelMerge_" + Guid.NewGuid().ToString());
  13. Directory.CreateDirectory(tempDir);
  14. List<string> tempFiles = new List<string>();
  15. Application excelApp = null;
  16. Workbook newWorkbook = null;
  17. using (WebClient webClient = new WebClient())
  18. {
  19. try
  20. {
  21. // 下载所有报表到临时文件
  22. foreach (var report in reportUrls)
  23. {
  24. string url = report.Key;
  25. string sheetName = report.Value;
  26. // 添加Excel格式参数
  27. string excelUrl = url + "&format=excel";
  28. // 下载Excel数据
  29. byte[] excelData = webClient.DownloadData(excelUrl);
  30. // 保存到临时文件
  31. string tempFilePath = Path.Combine(tempDir, $"{sheetName}_{Guid.NewGuid().ToString()}.xlsx");
  32. File.WriteAllBytes(tempFilePath, excelData);
  33. tempFiles.Add(tempFilePath);
  34. }
  35. // 使用Excel Interop合并文件
  36. excelApp = new Application();
  37. excelApp.Visible = false;
  38. excelApp.DisplayAlerts = false;
  39. newWorkbook = excelApp.Workbooks.Add(XlWBATemplate.xlWBATWorksheet);
  40. // 删除默认创建的空工作表 - 修复版本
  41. while (newWorkbook.Worksheets.Count > 1)
  42. {
  43. Worksheet sheetToDelete = newWorkbook.Worksheets[1] as Worksheet;
  44. if (sheetToDelete != null)
  45. {
  46. // 执行删除操作
  47. sheetToDelete.Delete();
  48. }
  49. ReleaseComObject(sheetToDelete);
  50. }
  51. // 重命名唯一的工作表
  52. Worksheet firstSheet = (Worksheet)newWorkbook.Worksheets[1];
  53. firstSheet.Name = "合并报表";
  54. ReleaseComObject(firstSheet);
  55. // 合并所有工作表
  56. foreach (string tempFile in tempFiles)
  57. {
  58. Workbook sourceWorkbook = null;
  59. Worksheet sourceSheet = null;
  60. try
  61. {
  62. sourceWorkbook = excelApp.Workbooks.Open(tempFile);
  63. sourceSheet = (Worksheet)sourceWorkbook.Worksheets[1]; // 获取第一个工作表
  64. // 获取新工作簿中的最后一个工作表作为复制目标
  65. Worksheet lastSheetInNew = (Worksheet)newWorkbook.Worksheets[newWorkbook.Worksheets.Count];
  66. // 复制工作表到新工作簿
  67. sourceSheet.Copy(Type.Missing, lastSheetInNew);
  68. ReleaseComObject(lastSheetInNew);
  69. // 重命名复制的工作表
  70. string fileName = Path.GetFileNameWithoutExtension(tempFile);
  71. string sheetName = fileName.Split('_')[0];
  72. Worksheet newlyCopiedSheet = (Worksheet)newWorkbook.Worksheets[newWorkbook.Worksheets.Count];
  73. newlyCopiedSheet.Name = GetValidWorksheetName(sheetName, newWorkbook);
  74. ReleaseComObject(newlyCopiedSheet);
  75. }
  76. finally
  77. {
  78. // 确保释放COM对象
  79. if (sourceSheet != null)
  80. {
  81. ReleaseComObject(sourceSheet);
  82. }
  83. if (sourceWorkbook != null)
  84. {
  85. sourceWorkbook.Close(false);
  86. ReleaseComObject(sourceWorkbook);
  87. }
  88. }
  89. }
  90. // 删除最初创建的空白工作表
  91. try
  92. {
  93. Worksheet mergeSheet = (Worksheet)newWorkbook.Worksheets["合并报表"];
  94. mergeSheet.Delete();
  95. ReleaseComObject(mergeSheet);
  96. }
  97. catch (Exception ex)
  98. {
  99. Console.WriteLine($"删除初始工作表时出错: {ex.Message}");
  100. // 继续执行,这个错误不影响主要功能
  101. }
  102. // 保存合并后的工作簿
  103. newWorkbook.SaveAs(outputPath);
  104. newWorkbook.Close(false);
  105. ReleaseComObject(newWorkbook);
  106. newWorkbook = null;
  107. return "成功";
  108. }
  109. catch (Exception ex)
  110. {
  111. return $"出错: {ex.Message}";
  112. throw;
  113. }
  114. finally
  115. {
  116. // 确保正确释放所有COM对象
  117. if (newWorkbook != null)
  118. {
  119. try
  120. {
  121. newWorkbook.Close(false);
  122. ReleaseComObject(newWorkbook);
  123. }
  124. catch { }
  125. }
  126. // 确保Excel进程正确关闭
  127. if (excelApp != null)
  128. {
  129. try
  130. {
  131. excelApp.Quit();
  132. ReleaseComObject(excelApp);
  133. }
  134. catch (Exception ex)
  135. {
  136. Console.WriteLine($"关闭Excel应用时出错: {ex.Message}");
  137. }
  138. }
  139. // 清理临时文件
  140. try
  141. {
  142. foreach (string tempFile in tempFiles)
  143. {
  144. if (File.Exists(tempFile))
  145. File.Delete(tempFile);
  146. }
  147. if (Directory.Exists(tempDir))
  148. Directory.Delete(tempDir, true);
  149. }
  150. catch (Exception ex)
  151. {
  152. Console.WriteLine($"清理临时文件时出错: {ex.Message}");
  153. }
  154. }
  155. }
  156. }
  157. // 辅助方法:正确释放COM对象
  158. private void ReleaseComObject(object obj)
  159. {
  160. try
  161. {
  162. if (obj != null && System.Runtime.InteropServices.Marshal.IsComObject(obj))
  163. {
  164. System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
  165. }
  166. }
  167. catch (Exception ex)
  168. {
  169. Console.WriteLine($"释放COM对象时出错: {ex.Message}");
  170. }
  171. finally
  172. {
  173. obj = null;
  174. // 在.NET 4.5中谨慎使用GC,避免过度调用
  175. GC.Collect();
  176. GC.WaitForPendingFinalizers();
  177. }
  178. }
  179. // 辅助方法:确保工作表名称有效
  180. private string GetValidWorksheetName(string proposedName, Workbook workbook)
  181. {
  182. string validName = proposedName ?? "Sheet";
  183. // 截断超过31个字符的名称
  184. if (validName.Length > 31)
  185. {
  186. validName = validName.Substring(0, 31);
  187. }
  188. // 移除无效字符
  189. char[] invalidChars = { '\\', '/', '?', '*', '[', ']', ':' };
  190. foreach (char c in invalidChars)
  191. {
  192. validName = validName.Replace(c.ToString(), "");
  193. }
  194. // 确保名称不为空
  195. if (string.IsNullOrEmpty(validName))
  196. {
  197. validName = "Sheet";
  198. }
  199. // 检查是否与现有工作表名称冲突
  200. int counter = 1;
  201. string originalName = validName;
  202. while (WorksheetExists(validName, workbook))
  203. {
  204. validName = $"{originalName}_{counter}";
  205. counter++;
  206. // 如果名称太长,截断
  207. if (validName.Length > 31)
  208. {
  209. validName = originalName.Substring(0, Math.Max(0, 28 - counter.ToString().Length)) + $"_{counter}";
  210. }
  211. }
  212. return validName;
  213. }
  214. // 辅助方法:检查工作表名称是否已存在
  215. private bool WorksheetExists(string sheetName, Workbook workbook)
  216. {
  217. try
  218. {
  219. foreach (Worksheet sheet in workbook.Worksheets)
  220. {
  221. if (sheet.Name.Equals(sheetName, StringComparison.OrdinalIgnoreCase))
  222. {
  223. ReleaseComObject(sheet);
  224. return true;
  225. }
  226. ReleaseComObject(sheet);
  227. }
  228. return false;
  229. }
  230. catch
  231. {
  232. return false;
  233. }
  234. }
  235. }