package com.rj.service.impl; import com.alibaba.dashscope.aigc.generation.Generation; import com.alibaba.dashscope.aigc.generation.GenerationParam; import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fasterxml.jackson.databind.ObjectMapper; import com.rj.entity.LbAssessmentApply; import com.rj.mapper.LbAssessmentApplyMapper; import com.rj.service.DingTalkRobotService; import com.rj.service.ILbAssessmentApplyService; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFColor; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import java.awt.Color; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @Slf4j @Service public class LbAssessmentApplyServiceImpl extends ServiceImpl implements ILbAssessmentApplyService { @Autowired private ObjectMapper objectMapper; @Autowired private DingTalkRobotService dingTalkRobotService; @Value("${langchain4j.open-ai.chat-model.model-name:qwen-plus}") private String dashScopeChatModel; private static final String ASSESSMENT_PARSE_SYSTEM_PROMPT = """ 你是信息抽取助手。用户会提供一段与「申请评估」相关的自然语言描述。 请只输出一个 JSON 对象,不要 markdown 代码块,不要解释性文字。 字段均为可选;无法从原文推断的字段请省略或设为 null。 字段名与含义(JSON 键名必须完全一致,与 Java 驼峰一致): applicantName 申请人姓名; applicantPhone 申请人电话; applicantAge 年龄(整数); workExperience 从业经历; colleagueName 同事姓名; colleaguePhone 同事电话; teamLeaderName 团队长姓名; teamBigLeaderName 全收益团队长姓名; assessmentTeacherName 评估老师姓名; moderatorName 主持人姓名; meetingNumber 会议号; applicationDatetime 申请日期时间,优先 ISO-8601(如 2026-05-15T14:30:00),也可 yyyy-MM-dd HH:mm:ss;若仅有日期可设为当天 00:00:00。 不要输出 id、tenantId、createdAt、updatedAt、notificationText、sortedNum。 """; @Override public Map parseFromAssessmentTextByLlm(String assessmentText, String tenantId) { Map result = new HashMap<>(); try { if (assessmentText == null || assessmentText.trim().isEmpty()) { result.put("success", false); result.put("message", "申请评估文本不能为空"); return result; } if (tenantId == null || tenantId.trim().isEmpty()) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } String apiKey = System.getenv("DASHSCOPE_API_KEY"); if (apiKey == null || apiKey.isEmpty()) { result.put("success", false); result.put("message", "未配置环境变量 DASHSCOPE_API_KEY,无法调用大模型"); return result; } Generation gen = new Generation(); Message systemMsg = Message.builder() .role(Role.SYSTEM.getValue()) .content(ASSESSMENT_PARSE_SYSTEM_PROMPT) .build(); Message userMsg = Message.builder() .role(Role.USER.getValue()) .content("申请评估信息如下:\n" + assessmentText.trim()) .build(); GenerationParam param = GenerationParam.builder() .apiKey(apiKey) .model(dashScopeChatModel) .messages(Arrays.asList(systemMsg, userMsg)) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .build(); GenerationResult call = gen.call(param); String raw = call.getOutput().getChoices().get(0).getMessage().getContent(); if (raw == null || raw.trim().isEmpty()) { result.put("success", false); result.put("message", "大模型返回内容为空"); return result; } String json = normalizeJson(raw); LbAssessmentApply entity = objectMapper.readValue(json, LbAssessmentApply.class); entity.setTenantId(tenantId.trim()); entity.setId(null); entity.setCreatedAt(null); entity.setUpdatedAt(null); result.put("success", true); result.put("message", "解析成功"); result.put("data", entity); return result; } catch (NoApiKeyException e) { log.error("DashScope API Key 异常", e); result.put("success", false); result.put("message", "API密钥异常:" + e.getMessage()); return result; } catch (InputRequiredException e) { log.error("DashScope 入参异常", e); result.put("success", false); result.put("message", "调用大模型入参错误:" + e.getMessage()); return result; } catch (Exception e) { log.error("申请评估信息大模型解析失败", e); result.put("success", false); result.put("message", "解析失败:" + e.getMessage()); return result; } } private static String normalizeJson(String content) { if (content == null) { return ""; } String trimmed = content.trim(); if (trimmed.startsWith("```")) { int firstLineBreak = trimmed.indexOf('\n'); int lastFence = trimmed.lastIndexOf("```"); if (firstLineBreak >= 0 && lastFence > firstLineBreak) { trimmed = trimmed.substring(firstLineBreak + 1, lastFence).trim(); } } return trimmed; } @Override public Map add(LbAssessmentApply entity) { Map result = new HashMap<>(); try { if (entity.getTenantId() == null) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } if (entity.getApplicantName() == null || entity.getApplicantName().trim().isEmpty()) { result.put("success", false); result.put("message", "applicantName不能为空"); return result; } if (entity.getApplicantPhone() == null || entity.getApplicantPhone().trim().isEmpty()) { result.put("success", false); result.put("message", "applicantPhone不能为空"); return result; } if (entity.getId() == null || entity.getId().trim().isEmpty()) { entity.setId(UUID.randomUUID().toString()); } LocalDateTime now = LocalDateTime.now(); if (entity.getCreatedAt() == null) { entity.setCreatedAt(now); } entity.setUpdatedAt(now); if (entity.getSortedNum() == null || entity.getSortedNum().trim().isEmpty()) { entity.setSortedNum(resolveNextSortedNum(entity.getTenantId())); } refreshNotificationText(entity); boolean ok = this.save(entity); result.put("success", ok); result.put("message", ok ? "新增成功" : "新增失败"); if (ok) { result.put("data", entity); } return result; } catch (Exception e) { result.put("success", false); result.put("message", "新增异常:" + e.getMessage()); return result; } } @Override public Map update(LbAssessmentApply entity) { Map result = new HashMap<>(); try { if (entity.getId() == null || entity.getId().trim().isEmpty()) { result.put("success", false); result.put("message", "id不能为空"); return result; } entity.setUpdatedAt(LocalDateTime.now()); boolean ok = this.updateById(entity); if (ok) { persistNotificationText(entity.getId()); } result.put("success", ok); result.put("message", ok ? "编辑成功" : "编辑失败"); if (ok) { result.put("data", this.getById(entity.getId())); } return result; } catch (Exception e) { result.put("success", false); result.put("message", "编辑异常:" + e.getMessage()); return result; } } @Override public Map deleteById(String id) { Map result = new HashMap<>(); try { boolean ok = this.removeById(id); result.put("success", ok); result.put("message", ok ? "删除成功" : "删除失败"); return result; } catch (Exception e) { result.put("success", false); result.put("message", "删除异常:" + e.getMessage()); return result; } } @Override public Map pageQuery(Integer current, Integer size, String tenantId, String applicantName, String applicantPhone, String colleagueName, String teamLeaderName, String teamBigLeaderName, String assessmentTeacherName, String moderatorName, String meetingNumber) { Map result = new HashMap<>(); try { if (current == null || current < 1) { current = 1; } if (size == null || size < 1) { size = 10; } LambdaQueryWrapper queryWrapper = buildQueryWrapper( tenantId, applicantName, applicantPhone, colleagueName, teamLeaderName, teamBigLeaderName, assessmentTeacherName, moderatorName, meetingNumber ); Page page = this.page(new Page<>(current, size), queryWrapper); result.put("success", true); result.put("message", "查询成功"); result.put("data", page.getRecords()); result.put("total", page.getTotal()); result.put("current", page.getCurrent()); result.put("size", page.getSize()); result.put("pages", page.getPages()); return result; } catch (Exception e) { result.put("success", false); result.put("message", "查询异常:" + e.getMessage()); log.error( "查询异常", e); return result; } } private static final DateTimeFormatter NOTIFICATION_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("M月d日 HH:mm"); private static final DateTimeFormatter MODERATOR_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("M月d日 H:mm"); private static final long MODERATOR_CONNECT_MINUTES_BEFORE = 20L; @Override public Map buildAssessmentNotificationText(String tenantId, String id) { Map result = new HashMap<>(); try { if (tenantId == null || tenantId.trim().isEmpty()) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } if (id == null || id.trim().isEmpty()) { result.put("success", false); result.put("message", "id不能为空"); return result; } LbAssessmentApply record = getByTenantIdAndId(tenantId, id); if (record == null) { result.put("success", false); result.put("message", "未找到对应的申请评估记录"); return result; } String text = ensureAndSaveNotificationText(record); result.put("success", true); result.put("message", "生成成功"); result.put("data", text); return result; } catch (Exception e) { log.error("生成评估会议通知文案失败, tenantId={}, id={}", tenantId, id, e); result.put("success", false); result.put("message", "生成失败:" + e.getMessage()); return result; } } @Override public Map sendNotificationTextToDingTalk(String tenantId, String id) { Map result = new HashMap<>(); try { if (tenantId == null || tenantId.trim().isEmpty()) { result.put("success", false); result.put("message", "tenantId不能为空"); return result; } if (id == null || id.trim().isEmpty()) { result.put("success", false); result.put("message", "id不能为空"); return result; } LbAssessmentApply record = getByTenantIdAndId(tenantId, id); if (record == null) { result.put("success", false); result.put("message", "未找到对应的申请评估记录"); return result; } String text = ensureAndSaveNotificationText(record); dingTalkRobotService.sendText(text); result.put("success", true); result.put("message", "发送成功"); result.put("data", text); return result; } catch (IllegalArgumentException | IllegalStateException e) { log.warn("发送评估会议通知到钉钉失败, tenantId={}, id={}, reason={}", tenantId, id, e.getMessage()); result.put("success", false); result.put("message", e.getMessage()); return result; } catch (Exception e) { log.error("发送评估会议通知到钉钉异常, tenantId={}, id={}", tenantId, id, e); result.put("success", false); result.put("message", "发送失败:" + e.getMessage()); return result; } } private LbAssessmentApply getByTenantIdAndId(String tenantId, String id) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim()) .eq(LbAssessmentApply::getId, id.trim()); return this.getOne(queryWrapper); } /** * 返回可用的通知文案:库中已有则直接返回,否则生成并落库。 */ private String ensureAndSaveNotificationText(LbAssessmentApply record) { String text = formatAssessmentNotificationText(record); LbAssessmentApply update = new LbAssessmentApply(); update.setId(record.getId()); update.setNotificationText(text); update.setUpdatedAt(LocalDateTime.now()); if (!this.updateById(update)) { throw new IllegalStateException("通知文案保存失败"); } record.setNotificationText(text); return text; } private void refreshNotificationText(LbAssessmentApply entity) { entity.setNotificationText(formatAssessmentNotificationText(entity)); } private void persistNotificationText(String id) { LbAssessmentApply record = this.getById(id); if (record == null) { return; } LbAssessmentApply update = new LbAssessmentApply(); update.setId(id); update.setNotificationText(formatAssessmentNotificationText(record)); update.setUpdatedAt(LocalDateTime.now()); this.updateById(update); } private static String formatAssessmentNotificationText(LbAssessmentApply record) { String teacherName = nullToEmpty(record.getAssessmentTeacherName()); String applicantName = nullToEmpty(record.getApplicantName()); String colleagueName = nullToEmpty(record.getColleagueName()); String teamLeaderName = nullToEmpty(record.getTeamLeaderName()); String moderatorName = nullToEmpty(record.getModeratorName()); String meetingNumber = nullToEmpty(record.getMeetingNumber()); String assessmentDateTimeText = formatNotificationDateTime(record.getApplicationDatetime()); String moderatorDateTimeText = formatModeratorConnectDateTime(record.getApplicationDatetime()); StringBuilder sb = new StringBuilder(); sb.append(record.getSortedNum()); sb.append(teacherName).append(" ,您好!\n"); if (!assessmentDateTimeText.isEmpty()) { sb.append(assessmentDateTimeText).append("由您主评\n"); } else { sb.append("由您主评\n"); } sb.append("申评人: ").append(applicantName).append('\n'); sb.append("同事: ").append(colleagueName).append('\n'); sb.append("团队长: ").append(teamLeaderName).append('\n'); sb.append("评估老师: ").append(teacherName).append("\n"); sb.append("主持人: ").append(moderatorName); if (!moderatorDateTimeText.isEmpty()) { sb.append(" ").append(moderatorDateTimeText); } sb.append("连线☎\n\n"); sb.append("#腾讯会议: ").append(meetingNumber).append('\n'); sb.append("❤旁听学习须关麦静场\n"); sb.append("❤影响干扰会被移出"); return sb.toString(); } private static String formatNotificationDateTime(LocalDateTime datetime) { if (datetime == null) { return ""; } return datetime.format(NOTIFICATION_DATE_TIME_FORMATTER); } private static String formatModeratorConnectDateTime(LocalDateTime applicationDatetime) { if (applicationDatetime == null) { return ""; } LocalDateTime connectTime = applicationDatetime.minus(MODERATOR_CONNECT_MINUTES_BEFORE, ChronoUnit.MINUTES); return connectTime.format(MODERATOR_DATE_TIME_FORMATTER); } @Override public void exportExcel(String tenantId, String applicationDatetimeStart, String applicationDatetimeEnd, HttpServletResponse response) { try { if (tenantId == null || tenantId.trim().isEmpty()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "tenantId不能为空"); return; } if (applicationDatetimeStart == null || applicationDatetimeStart.trim().isEmpty()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "applicationDatetimeStart不能为空"); return; } if (applicationDatetimeEnd == null || applicationDatetimeEnd.trim().isEmpty()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "applicationDatetimeEnd不能为空"); return; } DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime start; LocalDateTime end; try { start = LocalDateTime.parse(applicationDatetimeStart.trim(), formatter); } catch (Exception e) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "申请开始时间格式错误,请使用 yyyy-MM-dd HH:mm:ss"); return; } try { end = LocalDateTime.parse(applicationDatetimeEnd.trim(), formatter); } catch (Exception e) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "申请结束时间格式错误,请使用 yyyy-MM-dd HH:mm:ss"); return; } if (start.isAfter(end)) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "申请开始时间不能晚于结束时间"); return; } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim()) .ge(LbAssessmentApply::getApplicationDatetime, start) .le(LbAssessmentApply::getApplicationDatetime, end) .orderByAsc(LbAssessmentApply::getSortedNum) .orderByDesc(LbAssessmentApply::getApplicationDatetime); List rows = this.list(queryWrapper); try (Workbook workbook = new XSSFWorkbook()) { Sheet sheet = workbook.createSheet("申请评估"); CellStyle headerStyle = workbook.createCellStyle(); Font headerFont = workbook.createFont(); headerFont.setBold(true); headerStyle.setFont(headerFont); headerStyle.setAlignment(HorizontalAlignment.CENTER); headerStyle.setVerticalAlignment(VerticalAlignment.CENTER); headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); ((XSSFCellStyle) headerStyle).setFillForegroundColor(new XSSFColor(new Color(255, 217, 102), null)); headerStyle.setBorderTop(BorderStyle.THIN); headerStyle.setBorderBottom(BorderStyle.THIN); headerStyle.setBorderLeft(BorderStyle.THIN); headerStyle.setBorderRight(BorderStyle.THIN); CellStyle dataStyle = workbook.createCellStyle(); dataStyle.setAlignment(HorizontalAlignment.CENTER); dataStyle.setVerticalAlignment(VerticalAlignment.CENTER); dataStyle.setBorderTop(BorderStyle.THIN); dataStyle.setBorderBottom(BorderStyle.THIN); dataStyle.setBorderLeft(BorderStyle.THIN); dataStyle.setBorderRight(BorderStyle.THIN); String[] headers = { "序号", "申请人", "电话", "同事", "电话", "团队长", "全收益团队长", "整理确定时间", "安排评估老师", "安排主持人", "会议号" }; Row headerRow = sheet.createRow(0); headerRow.setHeightInPoints(22); for (int i = 0; i < headers.length; i++) { createCell(headerRow, i, headers[i], headerStyle); } int rowIndex = 1; for (LbAssessmentApply item : rows) { Row row = sheet.createRow(rowIndex); row.setHeightInPoints(20); createCell(row, 0, formatSortedNum(item.getSortedNum(), rowIndex), dataStyle); createCell(row, 1, nullToEmpty(item.getApplicantName()), dataStyle); createCell(row, 2, nullToEmpty(item.getApplicantPhone()), dataStyle); createCell(row, 3, nullToEmpty(item.getColleagueName()), dataStyle); createCell(row, 4, nullToEmpty(item.getColleaguePhone()), dataStyle); createCell(row, 5, nullToEmpty(item.getTeamLeaderName()), dataStyle); createCell(row, 6, nullToEmpty(item.getTeamBigLeaderName()), dataStyle); createCell(row, 7, formatApplicationDatetime(item.getApplicationDatetime()), dataStyle); createCell(row, 8, nullToEmpty(item.getAssessmentTeacherName()), dataStyle); createCell(row, 9, nullToEmpty(item.getModeratorName()), dataStyle); createCell(row, 10, nullToEmpty(item.getMeetingNumber()), dataStyle); rowIndex++; } adjustColumnWidths(sheet, headers); String filename = "申请评估_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx"; String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded); try (ServletOutputStream os = response.getOutputStream()) { workbook.write(os); os.flush(); } } } catch (Exception e) { log.error("申请评估导出异常", e); try { response.reset(); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType("text/plain;charset=UTF-8"); response.getWriter().write("导出异常:" + e.getMessage()); } catch (Exception ignored) { // ignore } } } private LambdaQueryWrapper buildQueryWrapper(String tenantId, String applicantName, String applicantPhone, String colleagueName, String teamLeaderName, String teamBigLeaderName, String assessmentTeacherName, String moderatorName, String meetingNumber) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (tenantId != null && !tenantId.trim().isEmpty()) { queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim()); } if (applicantName != null && !applicantName.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getApplicantName, applicantName.trim()); } if (applicantPhone != null && !applicantPhone.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getApplicantPhone, applicantPhone.trim()); } if (colleagueName != null && !colleagueName.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getColleagueName, colleagueName.trim()); } if (teamLeaderName != null && !teamLeaderName.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getTeamLeaderName, teamLeaderName.trim()); } if (teamBigLeaderName != null && !teamBigLeaderName.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getTeamBigLeaderName, teamBigLeaderName.trim()); } if (assessmentTeacherName != null && !assessmentTeacherName.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getAssessmentTeacherName, assessmentTeacherName.trim()); } if (moderatorName != null && !moderatorName.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getModeratorName, moderatorName.trim()); } if (meetingNumber != null && !meetingNumber.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getMeetingNumber, meetingNumber.trim()); } queryWrapper.orderByAsc(LbAssessmentApply::getSortedNum) .orderByDesc(LbAssessmentApply::getUpdatedAt) .orderByDesc(LbAssessmentApply::getCreatedAt); return queryWrapper; } private String resolveNextSortedNum(String tenantId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim()) .isNotNull(LbAssessmentApply::getSortedNum) .ne(LbAssessmentApply::getSortedNum, ""); List list = this.list(queryWrapper); int max = 0; for (LbAssessmentApply item : list) { String num = item.getSortedNum(); if (num == null || num.trim().isEmpty()) { continue; } try { max = Math.max(max, Integer.parseInt(num.trim())); } catch (NumberFormatException ignored) { // 非数字序号不参与自增计算 } } return String.valueOf(max + 1); } private static String formatSortedNum(String sortedNum, int fallback) { return sortedNum != null && !sortedNum.trim().isEmpty() ? sortedNum.trim() : String.valueOf(fallback); } private static String nullToEmpty(String value) { return value == null ? "" : value; } private static String formatApplicationDatetime(LocalDateTime datetime) { if (datetime == null) { return ""; } if (datetime.getMinute() == 0 && datetime.getSecond() == 0) { return datetime.getMonthValue() + "." + datetime.getDayOfMonth() + "日" + datetime.getHour() + "点"; } return datetime.format(DateTimeFormatter.ofPattern("M月d日 H:mm")); } private static void createCell(Row row, int col, String text, CellStyle style) { Cell cell = row.createCell(col); cell.setCellValue(text); cell.setCellStyle(style); } /** * 按单元格内容计算列宽。POI 的 autoSizeColumn 对中文支持差,改为按字符宽度估算。 */ private static void adjustColumnWidths(Sheet sheet, String[] headers) { int columnCount = headers.length; int[] maxWidths = new int[columnCount]; for (int i = 0; i < columnCount; i++) { maxWidths[i] = calcDisplayWidth(headers[i]); } for (int r = 1; r <= sheet.getLastRowNum(); r++) { Row row = sheet.getRow(r); if (row == null) { continue; } for (int c = 0; c < columnCount; c++) { Cell cell = row.getCell(c); if (cell != null && cell.getCellType() == CellType.STRING) { maxWidths[c] = Math.max(maxWidths[c], calcDisplayWidth(cell.getStringCellValue())); } } } for (int i = 0; i < columnCount; i++) { // 表头加粗,额外留 1 个字符边距 int width = Math.min(255 * 256, (maxWidths[i] + 3) * 256); sheet.setColumnWidth(i, width); } } private static int calcDisplayWidth(String text) { if (text == null || text.isEmpty()) { return 0; } int width = 0; for (char ch : text.toCharArray()) { width += ch > 127 ? 2 : 1; } return width; } }