|
@@ -18,34 +18,46 @@ import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
|
|
import com.alibaba.excel.EasyExcel;
|
|
|
import com.alibaba.excel.ExcelWriter;
|
|
|
import com.alibaba.excel.write.metadata.WriteSheet;
|
|
|
-import com.itextpdf.text.DocumentException;
|
|
|
-import com.itextpdf.text.Paragraph;
|
|
|
-
|
|
|
+import com.itextpdf.text.Document;
|
|
|
import com.itextpdf.text.pdf.PdfWriter;
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
import io.swagger.v3.oas.annotations.Parameter;
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
-
|
|
|
+import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
+import org.apache.pdfbox.pdmodel.PDPage;
|
|
|
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|
|
+import org.apache.pdfbox.pdmodel.font.PDFont;
|
|
|
+import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
|
|
|
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
|
|
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
|
|
+import org.apache.poi.ss.usermodel.Sheet;
|
|
|
import org.apache.poi.xwpf.usermodel.*;
|
|
|
-
|
|
|
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
|
|
|
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
|
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
|
import org.springframework.security.access.prepost.PreAuthorize;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
+import com.itextpdf.text.*;
|
|
|
+import com.itextpdf.text.pdf.PdfPCell;
|
|
|
+import com.itextpdf.text.pdf.PdfPTable;
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
import javax.validation.Valid;
|
|
|
import java.io.*;
|
|
|
-
|
|
|
+import java.math.BigInteger;
|
|
|
import java.nio.file.Files;
|
|
|
-import java.nio.file.Paths;
|
|
|
-
|
|
|
+import java.time.DayOfWeek;
|
|
|
import java.time.LocalDate;
|
|
|
import java.util.*;
|
|
|
-
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
|
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
|
|
-
|
|
|
+import static org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc.CENTER;
|
|
|
|
|
|
@Tag(name = "管理后台 - 邮件模版")
|
|
|
@RestController
|
|
@@ -65,11 +77,13 @@ public class MailTemplateController {
|
|
|
@Resource
|
|
|
private PermissionService permissionService;
|
|
|
|
|
|
+ private static final short[] COLUMN_WIDTHS = {20, 20, 20, 20}; // 根据需要设置列宽
|
|
|
+
|
|
|
@Scheduled(cron = "0 30 8 * * ?") // 每天早上 8:30发送前一天的
|
|
|
@Operation(summary = "定时发送邮件给导师")
|
|
|
public void sendMailToTeacherScheduled() {
|
|
|
try {
|
|
|
- sendWordMailToTeacher();
|
|
|
+ sendPDFMailToTeacher();
|
|
|
} catch (IOException e) {
|
|
|
throw new RuntimeException(e);
|
|
|
}
|
|
@@ -208,7 +222,6 @@ public class MailTemplateController {
|
|
|
.append("<br/>");
|
|
|
}
|
|
|
|
|
|
-
|
|
|
if (!errorList.isEmpty()) {
|
|
|
templateParams.put("errType","未打卡同学清单");
|
|
|
templateParams.put("errList",errorListBuilder);
|
|
@@ -242,21 +255,248 @@ public class MailTemplateController {
|
|
|
|
|
|
document.write(byteArrayOutputStream);
|
|
|
|
|
|
- String outputPath ="output.pdf";
|
|
|
- // 将 Word 文档转换为 PDF
|
|
|
- convert(byteArrayOutputStream.toByteArray(), outputPath);
|
|
|
+ Map<String, InputStream> attachments = new HashMap<>();
|
|
|
+ String fileName = String.format("%s考勤信息.docx", (teacher.getDeptId() == null || teacher.getDeptId() == 0 ? "" : teacher.getDeptName()) + yesterday);
|
|
|
+ attachments.put(fileName, new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
|
|
|
+ // 发送邮件,包含附件
|
|
|
+ if (teacher.getEmail() != null) {
|
|
|
+ mailSendService.sendSingleMailToMemberWithAttachments("1473464808@qq.com", null, "attendance-list-word", templateParams, attachments);
|
|
|
+ }
|
|
|
+ // 每次推送后等待0.1s
|
|
|
+ try {
|
|
|
+ Thread.sleep(100); // 0.1s
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ @PostMapping("/sendPDFToTeacher")
|
|
|
+ @Operation(summary = "发送PDF给导师")
|
|
|
+ public void sendPDFMailToTeacher() throws IOException {
|
|
|
+ // 获取导师
|
|
|
+ Set<Long> collegeIdList = permissionService.getUserListByRoleId(113L);
|
|
|
+ List<AdminUserDO> TeacherList = adminUserService.getUserList(collegeIdList);
|
|
|
+
|
|
|
+ StudentAttendancePageReqVO pageReqVO = new StudentAttendancePageReqVO();
|
|
|
+ Map<String, Object> templateParams = new HashMap<>(); // 模板参数设置
|
|
|
+
|
|
|
+ // 获取前一天
|
|
|
+ LocalDate yesterday = LocalDate.now().minusDays(1);
|
|
|
+
|
|
|
+ if (TeacherList != null && !TeacherList.isEmpty()) {
|
|
|
+ for (AdminUserDO teacher : TeacherList) {
|
|
|
+ if (!(teacher.getDeptId() == null || teacher.getDeptId() == 0)) {
|
|
|
+ pageReqVO.setDeptId(teacher.getDeptId());
|
|
|
+ }
|
|
|
+ if (!(teacher.getDeptId() == null || teacher.getDeptId() == 0)) {
|
|
|
+ pageReqVO.setDeptId(teacher.getDeptId());
|
|
|
+ }
|
|
|
+ pageReqVO.setDate(yesterday);
|
|
|
+ List<StudentAttendanceSupervisorTemplateVO> normalList = BeanUtils.toBean(
|
|
|
+ studentAttendanceService.getStudentAttendanceListForTeacher(pageReqVO.setSupervisorId(teacher.getId())),
|
|
|
+ StudentAttendanceSupervisorTemplateVO.class
|
|
|
+ );
|
|
|
+ List<StudentAttendanceSupervisorTemplateVO> errorList = BeanUtils.toBean(
|
|
|
+ studentAttendanceService.getStudentAttendanceErrorListForTeacher(pageReqVO.setSupervisorId(teacher.getId())),
|
|
|
+ StudentAttendanceSupervisorTemplateVO.class
|
|
|
+ );
|
|
|
+ if (normalList.isEmpty() && errorList.isEmpty()) {
|
|
|
+ if(!adminUserService.isSupervisorNullHasStudent(teacher.getId())){
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ templateParams.put("teacherName", teacher.getNickname());
|
|
|
+ templateParams.put("date",yesterday.toString());
|
|
|
|
|
|
- FileInputStream pdfInputStream =new FileInputStream(outputPath);
|
|
|
|
|
|
+ StringBuilder normalListBuilder = new StringBuilder();
|
|
|
+ for (StudentAttendanceSupervisorTemplateVO attendance : normalList) {
|
|
|
+ normalListBuilder
|
|
|
+ .append(" 学生姓名: ").append(attendance.getStudentName())
|
|
|
+ .append(" 学生学号: ").append(attendance.getUserNumber() != null ? attendance.getUserNumber() : "无")
|
|
|
+ .append(" 打卡时间: ").append(attendance.getClockInTime() != null ? attendance.getClockInTime() : "未打卡")
|
|
|
+ .append("<br/>");
|
|
|
+ }
|
|
|
|
|
|
+ StringBuilder errorListBuilder = new StringBuilder();
|
|
|
+ for (StudentAttendanceSupervisorTemplateVO attendance : errorList) {
|
|
|
+ errorListBuilder
|
|
|
+ .append(" 学生姓名: ").append(attendance.getStudentName())
|
|
|
+ .append(" 学生学号: ").append(attendance.getUserNumber() != null ? attendance.getUserNumber() : "无")
|
|
|
+ .append(" 打卡时间: ").append(attendance.getClockInTime() != null ? attendance.getClockInTime() : "未打卡")
|
|
|
+ .append("<br/>");
|
|
|
+ }
|
|
|
Map<String, InputStream> attachments = new HashMap<>();
|
|
|
- String fileName = String.format("%s考勤信息.pdf", (teacher.getDeptId() == null || teacher.getDeptId() == 0 ? "" : teacher.getDeptName()) + yesterday);
|
|
|
- attachments.put(fileName, pdfInputStream);
|
|
|
+
|
|
|
+ try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
|
|
+ PDDocument document = new PDDocument()) {
|
|
|
+
|
|
|
+ PDPage page = new PDPage();
|
|
|
+ document.addPage(page);
|
|
|
+ // 提前创建 5 个流并存储在列表中
|
|
|
+ List<PDPageContentStream> contentStreams = new ArrayList<>();
|
|
|
+ contentStreams.add(new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true));
|
|
|
+ for (int i = 0; i < 1; i++) {
|
|
|
+ PDPage newPage = new PDPage();
|
|
|
+ document.addPage(newPage);
|
|
|
+ contentStreams.add(new PDPageContentStream(document, newPage, PDPageContentStream.AppendMode.APPEND, true, true));
|
|
|
+ }
|
|
|
+
|
|
|
+ PDFont font = PDType0Font.load(document, getClass().getResourceAsStream("/font/STXIHEI.TTF"));
|
|
|
+ // 使用第一个流写标题
|
|
|
+ PDPageContentStream contentStream = contentStreams.get(0);
|
|
|
+ // 页面宽度与高度
|
|
|
+ float pageWidth = page.getMediaBox().getWidth();
|
|
|
+ float pageHeight = page.getMediaBox().getHeight();
|
|
|
+
|
|
|
+ // 设置初始 Y 坐标,页面顶部保留边距
|
|
|
+ float margin = 50;
|
|
|
+ float yPosition = pageHeight - margin;
|
|
|
+
|
|
|
+ // 1. 添加标题
|
|
|
+ String title = "测绘地理信息学院研究生考勤情况日报告";
|
|
|
+ float titleWidth = font.getStringWidth(title) / 1000 * 16; // 字体宽度 * 字号
|
|
|
+ float titleX = (pageWidth - titleWidth) / 2; // 居中显示
|
|
|
+ contentStream.beginText();
|
|
|
+ contentStream.setFont(font, 16);
|
|
|
+ contentStream.newLineAtOffset(titleX, yPosition);
|
|
|
+ contentStream.showText(title);
|
|
|
+ contentStream.endText();
|
|
|
+ yPosition -= 100; // 下移一行
|
|
|
+
|
|
|
+
|
|
|
+ // 2. 添加老师称呼
|
|
|
+ String teacherGreeting = "尊敬的" + teacher.getNickname() + "导师:";
|
|
|
+ contentStream.beginText();
|
|
|
+ contentStream.setFont(font, 14);
|
|
|
+ contentStream.newLineAtOffset(50, yPosition); // 左对齐,X 偏移量 100
|
|
|
+ contentStream.showText(teacherGreeting);
|
|
|
+ contentStream.endText();
|
|
|
+ yPosition -= 50; // 下移一行
|
|
|
+
|
|
|
+ // 3. 添加日期信息段落
|
|
|
+ String dateInfoText = "您所指导的研究生 " + yesterday.getYear() + " 年 " + yesterday.getMonthValue() + " 月 " + yesterday.getDayOfMonth() + " 日 考勤情况如下:";
|
|
|
+ contentStream.beginText();
|
|
|
+ contentStream.setFont(font, 14);
|
|
|
+ contentStream.newLineAtOffset(50, yPosition); // 左对齐,X 偏移量 60
|
|
|
+ contentStream.showText(dateInfoText);
|
|
|
+ contentStream.endText();
|
|
|
+ yPosition -= 30; // 下移一行
|
|
|
+
|
|
|
+ String errlistName = "打卡异常同学清单";
|
|
|
+
|
|
|
+ // 计算标题宽度以居中显示
|
|
|
+ float errlistNameWidth = font.getStringWidth(errlistName) / 1000 * 16;
|
|
|
+ float errlistNameX = (pageWidth - errlistNameWidth) / 2;
|
|
|
+
|
|
|
+
|
|
|
+ contentStream.beginText();
|
|
|
+ contentStream.setFont(font, 16); // 设置字体大小为 16
|
|
|
+ contentStream.newLineAtOffset(errlistNameX, yPosition);
|
|
|
+ contentStream.showText(errlistName);
|
|
|
+ contentStream.endText();
|
|
|
+ yPosition -= 30; // 下移高度
|
|
|
+
|
|
|
+ if (!errorList.isEmpty()) {
|
|
|
+ templateParams.put("errType","未打卡同学清单");
|
|
|
+ templateParams.put("errList",errorListBuilder);
|
|
|
+ yPosition = generatePDFWithTableUsingStreamPool(contentStreams, document, page, errorList, yPosition, font, false);
|
|
|
+ } else {
|
|
|
+ templateParams.put("errType","未打卡同学清单!");
|
|
|
+ templateParams.put("errList","无");
|
|
|
+ writeText(contentStream, "无", 100, yPosition, font);
|
|
|
+ yPosition -= 30; // 下移高度
|
|
|
+ }
|
|
|
+ System.out.println(teacher.getNickname() + "我没进来"+ currentStreamIndex + contentStreams.size());
|
|
|
+
|
|
|
+ if (yPosition < margin + 40) {
|
|
|
+ currentStreamIndex++;
|
|
|
+ // 如果流池已经用尽,则动态创建新的流
|
|
|
+ if (currentStreamIndex >= contentStreams.size()) {
|
|
|
+ System.out.println(teacher.getNickname() + "我进来了"+ currentStreamIndex + contentStreams.size());
|
|
|
+ PDPage newPage = new PDPage();
|
|
|
+ document.addPage(newPage);
|
|
|
+ PDPageContentStream newStream = new PDPageContentStream(document, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
|
|
|
+ contentStreams.add(newStream);
|
|
|
+ }
|
|
|
+ // 切换到下一个流
|
|
|
+ contentStream = contentStreams.get(currentStreamIndex);
|
|
|
+ yPosition = pageHeight - margin;
|
|
|
+ }
|
|
|
+
|
|
|
+ contentStream = contentStreams.get(currentStreamIndex);
|
|
|
+
|
|
|
+ String listName = "正常打卡同学清单";
|
|
|
+
|
|
|
+ // 计算标题宽度以居中显示
|
|
|
+ float listNameWidth = font.getStringWidth(listName) / 1000 * 16;
|
|
|
+ float listNameX = (pageWidth - listNameWidth) / 2;
|
|
|
+
|
|
|
+
|
|
|
+ contentStream.beginText();
|
|
|
+ contentStream.setFont(font, 16); // 设置字体大小为 16
|
|
|
+ contentStream.newLineAtOffset(listNameX, yPosition);
|
|
|
+ contentStream.showText(listName);
|
|
|
+ contentStream.endText();
|
|
|
+
|
|
|
+ yPosition -= 20; // 下移高度
|
|
|
+
|
|
|
+ // 添加正常打卡学生列表
|
|
|
+ if (!normalList.isEmpty()) {
|
|
|
+ templateParams.put("normaltype","正常打卡同学清单");
|
|
|
+ templateParams.put("normalList",normalListBuilder);
|
|
|
+ yPosition = generatePDFWithTableUsingStreamPool(contentStreams, document, page, normalList, yPosition, font, true);
|
|
|
+ } else {
|
|
|
+ templateParams.put("normaltype","正常打卡同学清单");
|
|
|
+ templateParams.put("normalList","无");
|
|
|
+ writeText(contentStream, "无", 100, yPosition, font);
|
|
|
+ yPosition -= 30; // 下移高度
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 添加结尾
|
|
|
+ String footer = "测绘地理信息学院研究生管理委员会";
|
|
|
+
|
|
|
+ if (yPosition < margin + 40) {
|
|
|
+ currentStreamIndex++;
|
|
|
+ // 如果流池已经用尽,则动态创建新的流
|
|
|
+ if (currentStreamIndex >= contentStreams.size()) {
|
|
|
+ PDPage newPage = new PDPage();
|
|
|
+ document.addPage(newPage);
|
|
|
+ PDPageContentStream newStream = new PDPageContentStream(document, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
|
|
|
+ contentStreams.add(newStream);
|
|
|
+ }
|
|
|
+ // 切换到下一个流
|
|
|
+ contentStream = contentStreams.get(currentStreamIndex);
|
|
|
+ yPosition = pageHeight - margin;
|
|
|
+ }
|
|
|
+ contentStream = contentStreams.get(currentStreamIndex);
|
|
|
+ // 写结尾
|
|
|
+ writeText(contentStream, footer, 350, yPosition, font);
|
|
|
+
|
|
|
+ // 关闭所有流
|
|
|
+ for (PDPageContentStream stream : contentStreams) {
|
|
|
+ if (stream != null) {
|
|
|
+ stream.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存文档到输出流
|
|
|
+ document.save(byteArrayOutputStream);
|
|
|
+ // 保存文档到输出流
|
|
|
+ String fileName = String.format("%s考勤信息.pdf", (teacher.getDeptId() == null || teacher.getDeptId() == 0 ? "" : teacher.getDeptName()) + yesterday);
|
|
|
+ attachments.put(fileName, new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ throw new RuntimeException("生成 PDF 时发生错误", e);
|
|
|
+ }
|
|
|
// 发送邮件,包含附件
|
|
|
if (teacher.getEmail() != null) {
|
|
|
- mailSendService.sendSingleMailToMemberWithAttachments("1157853982@qq.com", null, "attendance-list-word", templateParams, attachments);
|
|
|
+ mailSendService.sendSingleMailToMemberWithAttachments(teacher.getEmail(), null, "attendance-list-word", templateParams, attachments);
|
|
|
+ currentStreamIndex = 0;
|
|
|
}
|
|
|
- // 每次推送后等待0.1s
|
|
|
+ // 每次推送后等待0.1秒
|
|
|
try {
|
|
|
Thread.sleep(100); // 0.1s
|
|
|
} catch (InterruptedException e) {
|
|
@@ -266,6 +506,20 @@ public class MailTemplateController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void writeText(PDPageContentStream contentStream, String text, float x, float y, PDFont font) throws IOException {
|
|
|
+ if (contentStream == null) {
|
|
|
+ throw new IllegalStateException("ContentStream is null!");
|
|
|
+ }
|
|
|
+ if (font == null) {
|
|
|
+ throw new IllegalStateException("Font is null!");
|
|
|
+ }
|
|
|
+
|
|
|
+ contentStream.beginText();
|
|
|
+ contentStream.setFont(font, 12); // 默认字体大小
|
|
|
+ contentStream.newLineAtOffset(x, y);
|
|
|
+ contentStream.showText(text);
|
|
|
+ contentStream.endText();
|
|
|
+ }
|
|
|
@PostMapping("/sendExcelToCollege")
|
|
|
@Operation(summary = "发送Excel给学院")
|
|
|
public void sendExcelMailToCollege() throws IOException {
|
|
@@ -576,17 +830,11 @@ public class MailTemplateController {
|
|
|
|
|
|
document.write(byteArrayOutputStream);
|
|
|
|
|
|
- String outputPath ="output.pdf";
|
|
|
- // 将 Word 文档转换为 PDF
|
|
|
- convert(byteArrayOutputStream.toByteArray(), outputPath);
|
|
|
-
|
|
|
- FileInputStream pdfInputStream =new FileInputStream(outputPath);
|
|
|
-
|
|
|
// 创建附件并将文件保存
|
|
|
Map<String, InputStream> attachments = new HashMap<>();
|
|
|
// String fileName = String.format("%s考勤信息.docx", (teacher.getDeptId()==null||teacher.getDeptId()==0?"":teacher.getDeptName())+yesterday);
|
|
|
String fileName = String.format("%s考勤信息.docx", yesterday);
|
|
|
- attachments.put(fileName, pdfInputStream);
|
|
|
+ attachments.put(fileName, new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
|
|
|
// 发送邮件
|
|
|
if (teacher.getEmail() != null) {
|
|
|
mailSendService.sendSingleMailToMemberWithAttachments(email, null, "attendance-list-excel", templateParams, attachments);
|
|
@@ -699,6 +947,7 @@ public class MailTemplateController {
|
|
|
paragraph.setSpacingAfter(spacingAfter);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
// 方法: 创建表格并填充数据
|
|
|
private void createAttendanceTable(XWPFDocument document, List<StudentAttendanceSupervisorTemplateVO> dataList, boolean isNormalList) {
|
|
|
XWPFTable table = document.createTable();
|
|
@@ -746,102 +995,204 @@ public class MailTemplateController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- /**
|
|
|
- * 生成用于展示的考勤报告文字内容
|
|
|
- *
|
|
|
- * @param teacher 导师信息
|
|
|
- * @param normalList 正常打卡学生列表
|
|
|
- * @param errorList 未打卡学生列表
|
|
|
- * @param date 报告日期
|
|
|
- * @return 生成的文字内容
|
|
|
- */
|
|
|
- public String generateAttendanceReportText(AdminUserDO teacher,
|
|
|
- List<StudentAttendanceSupervisorTemplateVO> normalList,
|
|
|
- List<StudentAttendanceSupervisorTemplateVO> errorList,
|
|
|
- LocalDate date) {
|
|
|
- StringBuilder report = new StringBuilder();
|
|
|
-
|
|
|
- // 标题
|
|
|
- report.append("测绘地理信息学院研究生考勤情况日报告").append("\n\n");
|
|
|
-
|
|
|
- // 问候语
|
|
|
- report.append("尊敬的").append(teacher.getNickname()).append("老师:").append("\n\n");
|
|
|
-
|
|
|
- // 时间信息
|
|
|
- report.append(" 您所指导的研究生 ")
|
|
|
- .append(date.getYear()).append(" 年 ")
|
|
|
- .append(date.getMonthValue()).append(" 月 ")
|
|
|
- .append(date.getDayOfMonth()).append(" 日考勤情况如下:").append("\n\n");
|
|
|
-
|
|
|
- // 未打卡学生清单
|
|
|
- if (errorList != null && !errorList.isEmpty()) {
|
|
|
- report.append("未打卡同学清单").append("\n");
|
|
|
- report.append(generateAttendanceTable(errorList, false)).append("\n");
|
|
|
- report.append("特此通知,请您及时检查学生在校情况。").append("\n\n");
|
|
|
- report.append("测绘地理信息学院研究生管理委员会").append("\n");
|
|
|
- report.append(date.toString()).append("\n\n");
|
|
|
- } else {
|
|
|
- report.append("您所指导的所有研究生今日已全部打卡!").append("\n\n");
|
|
|
- report.append("测绘地理信息学院研究生管理委员会").append("\n");
|
|
|
- report.append(date.toString()).append("\n\n");
|
|
|
+ private int currentStreamIndex = 0; // 用于记录当前使用的流索引
|
|
|
+ private float generatePDFWithTableUsingStreamPool(List<PDPageContentStream> contentStreams, PDDocument document, PDPage page,
|
|
|
+ List<StudentAttendanceSupervisorTemplateVO> dataList,
|
|
|
+ float yPosition, PDFont font, boolean isNormal) throws IOException {
|
|
|
+ float margin = 50;
|
|
|
+ float tableWidth = page.getMediaBox().getWidth() - 2 * margin; // 表格总宽度
|
|
|
+ float rowHeight = 20; // 每行高度
|
|
|
+ float yStartNewPage = page.getMediaBox().getHeight() - 50; // 新页面顶部位置
|
|
|
+ float cellMargin = 5; // 单元格内边距
|
|
|
+
|
|
|
+ float[] colWidths = {40, 100, 100, 100, 200}; // 列宽
|
|
|
+ String[] headers = {
|
|
|
+ "序号", "学生姓名", "学生学号", "实验室房号", isNormal ? "打卡时间" : "备注"
|
|
|
+ };
|
|
|
+
|
|
|
+ // 当前使用的流
|
|
|
+ PDPageContentStream contentStream = contentStreams.get(currentStreamIndex);
|
|
|
+
|
|
|
+ // 绘制表头
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
+ float cellX = margin + getColumnStartX(i, colWidths);
|
|
|
+ drawCell(contentStream, cellX, yPosition, colWidths[i], rowHeight, headers[i], font, true);
|
|
|
}
|
|
|
+ yPosition -= rowHeight; // 下移一行
|
|
|
+
|
|
|
+
|
|
|
+ // 绘制表格内容
|
|
|
+ int index = 1;
|
|
|
+ for (StudentAttendanceSupervisorTemplateVO data : dataList) {
|
|
|
+ System.out.println( "生成表格中!!"+ currentStreamIndex + contentStreams.size());
|
|
|
+
|
|
|
+ if (yPosition < margin + rowHeight) {
|
|
|
+ currentStreamIndex++;
|
|
|
+ // 如果流池已经用尽,则动态创建新的流
|
|
|
+ if (currentStreamIndex >= contentStreams.size()) {
|
|
|
+ PDPage newPage = new PDPage();
|
|
|
+ document.addPage(newPage);
|
|
|
+ PDPageContentStream newStream = new PDPageContentStream(document, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
|
|
|
+ contentStreams.add(newStream);
|
|
|
+ }
|
|
|
+ // 切换到下一个流
|
|
|
+ contentStream = contentStreams.get(currentStreamIndex);
|
|
|
+ yPosition = yStartNewPage;
|
|
|
+
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
+ float cellX = margin + getColumnStartX(i, colWidths);
|
|
|
+ drawCell(contentStream, cellX, yPosition, colWidths[i], rowHeight, headers[i], font, true);
|
|
|
+ }
|
|
|
|
|
|
- // 正常打卡学生清单
|
|
|
- report.append("正常打卡同学清单").append("\n");
|
|
|
- report.append(generateAttendanceTable(normalList, true));
|
|
|
+ yPosition -= rowHeight; // 下移一行
|
|
|
+ }
|
|
|
+
|
|
|
+ String[] rowData = {
|
|
|
+ String.valueOf(index),
|
|
|
+ data.getStudentName(),
|
|
|
+ data.getUserNumber() != null ? data.getUserNumber() : "无",
|
|
|
+ data.getDeptName() != null ? data.getDeptName() : "无",
|
|
|
+ isNormal
|
|
|
+ ? (data.getClockInTime() != null ? data.getClockInTime().toString() : "无")
|
|
|
+ : (data.getRemark() != null ? data.getRemark() : "无")
|
|
|
+ };
|
|
|
+
|
|
|
+ for (int i = 0; i < rowData.length; i++) {
|
|
|
+ float cellX = margin + getColumnStartX(i, colWidths);
|
|
|
+ drawCell(contentStream, cellX, yPosition, colWidths[i], rowHeight, rowData[i], font, false);
|
|
|
+ }
|
|
|
|
|
|
- return report.toString();
|
|
|
+ yPosition -= rowHeight; // 下移一行
|
|
|
+ index++;
|
|
|
+ }
|
|
|
+ yPosition -= 30; // 下移一行
|
|
|
+ return yPosition;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 生成考勤表格内容
|
|
|
- *
|
|
|
- * @param dataList 学生考勤数据列表
|
|
|
- * @param isNormalList 是否为正常打卡名单
|
|
|
- * @return 表格文字内容
|
|
|
- */
|
|
|
- private String generateAttendanceTable(List<StudentAttendanceSupervisorTemplateVO> dataList, boolean isNormalList) {
|
|
|
- StringBuilder table = new StringBuilder();
|
|
|
+ private float getColumnStartX(int columnIndex, float[] colWidths) {
|
|
|
+ float x = 0;
|
|
|
+ for (int i = 0; i < columnIndex; i++) {
|
|
|
+ x += colWidths[i];
|
|
|
+ }
|
|
|
+ return x;
|
|
|
+ }
|
|
|
+ private void drawCell(PDPageContentStream contentStream, float x, float y, float width, float height,
|
|
|
+ String text, PDFont font, boolean isHeader) throws IOException {
|
|
|
|
|
|
- // 表头
|
|
|
- table.append(String.format("%-5s%-10s%-15s%-15s%-15s%n", "序号", "学生姓名", "学生学号", "所在实验室房号", isNormalList ? "打卡时间" : "备注"));
|
|
|
+ if (contentStream == null) {
|
|
|
+ throw new IllegalStateException("contentStream is null in drawCell method.");
|
|
|
+ }
|
|
|
|
|
|
- // 填充数据
|
|
|
- int sequenceNumber = 1;
|
|
|
- for (StudentAttendanceSupervisorTemplateVO vo : dataList) {
|
|
|
- table.append(String.format("%-5d%-10s%-15s%-15s%-15s%n",
|
|
|
- sequenceNumber++, vo.getStudentName(), vo.getUserNumber(), vo.getDeptName(),
|
|
|
- isNormalList ? vo.getClockInTime() : vo.getRemark()));
|
|
|
+ if (font == null) {
|
|
|
+ throw new IllegalStateException("font is null in drawCell method.");
|
|
|
}
|
|
|
|
|
|
- return table.toString();
|
|
|
+ // 绘制单元格边框
|
|
|
+ contentStream.addRect(x, y - height/2, width, height);
|
|
|
+ contentStream.stroke();
|
|
|
+
|
|
|
+ // 设置字体(原有字体)
|
|
|
+ contentStream.setFont(font, 12); // 保持字体大小为 12
|
|
|
+
|
|
|
+ // 绘制文本
|
|
|
+ float textX = x + 5; // 左侧留 5 单元格内边距
|
|
|
+ float textY = y - height / 2 + 5; // 高度中心对齐,并略微向下调整
|
|
|
|
|
|
+ contentStream.beginText();
|
|
|
+ contentStream.newLineAtOffset(textX, textY);
|
|
|
+
|
|
|
+ // 如果是表头,设置加粗效果(PDType0Font 不支持加粗,需通过更换字体实现)
|
|
|
+ if (isHeader) {
|
|
|
+ contentStream.setFont(font, 14); // 加大字体模拟加粗
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查文字宽度,进行动态换行处理
|
|
|
+ float textWidth = getStringWidth(font, 12, text); // 计算文字宽度
|
|
|
+ if (textWidth > width - 10) { // 如果文字宽度超出单元格宽度
|
|
|
+ String[] words = text.split(" "); // 按空格拆分单词
|
|
|
+ StringBuilder line = new StringBuilder();
|
|
|
+ for (String word : words) {
|
|
|
+ if (getStringWidth(font, 12, line + word) > width - 10) {
|
|
|
+ // 当前行文字超出宽度,写入并换行
|
|
|
+ contentStream.showText(line.toString());
|
|
|
+ contentStream.newLineAtOffset(0, -12); // 换行(12 是字体高度)
|
|
|
+ line = new StringBuilder();
|
|
|
+ }
|
|
|
+ line.append(word).append(" ");
|
|
|
+ }
|
|
|
+ // 写入剩余文字
|
|
|
+ if (line.length() != 0) {
|
|
|
+ contentStream.showText(line.toString().trim());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ contentStream.showText(text); // 正常绘制文字
|
|
|
+ }
|
|
|
+
|
|
|
+ contentStream.endText();
|
|
|
}
|
|
|
|
|
|
- public static void convert(byte[] wordBytes, String outputFilePath) {
|
|
|
+
|
|
|
+ // 获取文字宽度的工具方法
|
|
|
+ private float getStringWidth(PDFont font, float fontSize, String text) throws IOException {
|
|
|
+ return font.getStringWidth(text) / 1000 * fontSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ @GetMapping("/generatePdf")
|
|
|
+ public void generatePdf() {
|
|
|
+ PDDocument document = new PDDocument();
|
|
|
+// 创建 ByteArrayOutputStream 用来存储 PDF 文件数据
|
|
|
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
|
|
+
|
|
|
+ PDPage page = new PDPage();
|
|
|
+ document.addPage(page);
|
|
|
+
|
|
|
try {
|
|
|
- // 使用 ByteArrayInputStream 读取 Word 文档的字节数据
|
|
|
- ByteArrayInputStream bis = new ByteArrayInputStream(wordBytes);
|
|
|
- XWPFDocument document = new XWPFDocument(bis);
|
|
|
-
|
|
|
- // 创建 PDF 文档
|
|
|
- com.itextpdf.text.Document pdfDocument = (com.itextpdf.text.Document) new com.itextpdf.text.Document();
|
|
|
- PdfWriter.getInstance((com.itextpdf.text.Document) pdfDocument, Files.newOutputStream(Paths.get(outputFilePath)));
|
|
|
- pdfDocument.open();
|
|
|
-
|
|
|
- // 将 Word 文档内容写入 PDF 文档
|
|
|
- List<XWPFParagraph> paragraphs = document.getParagraphs();
|
|
|
- for (XWPFParagraph paragraph : paragraphs) {
|
|
|
- pdfDocument.add(new Paragraph(paragraph.getText()));
|
|
|
+
|
|
|
+ PDFont font = PDType0Font.load(document, getClass().getResourceAsStream("/font/STXIHEI.TTF"));
|
|
|
+ // 创建第一页
|
|
|
+ PDPage page1 = new PDPage();
|
|
|
+ document.addPage(page1);
|
|
|
+
|
|
|
+ // 添加内容到第一页
|
|
|
+ try (PDPageContentStream contentStream1 = new PDPageContentStream(document, page1)) {
|
|
|
+ contentStream1.beginText();
|
|
|
+ contentStream1.setFont(font, 12);
|
|
|
+ contentStream1.newLineAtOffset(25, 750);
|
|
|
+ contentStream1.showText("This is the first page.");
|
|
|
+ contentStream1.endText();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建第二页
|
|
|
+ PDPage page2 = new PDPage();
|
|
|
+ document.addPage(page2);
|
|
|
+
|
|
|
+ // 添加内容到第二页
|
|
|
+ try (PDPageContentStream contentStream2 = new PDPageContentStream(document, page2)) {
|
|
|
+ contentStream2.beginText();
|
|
|
+ contentStream2.setFont(font, 12);
|
|
|
+ contentStream2.newLineAtOffset(25, 750);
|
|
|
+ contentStream2.showText("This is the second page.");
|
|
|
+ contentStream2.endText();
|
|
|
+ }
|
|
|
+ Map<String, Object> templateParams = new HashMap<>();//模板参数设置
|
|
|
+ // 附件
|
|
|
+ Map<String, InputStream> attachments = new HashMap<>();
|
|
|
+ // 创建附件
|
|
|
+
|
|
|
+ document.save(byteArrayOutputStream);
|
|
|
+ document.close();
|
|
|
+
|
|
|
+ attachments.put( "_" + "123.pdf", new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
|
|
|
+ mailSendService.sendSingleMailToMemberWithAttachments("1473464808@qq.com", null, "attendance-list-excel", templateParams, attachments);
|
|
|
+
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ document.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
}
|
|
|
- // 关闭文档
|
|
|
- pdfDocument.close();
|
|
|
- bis.close();
|
|
|
- System.out.println("Word转PDF成功!");
|
|
|
- } catch (IOException | DocumentException e) {
|
|
|
- System.out.println("Word转PDF失败:" + e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
}
|