package com.ruoyi.device.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.device.dto.DeviceRecordDto;
import com.ruoyi.device.mapper.DeviceRecordMapper;
import com.ruoyi.device.pojo.DeviceRecord;
import com.ruoyi.device.service.DeviceRecordService;
import com.ruoyi.system.domain.vo.SysRoleVo;
import com.ruoyi.system.mapper.SysRoleMenuMapper;
import org.apache.poi.xwpf.usermodel.BreakType;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.TableRowAlign;
import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblCellMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGrid;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblLayoutType;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STPageOrientation;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblLayoutType;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
*
* cnas设备使用记录表 服务实现类
*
*
* @author 江苏鵷雏网络科技有限公司
* @since 2024-09-21 11:06:47
*/
@Service
public class DeviceRecordServiceImpl extends ServiceImpl implements DeviceRecordService {
private static final String FORM_TITLE = "仪器设备运行记录";
private static final String RECORD_NUMBER = "LMSY-CX07-JL03";
private static final String FONT_NAME = "宋体";
private static final int[] COLUMN_WIDTHS = {1622, 4168, 1288, 1107, 1137, 1076, 940, 1182, 1288, 1350};
private static final String[] HEADER_FIRST_ROW = {"使用日期", "样品名称", "时间 24 小时制", "", "设备状况", "", "环境条件", "", "使用人", "备注"};
private static final String[] HEADER_SECOND_ROW = {"", "", "开始", "结束", "使用前", "使用后", "℃", "%RH", "", ""};
private static final String FOOTNOTE = "注:设备使用前后一栏,正常划“√”;不正常划“×”并写明情况。";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
@Resource
private SysRoleMenuMapper sysRoleMenuMapper;
@Override
public IPage deviceRecordPage(Integer deviceId, Page page, String deviceName,
String specificationModel, String sampleCode,
String managementNumber) {
// todo:仅看自己
Long userId = SecurityUtils.getUserId();
// 获取权限判断是否是仅看我
SysRoleVo sysRoleVo = sysRoleMenuMapper.selectRoleMenu("get:device:record", userId);
Integer testUserId = null;
//没有仅看我权限跳过
if (sysRoleVo != null && sysRoleVo.getIsRersonal() != null && sysRoleVo.getIsRersonal().equals(1)) {
testUserId = SecurityUtils.getUserId().intValue();
}
return baseMapper.deviceRecordPage(deviceId, page, deviceName, specificationModel,
sampleCode, managementNumber, testUserId);
}
@Override
public void exportUseRecord(Integer deviceId, String deviceName, String specificationModel,
String managementNumber, String sampleCode, HttpServletResponse response) {
List records = baseMapper.selectExportList(deviceId, deviceName,
specificationModel, managementNumber, sampleCode);
if (records == null || records.isEmpty()) {
throw new ServiceException("没有可导出的使用记录,无法导出");
}
// 一台设备一本运行记录,多台设备在同一 Word 内按设备分页
Map> deviceRecords = new LinkedHashMap<>();
for (DeviceRecordDto record : records) {
deviceRecords.computeIfAbsent(record.getDeviceId(), key -> new ArrayList<>()).add(record);
}
try (XWPFDocument document = new XWPFDocument()) {
setupPage(document);
boolean first = true;
for (List group : deviceRecords.values()) {
if (!first) {
addPageBreak(document);
}
renderForm(document, group.get(0), mergeSameConditionRecords(group));
first = false;
}
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
String fileName = URLEncoder.encode(FORM_TITLE, StandardCharsets.UTF_8).replace("+", "%20");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".docx");
try (OutputStream os = response.getOutputStream()) {
document.write(os);
os.flush();
}
} catch (IOException e) {
throw new RuntimeException("导出失败", e);
}
}
/**
* 同一设备、同一天、起止时间/温湿度/使用人/使用前后状况相同的记录合并一行,样品编号用“、”拼接。
*/
private List mergeSameConditionRecords(List records) {
Map merged = new LinkedHashMap<>();
for (DeviceRecordDto record : records) {
String key = String.join("|",
formatDate(record.getUseStartDate()),
formatTime(record.getUseStartDate()),
formatTime(record.getUseEndDate()),
valueOrEmpty(record.getTemperature()),
valueOrEmpty(record.getHumidity()),
valueOrEmpty(record.getUsePerson()),
String.valueOf(record.getUseBefore()),
String.valueOf(record.getUseAfter()));
DeviceRecordDto existing = merged.get(key);
if (existing == null) {
merged.put(key, record);
} else {
existing.setSampleCode(appendDistinct(existing.getSampleCode(), record.getSampleCode()));
existing.setRemark(appendDistinct(existing.getRemark(), record.getRemark()));
}
}
return new ArrayList<>(merged.values());
}
private String appendDistinct(String existing, String addition) {
if (addition == null || addition.trim().isEmpty()) {
return existing;
}
if (existing == null || existing.trim().isEmpty()) {
return addition;
}
List parts = java.util.Arrays.asList(existing.split("、"));
return parts.contains(addition) ? existing : existing + "、" + addition;
}
private void addPageBreak(XWPFDocument document) {
XWPFParagraph paragraph = document.createParagraph();
paragraph.setSpacingAfter(0);
paragraph.createRun().addBreak(BreakType.PAGE);
}
private void setupPage(XWPFDocument document) {
CTBody body = document.getDocument().getBody();
CTSectPr sectPr = body.isSetSectPr() ? body.getSectPr() : body.addNewSectPr();
CTPageSz pageSz = sectPr.isSetPgSz() ? sectPr.getPgSz() : sectPr.addNewPgSz();
pageSz.setW(BigInteger.valueOf(16838));
pageSz.setH(BigInteger.valueOf(11906));
pageSz.setOrient(STPageOrientation.LANDSCAPE);
CTPageMar pageMar = sectPr.isSetPgMar() ? sectPr.getPgMar() : sectPr.addNewPgMar();
pageMar.setTop(BigInteger.valueOf(680));
pageMar.setBottom(BigInteger.valueOf(680));
pageMar.setLeft(BigInteger.valueOf(794));
pageMar.setRight(BigInteger.valueOf(794));
pageMar.setHeader(BigInteger.valueOf(851));
pageMar.setFooter(BigInteger.valueOf(992));
pageMar.setGutter(BigInteger.ZERO);
}
private void renderForm(XWPFDocument document, DeviceRecordDto device, List records) {
XWPFParagraph recordNumber = addParagraph(document, "记录编号:" + RECORD_NUMBER, 10.5, false, ParagraphAlignment.LEFT);
recordNumber.setSpacingAfter(240);
XWPFParagraph title = addParagraph(document, FORM_TITLE, 22, true, ParagraphAlignment.CENTER);
title.setSpacingAfter(240);
renderDeviceInfo(document, device);
document.createParagraph();
XWPFTable table = document.createTable(2 + records.size(), COLUMN_WIDTHS.length);
configureTable(table, COLUMN_WIDTHS);
for (int i = 0; i < table.getNumberOfRows(); i++) {
XWPFTableRow row = table.getRow(i);
row.setHeight(i < 2 ? 560 : 520);
for (int j = 0; j < COLUMN_WIDTHS.length; j++) {
setCellWidth(row.getCell(j), COLUMN_WIDTHS[j]);
}
setRowProperties(row, i < 2);
}
XWPFTableRow firstHeader = table.getRow(0);
for (int i = 0; i < HEADER_FIRST_ROW.length; i++) {
setCellText(firstHeader.getCell(i), HEADER_FIRST_ROW[i]);
}
XWPFTableRow secondHeader = table.getRow(1);
for (int i = 0; i < HEADER_SECOND_ROW.length; i++) {
setCellText(secondHeader.getCell(i), HEADER_SECOND_ROW[i]);
}
mergeHorizontal(firstHeader, COLUMN_WIDTHS, 6, 2);
mergeHorizontal(firstHeader, COLUMN_WIDTHS, 4, 2);
mergeHorizontal(firstHeader, COLUMN_WIDTHS, 2, 2);
mergeVertical(firstHeader.getCell(0), secondHeader.getCell(0));
mergeVertical(firstHeader.getCell(1), secondHeader.getCell(1));
mergeVertical(firstHeader.getCell(5), secondHeader.getCell(8));
mergeVertical(firstHeader.getCell(6), secondHeader.getCell(9));
for (int i = 0; i < records.size(); i++) {
XWPFTableRow row = table.getRow(2 + i);
String[] values = buildRowValues(records.get(i));
for (int j = 0; j < values.length; j++) {
setCellText(row.getCell(j), values[j]);
}
}
XWPFParagraph footnote = addParagraph(document, FOOTNOTE, 10.5, false, ParagraphAlignment.LEFT);
footnote.setSpacingBefore(120);
footnote.setSpacingAfter(0);
}
private XWPFParagraph addParagraph(XWPFDocument document, String text, double fontSize, boolean bold, ParagraphAlignment alignment) {
XWPFParagraph paragraph = document.createParagraph();
paragraph.setAlignment(alignment);
addRun(paragraph, text, fontSize, bold);
return paragraph;
}
private void addDeviceField(XWPFParagraph paragraph, String label, String value) {
addRun(paragraph, label, 10.5, false);
XWPFRun valueRun = addRun(paragraph, padUnderline(value), 10.5, false);
valueRun.setUnderline(UnderlinePatterns.SINGLE);
}
private String padUnderline(String value) {
String content = value == null ? "" : value.trim();
int width = 0;
for (int i = 0; i < content.length(); i++) {
width += content.charAt(i) >= 0x2E80 ? 2 : 1;
}
StringBuilder text = new StringBuilder(content);
for (int i = width; i < 20; i++) {
text.append(' ');
}
return text.toString();
}
private XWPFRun addRun(XWPFParagraph paragraph, String text, double fontSize, boolean bold) {
XWPFRun run = paragraph.createRun();
run.setFontFamily(FONT_NAME);
run.setFontFamily(FONT_NAME, XWPFRun.FontCharRange.eastAsia);
run.setFontSize(fontSize);
run.setBold(bold);
run.setText(text);
return run;
}
private void renderDeviceInfo(XWPFDocument document, DeviceRecordDto device) {
String[] labels = {"设备名称:", "设备型号:", "设备编号:"};
String[] values = {
device == null ? "" : device.getDeviceName(),
device == null ? "" : device.getSpecificationModel(),
device == null ? "" : device.getManagementNumber()
};
int[] widths = {5052, 5053, 5053};
XWPFTable table = document.createTable(1, labels.length);
CTTbl ctTbl = table.getCTTbl();
CTTblPr tblPr = ctTbl.getTblPr() != null ? ctTbl.getTblPr() : ctTbl.addNewTblPr();
CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW();
tblWidth.setType(STTblWidth.DXA);
tblWidth.setW(BigInteger.valueOf(totalWidth(COLUMN_WIDTHS)));
CTTblLayoutType layoutType = tblPr.isSetTblLayout() ? tblPr.getTblLayout() : tblPr.addNewTblLayout();
layoutType.setType(STTblLayoutType.FIXED);
setNoBorders(tblPr);
CTTblCellMar cellMar = tblPr.isSetTblCellMar() ? tblPr.getTblCellMar() : tblPr.addNewTblCellMar();
setZeroWidth(cellMar.isSetLeft() ? cellMar.getLeft() : cellMar.addNewLeft());
setZeroWidth(cellMar.isSetRight() ? cellMar.getRight() : cellMar.addNewRight());
CTTblGrid grid = ctTbl.getTblGrid() != null ? ctTbl.getTblGrid() : ctTbl.addNewTblGrid();
while (grid.sizeOfGridColArray() > 0) {
grid.removeGridCol(0);
}
for (int width : widths) {
grid.addNewGridCol().setW(BigInteger.valueOf(width));
}
XWPFTableRow row = table.getRow(0);
for (int i = 0; i < labels.length; i++) {
XWPFTableCell cell = row.getCell(i);
setCellWidth(cell, widths[i]);
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0);
paragraph.setAlignment(ParagraphAlignment.LEFT);
paragraph.setSpacingBefore(0);
paragraph.setSpacingAfter(0);
addDeviceField(paragraph, labels[i], values[i]);
}
}
private void setNoBorders(CTTblPr tblPr) {
CTTblBorders borders = tblPr.isSetTblBorders() ? tblPr.getTblBorders() : tblPr.addNewTblBorders();
setNoBorder(borders.isSetTop() ? borders.getTop() : borders.addNewTop());
setNoBorder(borders.isSetLeft() ? borders.getLeft() : borders.addNewLeft());
setNoBorder(borders.isSetBottom() ? borders.getBottom() : borders.addNewBottom());
setNoBorder(borders.isSetRight() ? borders.getRight() : borders.addNewRight());
setNoBorder(borders.isSetInsideH() ? borders.getInsideH() : borders.addNewInsideH());
setNoBorder(borders.isSetInsideV() ? borders.getInsideV() : borders.addNewInsideV());
}
private void setNoBorder(CTBorder border) {
border.setVal(STBorder.NIL);
border.setSz(BigInteger.ZERO);
border.setSpace(BigInteger.ZERO);
border.setColor("auto");
}
private void setZeroWidth(CTTblWidth width) {
width.setType(STTblWidth.DXA);
width.setW(BigInteger.ZERO);
}
private void configureTable(XWPFTable table, int[] widths) {
table.setTableAlignment(TableRowAlign.CENTER);
CTTbl ctTbl = table.getCTTbl();
CTTblPr tblPr = ctTbl.getTblPr() != null ? ctTbl.getTblPr() : ctTbl.addNewTblPr();
CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW();
tblWidth.setType(STTblWidth.DXA);
tblWidth.setW(BigInteger.valueOf(totalWidth(widths)));
CTTblLayoutType layoutType = tblPr.isSetTblLayout() ? tblPr.getTblLayout() : tblPr.addNewTblLayout();
layoutType.setType(STTblLayoutType.FIXED);
CTTblBorders borders = tblPr.isSetTblBorders() ? tblPr.getTblBorders() : tblPr.addNewTblBorders();
setBorder(borders.isSetTop() ? borders.getTop() : borders.addNewTop());
setBorder(borders.isSetLeft() ? borders.getLeft() : borders.addNewLeft());
setBorder(borders.isSetBottom() ? borders.getBottom() : borders.addNewBottom());
setBorder(borders.isSetRight() ? borders.getRight() : borders.addNewRight());
setBorder(borders.isSetInsideH() ? borders.getInsideH() : borders.addNewInsideH());
setBorder(borders.isSetInsideV() ? borders.getInsideV() : borders.addNewInsideV());
CTTblGrid grid = ctTbl.getTblGrid() != null ? ctTbl.getTblGrid() : ctTbl.addNewTblGrid();
while (grid.sizeOfGridColArray() > 0) {
grid.removeGridCol(0);
}
for (int width : widths) {
grid.addNewGridCol().setW(BigInteger.valueOf(width));
}
}
private int totalWidth(int[] widths) {
int total = 0;
for (int width : widths) {
total += width;
}
return total;
}
private void setBorder(CTBorder border) {
border.setVal(STBorder.SINGLE);
border.setSz(BigInteger.valueOf(4));
border.setSpace(BigInteger.ZERO);
border.setColor("000000");
}
private void setCellWidth(XWPFTableCell cell, int width) {
CTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();
CTTblWidth tcW = tcPr.isSetTcW() ? tcPr.getTcW() : tcPr.addNewTcW();
tcW.setType(STTblWidth.DXA);
tcW.setW(BigInteger.valueOf(width));
}
private void setCellText(XWPFTableCell cell, String text) {
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0);
paragraph.setAlignment(ParagraphAlignment.CENTER);
paragraph.setSpacingBefore(0);
paragraph.setSpacingAfter(0);
addRun(paragraph, text == null ? "" : text, 10.5, false);
}
private void setRowProperties(XWPFTableRow row, boolean repeatHeader) {
CTRow ctRow = row.getCtRow();
CTTrPr trPr = ctRow.isSetTrPr() ? ctRow.getTrPr() : ctRow.addNewTrPr();
trPr.addNewCantSplit();
if (repeatHeader) {
trPr.addNewTblHeader();
}
}
private void mergeHorizontal(XWPFTableRow row, int[] widths, int firstIndex, int span) {
int width = 0;
for (int i = firstIndex; i < firstIndex + span; i++) {
width += widths[i];
}
XWPFTableCell first = row.getCell(firstIndex);
setCellWidth(first, width);
CTTcPr tcPr = first.getCTTc().isSetTcPr() ? first.getCTTc().getTcPr() : first.getCTTc().addNewTcPr();
(tcPr.isSetGridSpan() ? tcPr.getGridSpan() : tcPr.addNewGridSpan()).setVal(BigInteger.valueOf(span));
for (int i = firstIndex + span - 1; i > firstIndex; i--) {
row.removeCell(i);
}
}
private void mergeVertical(XWPFTableCell first, XWPFTableCell second) {
setVMerge(first, STMerge.RESTART);
setVMerge(second, STMerge.CONTINUE);
}
private void setVMerge(XWPFTableCell cell, STMerge.Enum value) {
CTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();
(tcPr.isSetVMerge() ? tcPr.getVMerge() : tcPr.addNewVMerge()).setVal(value);
}
private String[] buildRowValues(DeviceRecordDto record) {
return new String[]{
formatDate(record.getUseStartDate()),
valueOrEmpty(record.getSampleCode()),
formatTime(record.getUseStartDate()),
formatTime(record.getUseEndDate()),
statusText(record.getUseBefore()),
statusText(record.getUseAfter()),
valueOrEmpty(record.getTemperature()),
valueOrEmpty(record.getHumidity()),
valueOrEmpty(record.getUsePerson()),
valueOrEmpty(record.getRemark())
};
}
private String formatDate(LocalDateTime value) {
return value == null ? "" : value.format(DATE_FORMATTER);
}
private String formatTime(LocalDateTime value) {
return value == null ? "" : value.format(TIME_FORMATTER);
}
private String statusText(Integer value) {
if (value == null) {
return "";
}
return value == 0 ? "异常" : "正常";
}
private String valueOrEmpty(String value) {
return value == null ? "" : value;
}
}