729 lines
32 KiB
Java
729 lines
32 KiB
Java
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<LbAssessmentApplyMapper, LbAssessmentApply>
|
||
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<String, Object> parseFromAssessmentTextByLlm(String assessmentText, String tenantId) {
|
||
Map<String, Object> 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<String, Object> add(LbAssessmentApply entity) {
|
||
Map<String, Object> 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<String, Object> update(LbAssessmentApply entity) {
|
||
Map<String, Object> 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<String, Object> deleteById(String id) {
|
||
Map<String, Object> 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<String, Object> pageQuery(Integer current,
|
||
Integer size,
|
||
String tenantId,
|
||
String applicantName,
|
||
String applicantPhone,
|
||
String colleagueName,
|
||
String teamLeaderName,
|
||
String teamBigLeaderName,
|
||
String assessmentTeacherName,
|
||
String moderatorName,
|
||
String meetingNumber) {
|
||
Map<String, Object> result = new HashMap<>();
|
||
try {
|
||
if (current == null || current < 1) {
|
||
current = 1;
|
||
}
|
||
if (size == null || size < 1) {
|
||
size = 10;
|
||
}
|
||
|
||
LambdaQueryWrapper<LbAssessmentApply> queryWrapper = buildQueryWrapper(
|
||
tenantId, applicantName, applicantPhone, colleagueName, teamLeaderName,
|
||
teamBigLeaderName, assessmentTeacherName, moderatorName, meetingNumber
|
||
);
|
||
|
||
Page<LbAssessmentApply> 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<String, Object> buildAssessmentNotificationText(String tenantId, String id) {
|
||
Map<String, Object> 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<String, Object> sendNotificationTextToDingTalk(String tenantId, String id) {
|
||
Map<String, Object> 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<LbAssessmentApply> 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<LbAssessmentApply> queryWrapper = new LambdaQueryWrapper<>();
|
||
queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim())
|
||
.ge(LbAssessmentApply::getApplicationDatetime, start)
|
||
.le(LbAssessmentApply::getApplicationDatetime, end)
|
||
.orderByAsc(LbAssessmentApply::getSortedNum)
|
||
.orderByDesc(LbAssessmentApply::getApplicationDatetime);
|
||
List<LbAssessmentApply> 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<LbAssessmentApply> buildQueryWrapper(String tenantId,
|
||
String applicantName,
|
||
String applicantPhone,
|
||
String colleagueName,
|
||
String teamLeaderName,
|
||
String teamBigLeaderName,
|
||
String assessmentTeacherName,
|
||
String moderatorName,
|
||
String meetingNumber) {
|
||
LambdaQueryWrapper<LbAssessmentApply> 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<LbAssessmentApply> queryWrapper = new LambdaQueryWrapper<>();
|
||
queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim())
|
||
.isNotNull(LbAssessmentApply::getSortedNum)
|
||
.ne(LbAssessmentApply::getSortedNum, "");
|
||
List<LbAssessmentApply> 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;
|
||
}
|
||
}
|