callm 5 днів тому
батько
коміт
82a93f75ab

+ 52 - 0
UAS_MES_ZDXW/.workbuddy/memory/2026-11-03.md

@@ -0,0 +1,52 @@
+## 2026-11-03 工作日志
+
+### Query_SOP SaveToImage 异常修复
+- 问题:`sheet.SaveToImage(filePath)` 抛出 `ArgumentOutOfRangeException: Index is out of range`
+- 根因分析:
+  1. try-catch 被注释掉(uploadfile + SaveFileToJPG + SaveFileToPDF 三处),异常直接弹出
+  2. CreateEmptySheet + CopyFrom 可能未正确复制 sheet 内部状态(如 AllocatedRange),导致 SaveToImage 渲染时索引越界
+  3. 重试循环内重复执行 `sheet.Pictures.Add`,每次 retry 都给同一 sheet 添加电子受控章和签名图片,图片集合索引冲突
+- 修复:
+  1. 恢复全部 3 处 try-catch
+  2. 去掉 CreateEmptySheet + CopyFrom,直接传 `workbook.Worksheets[i]` 给 SaveFileToJPG/SaveFileToPDF(串行模式下直接修改原 sheet 安全)
+  3. 图片添加(Pictures.Add)移到重试循环外,只添加一次
+  4. SaveFileToJPG/SaveFileToPDF 中目录创建和文件路径也移到循环外,避免重复
+
+### Spire.XLS 依赖 DLL 补充(前次会话遗留)
+- Spire.XLS.dll v8.8.1.6040 依赖 Spire.License v1.3.6.40 和 Spire.Pdf v4.8.3.2040
+- 之前这两个 DLL 缺失导致 TypeInitializationException,用户已手动补充到 bin/Debug
+
+### Query_SOP OutOfMemoryException 最终修复
+- 问题:30个Sheet的Excel文件上传时第一个sheet就OOM,3个Sheet的文件正常
+- 根因:32位x86进程内存上限2GB,30个sheet的workbook加载后已占大量内存,CopyFrom再复制一份+600DPI大位图三者叠加必然超限
+- 3个sheet文件正常的原因:workbook本身小,即使600DPI+CopyFrom也不会超2GB
+- 第一次修复(去掉CopyFrom)反而更糟:直接用原sheet调用SaveToImage时,Spire.XLS内部需要处理整个workbook(30个sheet)的布局上下文,内存爆炸,第一个sheet就OOM
+- 最终修复方案(恢复CopyFrom + 串行 + 低DPI + OOM降级):
+  1. **恢复CopyFrom**:每个sheet复制到独立Workbook(只有1个sheet),SaveToImage只处理1个sheet的布局上下文,内存可控
+  2. 串行处理(不并发):避免DataHelper static连接竞态+Spire.Xls非线程安全
+  3. DPI 600→96:位图内存降为约1/40
+  4. 每个sheet处理完立即 she=null; bw=null + GC.Collect 释放临时workbook
+  5. SaveFileToJPG恢复try-catch,catch(OutOfMemoryException)时降级为SaveToPdf(矢量格式内存极小)
+  6. 图片添加移到重试循环外,避免重复Pictures.Add
+  7. 提取UploadFile/SaveToPdfAndUpload辅助方法消除重复代码
+
+### 索引越界降级修复
+- 问题:恢复CopyFrom后第一个sheet成功,但第二个sheet开始报 `Index is out of range. 参数名:index`
+- 根因:CopyFrom没有完整复制某些sheet的内部状态(AllocatedRange等),SaveToImage渲染时索引越界
+- 修复:把 `catch(OutOfMemoryException)` 扩大为 `catch(Exception)`,任何SaveToImage异常都自动降级为PDF格式
+
+### 最终方案:放弃Spire.XLS,改用Aspose.Cells 18.4重写Query_SOP
+- 用户决定彻底放弃Spire.XLS(OOM+索引越界死循环无法解决),改用项目中已有的Aspose.Cells 18.4.0.0
+- API对照:
+  - 加载文件:`workbook.LoadFromFile(path)` → `new Aspose.Cells.Workbook(path)`
+  - TextBox文本:`tb.RichText.Text` → `tb.Text`
+  - TextBox对齐:`tb.HAlignment = CommentHAlignType.Center` → `tb.TextHorizontalAlignment = TextAlignmentType.Center`
+  - 添加图片:`sheet.Pictures.Add(path)` → `sheet.Pictures.Add(row, col, path)`
+  - 图片列:`pic.LeftColumn = 20` → Add时指定 `col=20`
+  - 图片行偏移:`pic.TopRowOffset = 20` → `pic.UpperDeltaY = 20`
+  - 转图片:`sheet.SaveToImage(path)` → `SheetRender.ToImage(pageIndex, path)` + `ImageOrPrintOptions`
+  - 转PDF:`sheet.SaveToPdf(path)` → 创建独立workbook + `CopyFrom(sheet)` + `Save(path, SaveFormat.Pdf)`
+- csproj删除了Spire.Pdf和Spire.License引用(不再需要)
+- using改为 Aspose.Cells + Aspose.Cells.Drawing + Aspose.Cells.Rendering + System.Drawing.Imaging
+- 功能逻辑保持不变:串行处理 + TextBox放大 + 电子受控章/签名图片 + 内存收集fp_id + 一次性UPDATE
+- 新增UploadFile辅助方法消除重复的上传代码

+ 97 - 69
UAS_MES_ZDXW/FunctionCode/Query/Query_SOP.cs

@@ -9,9 +9,12 @@ using System.Net.Sockets;
 using System.Data;
 using UAS_MES_NEW.DataOperate;
 using UAS_MES_NEW.PublicMethod;
-using Spire.Xls;
+using Aspose.Cells;
+using Aspose.Cells.Drawing;
+using Aspose.Cells.Rendering;
 using UAS_MES_NEW.PublicForm;
 using System.Web.Script.Serialization;
+using System.Drawing.Imaging;
 
 namespace UAS_MES_NEW.Query
 {
@@ -64,56 +67,52 @@ namespace UAS_MES_NEW.Query
             {
                 pr_code.Text = dt.Rows[0]["ps_prodcode"].ToString();
             }
-            Workbook workbook = new Workbook();
-            workbook.LoadFromFile(FilePath.Text);
-            workbook.ConverterSetting.JPEGQuality = 100;
-            workbook.ConverterSetting.XDpi = 600;
-            workbook.ConverterSetting.YDpi = 600;
-            //先清空旧的 ps_attachsop,避免残留旧数据
+
+            string dir = Application.StartupPath + @"\" + pr_code.Text;
+            if (!Directory.Exists(dir))
+            {
+                Directory.CreateDirectory(dir);
+            }
+
+            // 使用Aspose.Cells加载Excel文件
+            Aspose.Cells.Workbook workbook = new Aspose.Cells.Workbook(FilePath.Text);
+            int total = workbook.Worksheets.Count;
+
             dh.ExecuteSql("update ProductSOP set ps_attachsop='' where ps_prodcode='" + pr_code.Text + "'", "update");
-            OperatResult.AppendText("一共Sheet: " + workbook.Worksheets.Count + "\n");
+            OperatResult.AppendText("一共Sheet: " + total + "\n");
 
             //串行处理每个Sheet,避免并发导致缺页
-            //根因:DataHelper.connection是static全局唯一,多线程并发ExecuteSql会互相覆盖command字段
-            //      且ps_attachsop=ps_attachsop||fp_id并发拼接非原子,后提交覆盖先提交
-            //      Spire.Xls的SaveToPdf/SaveToImage也不是线程安全的
-            //方案:串行转换+上传,内存收集fp_id,最后一次性UPDATE
             List<string> successIds = new List<string>();
             List<string> failedSheets = new List<string>();
-            int total = workbook.Worksheets.Count;
 
             for (int i = 0; i < total; i++)
             {
-                string sheetName = workbook.Worksheets[i].Name;
+                Worksheet sheet = workbook.Worksheets[i];
+                string sheetName = sheet.Name;
                 OperatResult.AppendText("[" + (i + 1) + "/" + total + "] 处理Sheet: " + sheetName + "\n");
                 try
                 {
-                    //处理TextBox(放大文字,保持原有逻辑)
-                    for (int k = 0; k < workbook.Worksheets[i].TextBoxes.Count; k++)
+                    //处理TextBox(放大文字×2,居中对齐)
+                    for (int k = 0; k < sheet.TextBoxes.Count; k++)
                     {
-                        if (workbook.Worksheets[i].TextBoxes[k].RichText.Text.Length > 1)
+                        Aspose.Cells.Drawing.TextBox tb = sheet.TextBoxes[k];
+                        if (tb.Text != null && tb.Text.Length > 1)
                         {
-                            workbook.Worksheets[i].TextBoxes[k].RichText.Text = workbook.Worksheets[i].TextBoxes[k].RichText.Text;
-                            workbook.Worksheets[i].TextBoxes[k].Width = workbook.Worksheets[i].TextBoxes[k].Width * 2;
-                            workbook.Worksheets[i].TextBoxes[k].Height = workbook.Worksheets[i].TextBoxes[k].Height * 2;
-                            workbook.Worksheets[i].TextBoxes[k].HAlignment = CommentHAlignType.Center;
+                            tb.Text = tb.Text;
+                            tb.Width = tb.Width * 2;
+                            tb.Height = tb.Height * 2;
+                            tb.TextHorizontalAlignment = TextAlignmentType.Center;
                         }
                     }
 
-                    //复制到独立Workbook再转换,避免污染原workbook
-                    Workbook bw = new Workbook();
-                    Worksheet she = bw.CreateEmptySheet();
-                    she.Name = sheetName;
-                    she.CopyFrom(workbook.Worksheets[i]);
-
                     string fp_id = "";
                     if (JPG.Checked)
                     {
-                        fp_id = SaveFileToJPG(she);
+                        fp_id = SaveFileToJPG(sheet, dir);
                     }
                     else
                     {
-                        fp_id = SaveFileToPDF(she);
+                        fp_id = SaveFileToPDF(sheet, dir);
                     }
 
                     if (fp_id != "")
@@ -158,27 +157,55 @@ namespace UAS_MES_NEW.Query
             }
         }
 
-        private string SaveFileToPDF(Worksheet sheet)
+        /// <summary>
+        /// 使用Aspose.Cells SheetRender将sheet转换为JPG图片并上传
+        /// </summary>
+        private string SaveFileToJPG(Worksheet sheet, string dir)
         {
             string fp_id = "";
-            //失败重试2次
+            string filePath = dir + @"\" + sheet.Name + ".jpg";
+
+            //图片只添加一次(放在重试循环外,避免重试时重复添加)
+            try
+            {
+                //电子受控章:第20列,Y偏移20像素
+                int picIdx1 = sheet.Pictures.Add(0, 20, @"图片\电子受控章.png");
+                Picture pic1 = sheet.Pictures[picIdx1];
+                pic1.Width = 113;
+                pic1.Height = 38;
+                pic1.UpperDeltaY = 20;
+
+                //签名:第15列,Y偏移600像素
+                int picIdx2 = sheet.Pictures.Add(0, 15, @"图片\签名.png");
+                Picture pic2 = sheet.Pictures[picIdx2];
+                pic2.Width = 100;
+                pic2.Height = 38;
+                pic2.UpperDeltaY = 600;
+            }
+            catch (Exception ex)
+            {
+                OperatResult.AppendText("添加图片异常: " + ex.Message + "\n");
+            }
+
+            //转换为图片并上传(失败重试2次)
             for (int retry = 0; retry < 2; retry++)
             {
                 try
                 {
-                    if (!Directory.Exists(Application.StartupPath + @"\" + pr_code.Text))
+                    //使用SheetRender转图片,Aspose.Cells的渲染引擎比Spire.XLS更稳定
+                    ImageOrPrintOptions options = new ImageOrPrintOptions();
+                    options.ImageFormat = ImageFormat.Jpeg;
+                    options.Quality = 85;
+                    options.OnePagePerSheet = true;
+
+                    SheetRender sr = new SheetRender(sheet, options);
+                    if (sr.PageCount > 0)
                     {
-                        Directory.CreateDirectory(Application.StartupPath + @"\" + pr_code.Text);
+                        sr.ToImage(0, filePath);
                     }
-                    string filePath = Application.StartupPath + @"\" + pr_code.Text + @"\" + sheet.Name + ".pdf";
-                    sheet.SaveToPdf(filePath);
-                    OperatResult.AppendText("解析PDF【" + sheet.Name + ".pdf】" + "\n");
-                    Dictionary<string, object> dic = new Dictionary<string, object>();
-                    dic.Add("em_name", "管理员");
-                    dic.Add("em_code", "ADMIN");
-                    dic.Add("caller", "ProductSOP");
-                    OperatResult.AppendText("上传文件【" + sheet.Name + ".pdf" + "】\n");
-                    fp_id = UploadFilesToRemoteUrl("http://192.168.101.253:8099/mes/MEScommon/uploadFiles.action?_noc=1", filePath, dic);
+
+                    OperatResult.AppendText("解析图片【" + sheet.Name + ".jpg】\n");
+                    fp_id = UploadFile(filePath, sheet.Name + ".jpg");
                     if (fp_id != "")
                     {
                         break;
@@ -190,43 +217,31 @@ namespace UAS_MES_NEW.Query
                 }
                 catch (Exception ex)
                 {
-                    OperatResult.AppendText("SaveFileToPDF异常: " + ex.Message + (retry == 0 ? " 重试中..." : "") + "\n");
+                    OperatResult.AppendText("SaveFileToJPG异常: " + ex.Message + (retry == 0 ? " 重试中..." : "") + "\n");
                 }
             }
             return fp_id;
         }
 
-        private string SaveFileToJPG(Worksheet sheet)
+        /// <summary>
+        /// 使用Aspose.Cells将sheet转换为PDF并上传
+        /// </summary>
+        private string SaveFileToPDF(Worksheet sheet, string dir)
         {
             string fp_id = "";
-            //失败重试2次
+            string filePath = dir + @"\" + sheet.Name + ".pdf";
+
             for (int retry = 0; retry < 2; retry++)
             {
                 try
                 {
-                    if (!Directory.Exists(Application.StartupPath + @"\" + pr_code.Text))
-                    {
-                        Directory.CreateDirectory(Application.StartupPath + @"\" + pr_code.Text);
-                    }
-                    ExcelPicture picture = sheet.Pictures.Add(@"图片\电子受控章.png");
-                    picture.Width = 113;
-                    picture.Height = 38;
-                    picture.LeftColumn = 20;
-                    picture.TopRowOffset = 20;
-                    ExcelPicture picture1 = sheet.Pictures.Add(@"图片\签名.png");
-                    picture1.Width = 100;
-                    picture1.Height = 38;
-                    picture1.LeftColumn = 15;
-                    picture1.TopRowOffset = 600;
-                    string filePath = Application.StartupPath + @"\" + pr_code.Text + @"\" + sheet.Name + ".jpg";
-                    sheet.SaveToImage(filePath);
-                    OperatResult.AppendText("解析图片【" + sheet.Name + ".jpg】" + "\n");
-                    Dictionary<string, object> dic = new Dictionary<string, object>();
-                    dic.Add("em_name", "管理员");
-                    dic.Add("em_code", "ADMIN");
-                    dic.Add("caller", "ProductSOP");
-                    OperatResult.AppendText("上传文件【" + sheet.Name + ".jpg" + "】\n");
-                    fp_id = UploadFilesToRemoteUrl("http://192.168.101.253:8099/mes/MEScommon/uploadFiles.action?_noc=1", filePath, dic);
+                    //创建只含1个sheet的独立workbook,Copy复制当前sheet内容
+                    Aspose.Cells.Workbook singleWb = new Aspose.Cells.Workbook();
+                    singleWb.Worksheets[0].Copy(sheet);
+                    singleWb.Save(filePath, SaveFormat.Pdf);
+
+                    OperatResult.AppendText("解析PDF【" + sheet.Name + ".pdf】\n");
+                    fp_id = UploadFile(filePath, sheet.Name + ".pdf");
                     if (fp_id != "")
                     {
                         break;
@@ -238,12 +253,25 @@ namespace UAS_MES_NEW.Query
                 }
                 catch (Exception ex)
                 {
-                    OperatResult.AppendText("SaveFileToJPG异常: " + ex.Message + (retry == 0 ? " 重试中..." : "") + "\n");
+                    OperatResult.AppendText("SaveFileToPDF异常: " + ex.Message + (retry == 0 ? " 重试中..." : "") + "\n");
                 }
             }
             return fp_id;
         }
 
+        /// <summary>
+        /// 上传文件到服务器
+        /// </summary>
+        private string UploadFile(string filePath, string fileName)
+        {
+            Dictionary<string, object> dic = new Dictionary<string, object>();
+            dic.Add("em_name", "管理员");
+            dic.Add("em_code", "ADMIN");
+            dic.Add("caller", "ProductSOP");
+            OperatResult.AppendText("上传文件【" + fileName + "】\n");
+            return UploadFilesToRemoteUrl("http://erp.ubtob.net:11773/mes/MEScommon/uploadFiles.action?_noc=1", filePath, dic);
+        }
+
         /// <summary>
         /// 请求上传图片到阿里云
         /// </summary>

BIN
UAS_MES_ZDXW/Tool/Spire.License.dll


BIN
UAS_MES_ZDXW/Tool/Spire.Pdf.dll