Commit 09158a24 authored by xiachenqi's avatar xiachenqi

调整快递单文字样式。优化港澳台购车人分析业务代码。

parent 6e958ec3
......@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
/**
......@@ -40,7 +41,18 @@ public class ExtensionApi {
if (fileName != null && !fileName.endsWith("xls") && !fileName.endsWith("xlsx")) {
throw new RuntimeException("Only .xls or .xlsx files are supported");
}
Thread thread = new Thread(() -> extensionService.analysisGAT(file));
// 必须在请求线程内提前读取文件字节!
// MultipartFile 底层资源依附于 HTTP 请求生命周期,
// 若直接将 file 传入新线程,请求结束后资源被释放,
// 子线程读到损坏数据会触发 "Invalid byte of UTF-8 sequence" 异常
final byte[] fileBytes;
final String originalFileName = fileName;
try {
fileBytes = file.getBytes();
} catch (IOException e) {
throw new RuntimeException("Failed to read uploaded file", e);
}
Thread thread = new Thread(() -> extensionService.analysisGAT(fileBytes, originalFileName));
// 处理文件并生成新的Excel
thread.start();
return "ok";
......@@ -58,7 +70,15 @@ public class ExtensionApi {
if (fileName != null && !fileName.endsWith("xls") && !fileName.endsWith("xlsx")) {
throw new RuntimeException("Only .xls or .xlsx files are supported");
}
Thread thread = new Thread(() -> extensionService.analysisGAT(file));
// 同样在请求线程内提前读取字节,避免子线程读到已释放的资源
final byte[] fileBytes;
final String originalFileName = fileName;
try {
fileBytes = file.getBytes();
} catch (IOException e) {
throw new RuntimeException("Failed to read uploaded file: " + fileName, e);
}
Thread thread = new Thread(() -> extensionService.analysisGAT(fileBytes, originalFileName));
// 处理文件并生成新的Excel
thread.start();
}
......
......@@ -5,12 +5,18 @@ import com.yxproject.start.dto.ExtensionGatDto;
import com.yxproject.start.mapper.ExtensionMapper;
import org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Files;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* 扩展服务impl
......@@ -30,26 +36,97 @@ public class ExtensionServiceImpl {
/**
* 解析港澳台购车人信息,判断是否办理过港澳台居住证
*
* @param file 文件
* @param fileBytes 文件字节(必须在请求线程中提前读取,避免子线程中 MultipartFile 资源已释放)
* @param fileName 原始文件名
*/
public void analysisGAT(MultipartFile file) {
try (InputStream inputStream = file.getInputStream();
public void analysisGAT(byte[] fileBytes, String fileName) {
System.out.println("start");
try {
// xlsx 本质是 ZIP,部分 WPS 生成的文件内部 XML(如 styles.xml)包含
// 非标准 UTF-8 字节序列,需要在交给 POI 解析前先修复编码
byte[] fixedBytes = fixXlsxEncoding(fileBytes);
try (InputStream inputStream = new ByteArrayInputStream(fixedBytes);
Workbook workbook = WorkbookFactory.create(inputStream)) {
Sheet hkSheet = workbook.getSheetAt(0);
// 分析和更新Sheet
analysisSheet(hkSheet);
String fileName = file.getOriginalFilename();
File excelFile = new File("./" + "(已比对)" + fileName);
try (OutputStream outputStream = Files.newOutputStream(excelFile.toPath())) {
File excelFile = new File("./" + "(modified)" + fileName);
// 使用 FileOutputStream 而非 Files.newOutputStream(file.toPath())
// 因为 toPath() 内部调用 UnixPath.encode() 依赖 JVM 默认字符集,
// 当系统默认字符集非 UTF-8 时,中文路径会触发 InvalidPathException
try (OutputStream outputStream = new FileOutputStream(excelFile)) {
workbook.write(outputStream);
outputStream.flush();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("比对完成");
}
/**
* 修复 xlsx(ZIP)内部 XML 文件中的非法 UTF-8 字节序列。
* WPS 等工具有时将 GBK 编码的字体名写入声明为 UTF-8 的 XML,
* 导致 Apache POI / Xerces 解析时抛出 MalformedByteSequenceException。
* 此方法将非法字节替换为 UTF-8 替换字符(U+FFFD),保证文件可被正常解析。
*/
private byte[] fixXlsxEncoding(byte[] fileBytes) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(fileBytes));
ZipOutputStream zos = new ZipOutputStream(out)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
zos.putNextEntry(new ZipEntry(entry.getName()));
String name = entry.getName();
if (name.endsWith(".xml") || name.endsWith(".rels")) {
// 读取 XML 原始字节,用宽容解码器修复非法 UTF-8 序列后重新写入
byte[] raw = readEntryBytes(zis);
zos.write(fixUtf8Bytes(raw));
} else {
// 非 XML 条目(图片、二进制等)原样复制
byte[] buf = new byte[8192];
int len;
while ((len = zis.read(buf)) > 0) {
zos.write(buf, 0, len);
}
}
zos.closeEntry();
zis.closeEntry();
}
}
return out.toByteArray();
}
/**
* 读取 ZipInputStream 当前条目的全部字节(不关闭流)。
*/
private byte[] readEntryBytes(ZipInputStream zis) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] tmp = new byte[8192];
int len;
while ((len = zis.read(tmp)) > 0) {
buffer.write(tmp, 0, len);
}
return buffer.toByteArray();
}
/**
* 使用宽容 UTF-8 解码器将非法字节替换为替换字符,再重新编码为合法 UTF-8 字节。
*/
private byte[] fixUtf8Bytes(byte[] raw) {
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
try {
CharBuffer chars = decoder.decode(ByteBuffer.wrap(raw));
return chars.toString().getBytes(StandardCharsets.UTF_8);
} catch (Exception e) {
// 极端情况下解码仍失败,原样返回
return raw;
}
}
/**
* 分析表
*
......
......@@ -119,7 +119,7 @@
<table cellspacing="0" cellpadding="0" style="border-collapse:collapse;height:24mm">
<tr>
<td style="width:72mm;height:4mm">
<span style="font-size:4mm; position: absolute;top:1mm;left:55mm;font-family:'黑体';">特快专递</span>
<span style="font-size:4mm; position: absolute;top:1mm;left:45mm;font-family:'黑体';">特快专递-特安</span>
</td>
</tr>
<tr>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment