From 0553ca4fa352c878adbb8f50a9f0c288458814ee Mon Sep 17 00:00:00 2001 From: john Date: Wed, 16 Sep 2026 04:30:51 +0000 Subject: [PATCH] =?UTF-8?q?fix(zip):=20UTF-8=20zip=20=E8=A7=A3=E7=A0=81?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=9B=9E=E9=80=80=20GBK=20=E6=BC=8F=E6=8A=93?= =?UTF-8?q?=20MalformedInputException=20+=20=E9=82=80=E8=AF=B7=E9=A1=B5?= =?UTF-8?q?=E5=AE=88=E5=8D=AB/=E7=A7=B0=E8=B0=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端 (3 个文件, 16+/5-): * ocr/ZipExtractor.java: catch (IllegalArgumentException) → catch (IllegalArgumentException | MalformedInputException). ZipInputStream 用错 charset 解码 entry name 抛的是 MalformedInputException (IOException 子类), 旧 catch 漏掉导致 GBK 回退永远是死代码 (中文 Windows 打包的 zip 直接报 MalformedInputException 上抛 500)。 * BizMeetingMaterialServiceImpl.java: 同款修复 (会务材料打包上传) * BizMeetingAttendeeServiceImpl.java: 同款修复 (参会人批量导入 zip) 前端 (2 个文件, 6+/1-): * MeetingDetail.vue pushInvite: posterUrl 空阻断 (无日程海报不允许 发送邀请, 批量/行内共用入口, 一处守卫同时卡两路) * MeetingInvitation.vue line 7: 称谓 "尊敬的 {{ name }}:" → "尊敬的 {{ name }} 教授:" (医生点邀请函链接看到的网页) --- .../src/main/java/com/ruoyi/business/ocr/ZipExtractor.java | 6 ++++-- .../service/impl/BizMeetingAttendeeServiceImpl.java | 5 ++++- .../service/impl/BizMeetingMaterialServiceImpl.java | 5 ++++- ry-vue3/src/views/doctor/MeetingInvitation.vue | 2 +- ry-vue3/src/views/meetings/MeetingDetail.vue | 4 ++++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/ocr/ZipExtractor.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/ocr/ZipExtractor.java index 009d87d..fa1ccfe 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/ocr/ZipExtractor.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/ocr/ZipExtractor.java @@ -8,6 +8,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -42,12 +43,13 @@ public class ZipExtractor log.info("zip 下载: url={} size={}B tmp={}", zipUrl, size, zipFile.getAbsolutePath()); // 优先 UTF-8 解包; 若 entry 名非 UTF-8 (常见 GBK, Windows 打包) 会抛 - // IllegalArgumentException (malformed input) → 回退 GBK 重试 + // MalformedInputException (IOException 子类, 不是 IllegalArgumentException) + // → 回退 GBK 重试 try { return doExtract(zipFile, tmpRoot, StandardCharsets.UTF_8); } - catch (IllegalArgumentException e) + catch (IllegalArgumentException | MalformedInputException e) { log.warn("zip 文件名非 UTF-8, 回退 GBK 重试: {}", e.getMessage()); return doExtract(zipFile, tmpRoot, Charset.forName("GBK")); diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java index fa2dbfe..e580e9e 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingAttendeeServiceImpl.java @@ -7,6 +7,7 @@ import java.io.InputStream; import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.charset.Charset; +import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; @@ -994,7 +995,9 @@ public class BizMeetingAttendeeServiceImpl implements IBizMeetingAttendeeService private LinkedHashMap parseAttendeeZip(byte[] zipBytes) throws IOException { try { return readAttendeeZip(zipBytes, StandardCharsets.UTF_8); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | MalformedInputException e) { + // 注: ZipInputStream 用错 charset 解码 entry name 抛 MalformedInputException (IOException 子类), + // 旧 catch (IllegalArgumentException) 漏掉, GBK 回退永远是死代码 log.warn("[attendee] zip UTF-8 解析失败, 回退 GBK: {}", e.getMessage()); return readAttendeeZip(zipBytes, Charset.forName("GBK")); } diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingMaterialServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingMaterialServiceImpl.java index 11d20cb..690d7c2 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingMaterialServiceImpl.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingMaterialServiceImpl.java @@ -18,6 +18,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.UUID; @@ -841,8 +842,10 @@ public class BizMeetingMaterialServiceImpl implements IBizMeetingMaterialService Map> folderNames = new HashMap<>(); try { readServiceZip(zipBytes, StandardCharsets.UTF_8, folderBytes, folderNames); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | MalformedInputException e) { // 中文 Windows 打包器 (WinRAR/资源管理器) 用 GBK 编码条目名且不置 UTF-8 标志, 回退 GBK 重解析 + // 注: ZipInputStream 用错 charset 解码 entry name 抛的是 MalformedInputException (IOException 子类), + // 不是 IllegalArgumentException, 旧 catch 漏掉导致 fallback 永远不会跑 log.warn("[material] 会务材料 zip UTF-8 解析失败, 回退 GBK: {}", e.getMessage()); folderBytes.clear(); folderNames.clear(); diff --git a/ry-vue3/src/views/doctor/MeetingInvitation.vue b/ry-vue3/src/views/doctor/MeetingInvitation.vue index f764f92..ba76b43 100644 --- a/ry-vue3/src/views/doctor/MeetingInvitation.vue +++ b/ry-vue3/src/views/doctor/MeetingInvitation.vue @@ -4,7 +4,7 @@ -

尊敬的 {{ attendeeName || '专家' }}:

+

尊敬的 {{ attendeeName || '专家' }} 教授:

diff --git a/ry-vue3/src/views/meetings/MeetingDetail.vue b/ry-vue3/src/views/meetings/MeetingDetail.vue index f0c776a..2be43fc 100644 --- a/ry-vue3/src/views/meetings/MeetingDetail.vue +++ b/ry-vue3/src/views/meetings/MeetingDetail.vue @@ -1320,6 +1320,10 @@ function onBatchEsign() { /** 调后端邀请参会 (推"会议邀请"站内信 + 置 is_invited=1, 不发短信); loadingId 非空→单条行级 loading, 否则批量工具栏 loading */ async function pushInvite(ids, loadingId) { if (!ids || !ids.length) return + if (!row.value.posterUrl) { + ElMessage.warning('该会议未上传日程海报,不允许发送邀请。请先上传海报后再发送。') + return + } if (loadingId != null) inviteLoadingId.value = loadingId else inviteSending.value = true try {