From 34c2a5f8b46a960c220b1a5916a854093e9e0776 Mon Sep 17 00:00:00 2001 From: alsszer Date: Mon, 9 Feb 2026 14:13:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(plugin):=20=E4=BC=98=E5=8C=96EMQX=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E5=8A=9F=E8=83=BD=E5=B9=B6=E5=A2=9E=E5=BC=BA=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在application.yml中将运行模式从prod改为dev - 在application-dev.yml中添加generate_statistics配置项以关闭统计信息 - 在AuthVerticle.java中添加MQTT ACL主题路径转换逻辑 - 在DeviceInfoDataCache.java和DeviceInfoDataImpl.java中增加设备状态查询方法 - 在DeviceInfoExpordVo和DeviceInfoImportVo中新增site字段用于设备位置导出导入 - 在DeviceInstallInfo.java中添加多个图片相关字段如泡沫水测漏图片、设备信息图片、门牌号图片 - 在DeviceInstallInfoDataImpl.java中实现findById方法 - 在DeviceInstallInfoExpordVo和DeviceInstallInfoVo中添加切断阀编号和报警器编号导出功能 - 在DeviceInstallInfoServiceImpl.java中完善安装信息查询逻辑 - 在DeviceManagerServiceImpl.java中支持设备状态查询参数 - 在DeviceQueryBo.java中新增deviceStatus字段用于设备状态筛选 - 在feijialuo-emqx-plugin中优化线程池配置并提升并发处理能力 - 在iot-iita-emqx-plugin中调整设备识别规则适配不同平台需求 --- .../model/device/DeviceInstallInfo.java | 6 + .../iotkit/data/manager/IDeviceInfoData.java | 3 + .../data/manager/IDeviceInstallInfoData.java | 2 + .../cc/iotkit/data/system/ISysRoleData.java | 2 + .../data/service/DeviceInfoDataCache.java | 6 + .../service/DeviceInfoPropertyDataCache.java | 5 + .../data/model/TbDeviceInstallInfo.java | 6 + .../data/service/DeviceInfoDataImpl.java | 139 +++- .../service/DeviceInstallInfoDataImpl.java | 6 + .../iotkit/data/service/SysRoleDataImpl.java | 9 + .../iotkit/manager/config/PDFGenerator.java | 620 ++++++++++++++++-- .../manager/dto/bo/device/DeviceQueryBo.java | 3 + .../dto/vo/deviceinfo/DeviceInfoExpordVo.java | 3 + .../dto/vo/deviceinfo/DeviceInfoImportVo.java | 2 + .../deviceinfo/DeviceInstallInfoExpordVo.java | 4 + .../vo/deviceinfo/DeviceInstallInfoVo.java | 4 + .../impl/DeviceInstallInfoServiceImpl.java | 4 +- .../impl/DeviceManagerServiceImpl.java | 3 +- .../iotkit/plugin/main/ThingServiceImpl.java | 8 +- .../service/impl/SysTenantServiceImpl.java | 3 +- .../src/main/resources/application-dev.yml | 1 + 21 files changed, 762 insertions(+), 77 deletions(-) diff --git a/iot-dao/iot-data-model/src/main/java/cc/iotkit/model/device/DeviceInstallInfo.java b/iot-dao/iot-data-model/src/main/java/cc/iotkit/model/device/DeviceInstallInfo.java index caed632..cc2d7dc 100644 --- a/iot-dao/iot-data-model/src/main/java/cc/iotkit/model/device/DeviceInstallInfo.java +++ b/iot-dao/iot-data-model/src/main/java/cc/iotkit/model/device/DeviceInstallInfo.java @@ -77,6 +77,8 @@ public class DeviceInstallInfo extends TenantModel implements Owned,Seri private String workingOfTheDetectorImage; // @ApiModelProperty(value = "测漏图片") private String sideLeakageImage; + // @ApiModelProperty(value = "泡沫水测漏图片") + private String formSideLeakageImage; // @ApiModelProperty(value = "点火图片") private String ignitionPictureImage; // @ApiModelProperty(value = "装完成全景图片") @@ -87,6 +89,10 @@ public class DeviceInstallInfo extends TenantModel implements Owned,Seri private String workOrderImage; //@ApiModelProperty(value = "打孔图片") private String punchingImage; + //@ApiModelProperty(value = "设备信息图片") + private String deviceInfoImage; + //@ApiModelProperty(value = "门牌号图片") + private String houseNumberImage; // @ApiModelProperty(value = "用户id") private String uid; // @ApiModelProperty(value = "租户编号") diff --git a/iot-dao/iot-data-service/src/main/java/cc/iotkit/data/manager/IDeviceInfoData.java b/iot-dao/iot-data-service/src/main/java/cc/iotkit/data/manager/IDeviceInfoData.java index 06bb415..d90b79b 100644 --- a/iot-dao/iot-data-service/src/main/java/cc/iotkit/data/manager/IDeviceInfoData.java +++ b/iot-dao/iot-data-service/src/main/java/cc/iotkit/data/manager/IDeviceInfoData.java @@ -114,6 +114,9 @@ public interface IDeviceInfoData extends IOwnedData { * @param page 页码 * @param size 分页大小 */ + Paging findByConditions(String name,String uid, String subUid, String productKey, + String groupId, Boolean online, String keyword, + int page, int size,Long areaDepeId,String startTime,String endTime,String deviceName,Integer deviceStatus); Paging findByConditions(String name,String uid, String subUid, String productKey, String groupId, Boolean online, String keyword, int page, int size,Long areaDepeId,String startTime,String endTime,String deviceName); diff --git a/iot-dao/iot-data-service/src/main/java/cc/iotkit/data/manager/IDeviceInstallInfoData.java b/iot-dao/iot-data-service/src/main/java/cc/iotkit/data/manager/IDeviceInstallInfoData.java index 8efe3e7..b7fe366 100644 --- a/iot-dao/iot-data-service/src/main/java/cc/iotkit/data/manager/IDeviceInstallInfoData.java +++ b/iot-dao/iot-data-service/src/main/java/cc/iotkit/data/manager/IDeviceInstallInfoData.java @@ -33,4 +33,6 @@ public interface IDeviceInstallInfoData extends IOwnedData { List selectRoleList(SysRole role); List findByUserId(Long id); + + List selectByTenantId(Long tenantId); } diff --git a/iot-dao/iot-data-serviceImpl-cache/src/main/java/cc/iotkit/data/service/DeviceInfoDataCache.java b/iot-dao/iot-data-serviceImpl-cache/src/main/java/cc/iotkit/data/service/DeviceInfoDataCache.java index fe5abb3..94cc0a7 100644 --- a/iot-dao/iot-data-serviceImpl-cache/src/main/java/cc/iotkit/data/service/DeviceInfoDataCache.java +++ b/iot-dao/iot-data-serviceImpl-cache/src/main/java/cc/iotkit/data/service/DeviceInfoDataCache.java @@ -244,6 +244,12 @@ public class DeviceInfoDataCache implements IDeviceInfoData, SmartInitializingSi String groupId, Boolean state, String keyword, int page, int size,Long deptAreaId,String startTime,String endTime,String deviceName) { return deviceInfoData.findByConditions(name,uid, subUid, productKey, groupId, state, keyword, page, size,deptAreaId,startTime,endTime,deviceName); } + + @Override + public Paging findByConditions(String name,String uid, String subUid, String productKey, + String groupId, Boolean state, String keyword, int page, int size,Long deptAreaId,String startTime,String endTime,String deviceName,Integer deviceStatus) { + return deviceInfoData.findByConditions(name,uid, subUid, productKey, groupId, state, keyword, page, size,deptAreaId,startTime,endTime,deviceName,deviceStatus); + } @Override public Paging findByConditionsExcel(String name,String uid, String subUid, String productKey, String groupId, Boolean state, String keyword, int page, int size,Long deptAreaId,String startTime,String endTime) { diff --git a/iot-dao/iot-data-serviceImpl-cache/src/main/java/cc/iotkit/data/service/DeviceInfoPropertyDataCache.java b/iot-dao/iot-data-serviceImpl-cache/src/main/java/cc/iotkit/data/service/DeviceInfoPropertyDataCache.java index 39d87fb..8b5695e 100644 --- a/iot-dao/iot-data-serviceImpl-cache/src/main/java/cc/iotkit/data/service/DeviceInfoPropertyDataCache.java +++ b/iot-dao/iot-data-serviceImpl-cache/src/main/java/cc/iotkit/data/service/DeviceInfoPropertyDataCache.java @@ -202,6 +202,11 @@ public class DeviceInfoPropertyDataCache implements IDeviceInfoData { public Paging findByConditions(String name,String uid, String subUid, String productKey, String groupId, Boolean online, String keyword, int page, int size,Long deptAreaId,String startTime,String endTime,String deviceName) { return deviceInfoData.findByConditions(name,uid, subUid, productKey, groupId, online, keyword, page, size,deptAreaId,startTime,endTime,deviceName); } + + @Override + public Paging findByConditions(String name,String uid, String subUid, String productKey, String groupId, Boolean online, String keyword, int page, int size,Long deptAreaId,String startTime,String endTime,String deviceName,Integer deviceStatus) { + return deviceInfoData.findByConditions(name,uid, subUid, productKey, groupId, online, keyword, page, size,deptAreaId,startTime,endTime,deviceName,deviceStatus); + } @Override public Paging findByConditionsExcel(String name,String uid, String subUid, String productKey, String groupId, Boolean online, String keyword, int page, int size,Long deptAreaId,String startTime,String endTime) { return deviceInfoData.findByConditionsExcel(name,uid, subUid, productKey, groupId, online, keyword, page, size,deptAreaId,startTime,endTime); diff --git a/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/model/TbDeviceInstallInfo.java b/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/model/TbDeviceInstallInfo.java index acac545..514fd08 100644 --- a/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/model/TbDeviceInstallInfo.java +++ b/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/model/TbDeviceInstallInfo.java @@ -86,6 +86,8 @@ public class TbDeviceInstallInfo extends BaseEntity implements TenantAware { private String workingOfTheDetectorImage; @ApiModelProperty(value = "测漏图片") private String sideLeakageImage; + @ApiModelProperty(value = "泡沫水测漏图片") + private String formSideLeakageImage; @ApiModelProperty(value = "点火图片") private String ignitionPictureImage; @ApiModelProperty(value = "装完成全景图片") @@ -98,6 +100,10 @@ public class TbDeviceInstallInfo extends BaseEntity implements TenantAware { private String punchingImage; @ApiModelProperty(value = "装电源线照片") private String fiexImage; + @ApiModelProperty(value = "设备信息图片") + private String deviceInfoImage; + @ApiModelProperty(value = "门牌号图片") + private String houseNumberImage; @ApiModelProperty(value = "用户id") private String uid; diff --git a/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/DeviceInfoDataImpl.java b/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/DeviceInfoDataImpl.java index d3cf489..e84b870 100644 --- a/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/DeviceInfoDataImpl.java +++ b/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/DeviceInfoDataImpl.java @@ -637,7 +637,7 @@ public class DeviceInfoDataImpl implements IDeviceInfoData, IJPACommData findByConditions(String name, String uid, String subUid, String productKey, String groupId, Boolean online, String keyword, - int page, int size, Long deptAreaId,String startTime,String endTime,String deviceName) { + int page, int size, Long deptAreaId,String startTime,String endTime,String deviceName,Integer deviceStatus) { JPAQuery query = jpaQueryFactory.selectFrom(tbDeviceInfo); // 根据groupId, 如果groupId存在,则关联查询TbDeviceGroupMapping, 根据groupId,查询对应的devices @@ -677,6 +677,21 @@ public class DeviceInfoDataImpl implements IDeviceInfoData, IJPACommData tbDeviceInfos = query.fetch(); + long total = query.fetchCount(); + List deviceInfos = new ArrayList<>(tbDeviceInfos.size()); + for (TbDeviceInfo tbDeviceInfo : tbDeviceInfos) { + DeviceInfo deviceInfo = MapstructUtils.convert(tbDeviceInfo, DeviceInfo.class); + fillDeviceInfo(tbDeviceInfo.getDeviceId(), tbDeviceInfo, deviceInfo); + deviceInfos.add(deviceInfo); + } + return new Paging<>(total, deviceInfos); + } + public Paging findByConditions(String name, String uid, String subUid, + String productKey, String groupId, + Boolean online, String keyword, + int page, int size, Long deptAreaId,String startTime,String endTime,String deviceName) { + JPAQuery query = jpaQueryFactory.selectFrom(tbDeviceInfo); + + // 根据groupId, 如果groupId存在,则关联查询TbDeviceGroupMapping, 根据groupId,查询对应的devices + if (StringUtils.isNotBlank(groupId)) { + if(!groupId.equals("0")){ + query.join(tbDeviceGroupMapping).on(tbDeviceGroupMapping.deviceId.eq(tbDeviceInfo.deviceId)); + // query.where(tbDeviceGroupMapping.groupId.eq(groupId)); + query.where(tbDeviceGroupMapping.groupId.eq(groupId)); + } + }else{ + query.join(tbDeviceGroupMapping).on(tbDeviceGroupMapping.deviceId.eq(tbDeviceInfo.deviceId)); + query.where(tbDeviceGroupMapping.groupId.isNull()); + + } + + + if (StringUtils.isNotBlank(uid)) { + query.where(tbDeviceInfo.uid.eq(uid)); + } + if (StringUtils.isNotBlank(name)) { + query.where(tbDeviceInfo.name.like("%" + name + "%")); + } + if (StringUtils.isNotBlank(deviceName)) { + query.where(tbDeviceInfo.deviceName.like("%" + deviceName + "%")); + } + System.out.println(TenantHelper.getTenantId()); + if (ObjectUtil.isNotNull(TenantHelper.getTenantId()) && (!LoginHelper.isSuperAdmin() || TenantHelper.getTenantId()!=0)) { + query.where(tbDeviceInfo.tenantId.eq(TenantHelper.getTenantId())); + } + if (ObjectUtil.isNotNull(deptAreaId)) { + /* List areaIds; + // if (Objects.nonNull(user) && Objects.nonNull(user.getDeptAreaId())) { + Long areaId = deptAreaId; + List depts = sysDeptData.findByDeptId(areaId); + areaIds = StreamUtils.toList(depts, SysDept::getId); + areaIds.add(areaId); + if (ObjectUtil.isNotEmpty(areaIds)) {*/ + if(deptAreaId != 0) { + query.where(tbDeviceInfo.deptAreaId.in(deptAreaId)); + } + // query.where(tbDeviceInfo.deptAreaId.in(deptAreaId)); + // } + }else{ + if(!LoginHelper.isSuperAdmin()) { + if(ObjectUtil.isNotEmpty(LoginHelper.getUserId())) { + + SysUser user = isSysUserData.findById(LoginHelper.getUserId()); + if (ObjectUtil.isNotNull(user)) { + if (ObjectUtil.isNotNull(user.getDeptAreaId())) { + /* List areaIds; + // if (Objects.nonNull(user) && Objects.nonNull(user.getDeptAreaId())) { + Long areaId = user.getDeptAreaId(); + List depts = sysDeptData.findByDeptId(areaId); + areaIds = StreamUtils.toList(depts, SysDept::getId); + areaIds.add(areaId); + if (ObjectUtil.isNotEmpty(areaIds)) {*/ + query.where(tbDeviceInfo.deptAreaId.in(user.getDeptAreaId())); + // } + } else { + if(ObjectUtil.isNotEmpty(LoginHelper.getUserType()) && !LoginHelper.getUserType().equals(UserType.APP_USER)) { + //没有绑定区域查不到设备 + query.where(tbDeviceInfo.id.eq("0")); + } + } + } + } + }else{ + query.where(tbDeviceInfo.deptAreaId.isNull()); + } + } + if (StringUtils.isNotBlank(subUid)) { + query.join(tbDeviceSubUser).on(tbDeviceSubUser.deviceId.eq(tbDeviceInfo.deviceId)); + query.where(tbDeviceSubUser.uid.eq(subUid)); + } + + if (StringUtils.isNotBlank(productKey)) { + query.where(tbDeviceInfo.productKey.eq(productKey)); + } + if (online != null) { query.where(tbDeviceInfo.state.eq(online ? "online" : "offline")); } diff --git a/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/DeviceInstallInfoDataImpl.java b/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/DeviceInstallInfoDataImpl.java index 4d7d912..cdf70cb 100644 --- a/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/DeviceInstallInfoDataImpl.java +++ b/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/DeviceInstallInfoDataImpl.java @@ -77,4 +77,10 @@ public class DeviceInstallInfoDataImpl implements IDeviceInstallInfoData, IJPACo public DeviceInstallInfo findByDeviceNameAndUidAndState(String deviceName, String uid, Integer state) { return MapstructUtils.convert(homeRepository.findByDeviceNameAndUidAndState(deviceName,uid,state), DeviceInstallInfo.class); } + + @Override + public DeviceInstallInfo findById(String id) { + TbDeviceInstallInfo tbDeviceInstallInfo = homeRepository.findById(id).orElse(null); + return MapstructUtils.convert(tbDeviceInstallInfo, DeviceInstallInfo.class); + } } diff --git a/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/SysRoleDataImpl.java b/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/SysRoleDataImpl.java index eb60a58..c5b595e 100644 --- a/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/SysRoleDataImpl.java +++ b/iot-dao/iot-data-serviceImpl-rdb/src/main/java/cc/iotkit/data/service/SysRoleDataImpl.java @@ -222,6 +222,15 @@ public class SysRoleDataImpl implements ISysRoleData, IJPACommData selectByTenantId(Long tenantId) { + return jpaQueryFactory.select(Projections.bean(SysRole.class, tbSysRole.id, tbSysRole.roleName, tbSysRole.roleKey, tbSysRole.roleSort, tbSysRole.dataScope, tbSysRole.status, tbSysRole.delFlag, tbSysRole.createTime, tbSysRole.remark)) + .from(tbSysRole) + .where(PredicateBuilder.instance() + .and(tbSysRole.tenantId.eq(tenantId)) + .build()).fetch(); + } + private List buildQueryTitle(Predicate predicate) { return jpaQueryFactory.selectDistinct(tbSysRole.id) .select(Projections.fields(SysRole.class, tbSysRole.id, tbSysRole.roleName, diff --git a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/config/PDFGenerator.java b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/config/PDFGenerator.java index 0c0f282..0eedd3c 100644 --- a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/config/PDFGenerator.java +++ b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/config/PDFGenerator.java @@ -13,13 +13,34 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.*; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; public class PDFGenerator { + + // 缓存字体实例以避免重复加载 + private static volatile PDType0Font cachedFont = null; + private static final Object fontLock = new Object(); + + // 缓存已处理的图片以避免重复下载和处理 + private static final ConcurrentHashMap imageCache = new ConcurrentHashMap<>(); + + // 线程池用于并发处理图片 + private static final ExecutorService imageProcessingPool = Executors.newFixedThreadPool(4); + // 1. 表格参数配置 // 参数配置(优化后) @@ -33,9 +54,13 @@ public class PDFGenerator { private static final float[] COLUMN_WIDTHS = {LEFT_COL_WIDTH, RIGHT_COL_WIDTH}; private static final float MIN_IMAGE_HEIGHT = 50; public static void generateInstallationPDF(DeviceInstallInfo entity, OutputStream outputStream) throws IOException { + + // 首先生成PDF内容到字节数组 + ByteArrayOutputStream pdfBaos = new ByteArrayOutputStream(); + try (PDDocument document = new PDDocument()) { // 加载字体(需替换实际路径) - //PDType0Font font = PDType0Font.load(document, new File("D:\\NotoSansCJK-Regular.ttf")); +// PDType0Font font = PDType0Font.load(document, new File("D:\\NotoSansCJK-Regular.ttf")); //PDType0Font font = PDType0Font.load(document, new File("/ttf/simsun.ttf")); PDType0Font font = PDType0Font.load(document, new File("/ttf/NotoSansCJK-Regular.ttf")); // 初始化第一页 @@ -76,15 +101,18 @@ public class PDFGenerator { // 图片处理(带自动分页) // 图片处理(带自动分页) List imageGroups = Arrays.asList( + new String[]{entity.getDeviceInfoImage(), "设备信息图片"}, new String[]{entity.getBeforeInstallationImage(), "安装前图片"}, new String[]{entity.getWorkingOfTheDetectorImage(), "安装完成探测器工作图片"}, new String[]{entity.getSideLeakageImage(), "测漏图片"}, + new String[]{entity.getFormSideLeakageImage(), "泡沫水测漏图片"}, new String[]{entity.getIgnitionPictureImage(), "点火图片"}, new String[]{entity.getInstallThePanoramicImage(), "安装完成全景图片"}, - new String[]{entity.getOfGasMeterImage(), "燃气表图片"}, + new String[]{entity.getOfGasMeterImage(), "燃气表号图片"}, new String[]{entity.getWorkOrderImage(), "工单图片"}, new String[]{entity.getPunchingImage(), "打孔图片"}, - new String[]{entity.getFiexImage(), "安装电源线图片"} + new String[]{entity.getFiexImage(), "安装电源线图片"}, + new String[]{entity.getHouseNumberImage(), "门牌号图片"} ); for (String[] group : imageGroups) { @@ -98,27 +126,61 @@ public class PDFGenerator { .collect(Collectors.toList()); if (!validUrls.isEmpty()) { - // float totalHeight = calculateTotalHeight(validUrls); - List wrappedTitleLines = wrapText(title,LEFT_COL_WIDTH - 2 * CELL_PADDING); - float titleHeight = wrappedTitleLines.size() * LINE_HEIGHT; - // 计算图片组的自适应高度 - float imagesHeight = calculateAdjustedImagesHeight(document, validUrls, - RIGHT_COL_WIDTH - 2 * CELL_PADDING, - PDRectangle.A4.getHeight() - 2 * TABLE_MARGIN - titleHeight); - - float totalHeight = Math.max(imagesHeight, titleHeight); - - if (currentY < TABLE_MARGIN + Math.max(totalHeight, titleHeight)) { - cs.close(); - currentPage = new PDPage(PDRectangle.A4); - document.addPage(currentPage); - cs = new PDPageContentStream(document, currentPage); - currentY = PDRectangle.A4.getHeight() - TABLE_MARGIN; + // 处理可能需要跨页的多图片情况 + if (validUrls.size() > 2) { + // 对于多于2张的图片,逐个或分组处理以支持跨页 + for (int i = 0; i < validUrls.size(); i += 2) { // 每次处理最多2张图片 + List subList = validUrls.subList(i, + Math.min(i + 2, validUrls.size())); + + List wrappedTitleLines = wrapText(title,LEFT_COL_WIDTH - 2 * CELL_PADDING); + float titleHeight = wrappedTitleLines.size() * LINE_HEIGHT; + // 计算图片组的自适应高度 + float imagesHeight = calculateAdjustedImagesHeight(document, subList, + RIGHT_COL_WIDTH - 2 * CELL_PADDING, + PDRectangle.A4.getHeight() - 2 * TABLE_MARGIN - titleHeight); + + float totalHeight = Math.max(imagesHeight, titleHeight); + + if (currentY < TABLE_MARGIN + Math.max(totalHeight, titleHeight)) { + cs.close(); + currentPage = new PDPage(PDRectangle.A4); + document.addPage(currentPage); + cs = new PDPageContentStream(document, currentPage); + currentY = PDRectangle.A4.getHeight() - TABLE_MARGIN; + } + + // 添加标识,表明这是多图片的一部分 + String subTitle = title + " (" + (i+1) + "-" + Math.min(i+2, validUrls.size()) + "/" + validUrls.size() + ")"; + List subWrappedTitleLines = wrapText(subTitle, LEFT_COL_WIDTH - 2 * CELL_PADDING); + + drawImageGroup(cs, document, font, subWrappedTitleLines, subList, currentY, + Math.max(totalHeight, titleHeight)); + currentY -= Math.max(totalHeight, titleHeight); + } + } else { + // 原有的单/双图片处理逻辑 + List wrappedTitleLines = wrapText(title,LEFT_COL_WIDTH - 2 * CELL_PADDING); + float titleHeight = wrappedTitleLines.size() * LINE_HEIGHT; + // 计算图片组的自适应高度 + float imagesHeight = calculateAdjustedImagesHeight(document, validUrls, + RIGHT_COL_WIDTH - 2 * CELL_PADDING, + PDRectangle.A4.getHeight() - 2 * TABLE_MARGIN - titleHeight); + + float totalHeight = Math.max(imagesHeight, titleHeight); + + if (currentY < TABLE_MARGIN + Math.max(totalHeight, titleHeight)) { + cs.close(); + currentPage = new PDPage(PDRectangle.A4); + document.addPage(currentPage); + cs = new PDPageContentStream(document, currentPage); + currentY = PDRectangle.A4.getHeight() - TABLE_MARGIN; + } + + drawImageGroup(cs, document, font, wrappedTitleLines, validUrls, currentY, + Math.max(totalHeight, titleHeight)); + currentY -= Math.max(totalHeight, titleHeight); } - - drawImageGroup(cs, document, font, wrappedTitleLines, validUrls, currentY, - Math.max(totalHeight, titleHeight)); - currentY -= Math.max(totalHeight, titleHeight); } } } @@ -126,20 +188,94 @@ public class PDFGenerator { } finally { cs.close(); } - // document.save("D:/output.pdf"); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - document.save(baos); - outputStream.flush(); - baos.writeTo(outputStream); - // document.save(outputStream); + // 将PDF保存到ByteArrayOutputStream + document.save(pdfBaos); + +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// document.save(baos); +// outputStream.flush(); +// baos.writeTo(outputStream); + + + } + // 创建ZIP压缩包并将PDF写入输出流 + try (ZipOutputStream zipOut = new ZipOutputStream(outputStream)) { + // 设置最高压缩级别 + zipOut.setLevel(Deflater.BEST_COMPRESSION); + // 创建ZIP条目 + ZipEntry entry = new ZipEntry(entity.getUserName()+entity.getCommunityName()+entity.getBuildingUnit()+entity.getRoomNo()+".pdf"); + zipOut.putNextEntry(entry); + + // 将PDF数据写入ZIP + byte[] pdfBytes = pdfBaos.toByteArray(); + zipOut.write(pdfBytes, 0, pdfBytes.length); + + // 关闭当前条目 + zipOut.closeEntry(); + } + } + + // 加载字体,确保使用正确的文档实例 + private static PDType0Font loadFont(PDDocument document) throws IOException { + // 尝试多个可能的字体路径 + String[] fontPaths = { + "D:\\NotoSansCJK-Regular.ttf", + "C:\\Windows\\Fonts\\simsun.ttc", + "C:\\Windows\\Fonts\\msyh.ttc", + "/System/Library/Fonts/Helvetica.dfont", // macOS + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" // Linux + }; + + for (String path : fontPaths) { + if (Files.exists(Paths.get(path))) { + try { + return PDType0Font.load(document, new File(path)); + } catch (IOException e) { + continue; // 尝试下一个字体文件 + } + } } + + return PDType0Font.load(document, new File("/ttf/NotoSansCJK-Regular.ttf")); } + + // 缓存已处理的图片以避免重复下载和处理 + private static final ConcurrentHashMap imageByteCache = new ConcurrentHashMap<>(); + + + // 预加载图片以避免重复下载 + private static PDImageXObject loadImageFromUrl(PDDocument document, String url) throws IOException { + byte[] imageBytes = imageByteCache.computeIfAbsent(url, key -> { + try { + return new URL(key).openStream().readAllBytes(); + } catch (IOException e) { + return null; // 或者抛出异常 + } + }); + + if (imageBytes == null) { + return null; + } + + return PDImageXObject.createFromByteArray(document, imageBytes, ""); + } + + private static List wrapText(String text, float maxWidth) { List lines = new ArrayList<>(); - int maxCharsPerLine = (int)(maxWidth / (FONT_SIZE * 0.5f)); // 中文字符估算 + if (text == null || text.isEmpty()) return lines; + + // 优化的文本换行逻辑,减少计算量 + int maxCharsPerLine = (int)(maxWidth / (FONT_SIZE * 0.4f)); // 调整字符估算系数 + + if (text.length() <= maxCharsPerLine) { + lines.add(text); + return lines; + } StringBuilder currentLine = new StringBuilder(); - for (String word : text.split("")) { + String[] words = text.split(""); + for (String word : words) { if (currentLine.length() + word.length() <= maxCharsPerLine) { currentLine.append(word); } else { @@ -152,6 +288,24 @@ public class PDFGenerator { } return lines; } +// private static List wrapText(String text, float maxWidth) { +// List lines = new ArrayList<>(); +// int maxCharsPerLine = (int)(maxWidth / (FONT_SIZE * 0.5f)); // 中文字符估算 +// +// StringBuilder currentLine = new StringBuilder(); +// for (String word : text.split("")) { +// if (currentLine.length() + word.length() <= maxCharsPerLine) { +// currentLine.append(word); +// } else { +// lines.add(currentLine.toString()); +// currentLine = new StringBuilder(word); +// } +// } +// if (currentLine.length() > 0) { +// lines.add(currentLine.toString()); +// } +// return lines; +// } private static float calculateTotalHeight(List imageUrls) throws IOException { @@ -263,7 +417,7 @@ public class PDFGenerator { // 绘制标题(垂直居中) float titleY = startY - totalHeight/2 + (titleLines.size() * LINE_HEIGHT)/2; // 绘制第一行标题(顶部) - // float titleY = startY - CELL_PADDING - FONT_SIZE; + // float titleY = startY - CELL_PADDING - FONT_SIZE; cs.beginText(); cs.setFont(font, FONT_SIZE); cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY); @@ -275,19 +429,20 @@ public class PDFGenerator { cs.endText(); // 绘制第一行图片(自动缩放) - PDImageXObject image1 = PDImageXObject.createFromByteArray(doc, - new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image"); - float scale1 = Math.min( - imageAvailableWidth / image1.getWidth(), - singleImageHeight / image1.getHeight() - ); - float scaledHeight1 = image1.getHeight() * scale1; - float scaledWidth1 = image1.getWidth() * scale1; + PDImageXObject image1 = loadImageFromUrl(doc, imageUrls.get(0)); + if (image1 != null) { + float scale1 = Math.min( + imageAvailableWidth / image1.getWidth(), + singleImageHeight / image1.getHeight() + ); + float scaledHeight1 = image1.getHeight() * scale1; + float scaledWidth1 = image1.getWidth() * scale1; - cs.drawImage(image1, - TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, - startY - CELL_PADDING - scaledHeight1, - scaledWidth1, scaledHeight1); + cs.drawImage(image1, + TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, + startY - CELL_PADDING - scaledHeight1, + scaledWidth1, scaledHeight1); + } } else if (imageUrls.size() == 2) { // 双图片模式:改为上下布局 @@ -306,34 +461,37 @@ public class PDFGenerator { cs.endText(); // 绘制第一张图片(上) - PDImageXObject image1 = PDImageXObject.createFromByteArray(doc, - new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image"); - float scale1 = Math.min( - imageAvailableWidth / image1.getWidth(), - imageHeightPer / image1.getHeight() - ); - float scaledHeight1 = image1.getHeight() * scale1; - float scaledWidth1 = image1.getWidth() * scale1; + PDImageXObject image1 = loadImageFromUrl(doc, imageUrls.get(0)); + float scaledHeight1 = 0; + if (image1 != null) { + float scale1 = Math.min( + imageAvailableWidth / image1.getWidth(), + imageHeightPer / image1.getHeight() + ); + scaledHeight1 = image1.getHeight() * scale1; + float scaledWidth1 = image1.getWidth() * scale1; - cs.drawImage(image1, - TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, - startY - CELL_PADDING - scaledHeight1, - scaledWidth1, scaledHeight1); + cs.drawImage(image1, + TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, + startY - CELL_PADDING - scaledHeight1, + scaledWidth1, scaledHeight1); + } // 绘制第二张图片(下) - PDImageXObject image2 = PDImageXObject.createFromByteArray(doc, - new URL(imageUrls.get(1)).openStream().readAllBytes(), "embedded-image"); - float scale2 = Math.min( - imageAvailableWidth / image2.getWidth(), - imageHeightPer / image2.getHeight() - ); - float scaledHeight2 = image2.getHeight() * scale2; - float scaledWidth2 = image2.getWidth() * scale2; + PDImageXObject image2 = loadImageFromUrl(doc, imageUrls.get(1)); + if (image2 != null) { + float scale2 = Math.min( + imageAvailableWidth / image2.getWidth(), + imageHeightPer / image2.getHeight() + ); + float scaledHeight2 = image2.getHeight() * scale2; + float scaledWidth2 = image2.getWidth() * scale2; - cs.drawImage(image2, - TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, - startY - CELL_PADDING - scaledHeight1 - CELL_PADDING - scaledHeight2, - scaledWidth2, scaledHeight2); + cs.drawImage(image2, + TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, + startY - CELL_PADDING - scaledHeight1 - CELL_PADDING - scaledHeight2, + scaledWidth2, scaledHeight2); + } /* // 双图片模式:一行并列显示 float imageWidthPer = (imageAvailableWidth - CELL_PADDING) / 2; @@ -378,8 +536,239 @@ public class PDFGenerator { TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING + imageWidthPer + CELL_PADDING, startY - CELL_PADDING - scaledHeight2, scaledWidth2, scaledHeight2);*/ + }else { + // 多图片模式(超过2张图片):垂直堆叠显示,与双图片模式保持一致的样式 + float imageHeightPer = (imageAvailableHeight - CELL_PADDING * (imageUrls.size() - 1)) / imageUrls.size(); + + // 绘制标题(垂直居中) + float titleY = startY - totalHeight / 2 + (titleLines.size() * LINE_HEIGHT) / 2; + cs.beginText(); + cs.setFont(font, FONT_SIZE); + cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY); + for (String line : titleLines) { + cs.showText(line); + titleY -= LINE_HEIGHT; + cs.newLineAtOffset(0, -LINE_HEIGHT); + } + cs.endText(); + + // 绘制所有图片,采用与单/双图片一致的样式 + float currentY = startY - CELL_PADDING; + for (int i = 0; i < imageUrls.size(); i++) { + PDImageXObject image = loadImageFromUrl(doc, imageUrls.get(i)); + if (image == null) continue; // 跳过加载失败的图片 + + float scale = Math.min( + imageAvailableWidth / image.getWidth(), + imageHeightPer / image.getHeight() + ); + float scaledHeight = image.getHeight() * scale; + float scaledWidth = image.getWidth() * scale; + + // 检查是否需要跨页 + if (currentY - scaledHeight - CELL_PADDING < TABLE_MARGIN) { + // 当前页面空间不足,需要跨页 + // 这里需要在实际应用中处理跨页逻辑 + // 由于在单个drawImageGroup方法中无法直接创建新页面, + // 需要在调用此方法的循环中处理跨页 + break; // 暂停绘制剩余图片,由外部循环处理跨页 + } + + // 绘制当前图片 + cs.drawImage(image, + TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, + currentY - scaledHeight, + scaledWidth, scaledHeight); + + // 更新下一个图片的起始Y坐标 + currentY -= (scaledHeight + CELL_PADDING); + } } } + + +// private static void drawImageGroup(PDPageContentStream cs, PDDocument doc, PDType0Font font, +// List titleLines, List imageUrls, +// float startY, float totalHeight) throws IOException { +// // 绘制单元格边框 +// cs.setLineWidth(0.5f); +// cs.addRect(TABLE_MARGIN, startY - totalHeight, LEFT_COL_WIDTH, totalHeight); +// cs.addRect(TABLE_MARGIN + LEFT_COL_WIDTH, startY - totalHeight, RIGHT_COL_WIDTH, totalHeight); +// cs.stroke(); +// +// // 计算每张图片的可用高度(根据图片数量动态调整) +// float imageAvailableHeight = totalHeight - 2 * CELL_PADDING; +// float imageAvailableWidth = RIGHT_COL_WIDTH - 2 * CELL_PADDING; +// +// if (imageUrls.size() == 1) { +// // 单图片模式:一页两行,每行占半页高度 +// float singleImageHeight = imageAvailableHeight; +// // 绘制标题(垂直居中) +// float titleY = startY - totalHeight/2 + (titleLines.size() * LINE_HEIGHT)/2; +// // 绘制第一行标题(顶部) +// // float titleY = startY - CELL_PADDING - FONT_SIZE; +// cs.beginText(); +// cs.setFont(font, FONT_SIZE); +// cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY); +// for (String line : titleLines) { +// cs.showText(line); +// titleY -= LINE_HEIGHT; +// cs.newLineAtOffset(0, -LINE_HEIGHT); +// } +// cs.endText(); +// +// // 绘制第一行图片(自动缩放) +// PDImageXObject image1 = PDImageXObject.createFromByteArray(doc, +// new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image"); +// float scale1 = Math.min( +// imageAvailableWidth / image1.getWidth(), +// singleImageHeight / image1.getHeight() +// ); +// float scaledHeight1 = image1.getHeight() * scale1; +// float scaledWidth1 = image1.getWidth() * scale1; +// +// cs.drawImage(image1, +// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, +// startY - CELL_PADDING - scaledHeight1, +// scaledWidth1, scaledHeight1); +// +// } else if (imageUrls.size() == 2) { +// // 双图片模式:改为上下布局 +// float imageHeightPer = (imageAvailableHeight - CELL_PADDING) / 2; +// +// // 绘制标题(垂直居中) +// float titleY = startY - totalHeight/2 + (titleLines.size() * LINE_HEIGHT)/2; +// cs.beginText(); +// cs.setFont(font, FONT_SIZE); +// cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY); +// for (String line : titleLines) { +// cs.showText(line); +// titleY -= LINE_HEIGHT; +// cs.newLineAtOffset(0, -LINE_HEIGHT); +// } +// cs.endText(); +// +// // 绘制第一张图片(上) +// PDImageXObject image1 = PDImageXObject.createFromByteArray(doc, +// new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image"); +// float scale1 = Math.min( +// imageAvailableWidth / image1.getWidth(), +// imageHeightPer / image1.getHeight() +// ); +// float scaledHeight1 = image1.getHeight() * scale1; +// float scaledWidth1 = image1.getWidth() * scale1; +// +// cs.drawImage(image1, +// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, +// startY - CELL_PADDING - scaledHeight1, +// scaledWidth1, scaledHeight1); +// +// // 绘制第二张图片(下) +// PDImageXObject image2 = PDImageXObject.createFromByteArray(doc, +// new URL(imageUrls.get(1)).openStream().readAllBytes(), "embedded-image"); +// float scale2 = Math.min( +// imageAvailableWidth / image2.getWidth(), +// imageHeightPer / image2.getHeight() +// ); +// float scaledHeight2 = image2.getHeight() * scale2; +// float scaledWidth2 = image2.getWidth() * scale2; +// +// cs.drawImage(image2, +// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, +// startY - CELL_PADDING - scaledHeight1 - CELL_PADDING - scaledHeight2, +// scaledWidth2, scaledHeight2); +// /* // 双图片模式:一行并列显示 +// float imageWidthPer = (imageAvailableWidth - CELL_PADDING) / 2; +// +// // 绘制标题(垂直居中) +// float titleY = startY - totalHeight/2 + (titleLines.size() * LINE_HEIGHT)/2; +// cs.beginText(); +// cs.setFont(font, FONT_SIZE); +// cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY); +// for (String line : titleLines) { +// cs.showText(line); +// titleY -= LINE_HEIGHT; +// cs.newLineAtOffset(0, -LINE_HEIGHT); +// } +// cs.endText(); +// +// // 绘制第一张图片(左) +// PDImageXObject image1 = PDImageXObject.createFromByteArray(doc, +// new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image"); +// float scale1 = Math.min( +// imageWidthPer / image1.getWidth(), +// imageAvailableHeight / image1.getHeight() +// ); +// float scaledHeight1 = image1.getHeight() * scale1; +// float scaledWidth1 = image1.getWidth() * scale1; +// +// cs.drawImage(image1, +// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, +// startY - CELL_PADDING - scaledHeight1, +// scaledWidth1, scaledHeight1); +// +// // 绘制第二张图片(右) +// PDImageXObject image2 = PDImageXObject.createFromByteArray(doc, +// new URL(imageUrls.get(1)).openStream().readAllBytes(), "embedded-image"); +// float scale2 = Math.min( +// imageWidthPer / image2.getWidth(), +// imageAvailableHeight / image2.getHeight() +// ); +// float scaledHeight2 = image2.getHeight() * scale2; +// float scaledWidth2 = image2.getWidth() * scale2; +// +// cs.drawImage(image2, +// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING + imageWidthPer + CELL_PADDING, +// startY - CELL_PADDING - scaledHeight2, +// scaledWidth2, scaledHeight2);*/ +// }else { +// // 多图片模式(超过2张图片):垂直堆叠显示,与双图片模式保持一致的样式 +// float imageHeightPer = (imageAvailableHeight - CELL_PADDING * (imageUrls.size() - 1)) / imageUrls.size(); +// +// // 绘制标题(垂直居中) +// float titleY = startY - totalHeight / 2 + (titleLines.size() * LINE_HEIGHT) / 2; +// cs.beginText(); +// cs.setFont(font, FONT_SIZE); +// cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY); +// for (String line : titleLines) { +// cs.showText(line); +// titleY -= LINE_HEIGHT; +// cs.newLineAtOffset(0, -LINE_HEIGHT); +// } +// cs.endText(); +// +// // 绘制所有图片,采用与单/双图片一致的样式 +// float currentY = startY - CELL_PADDING; +// for (int i = 0; i < imageUrls.size(); i++) { +// PDImageXObject image = PDImageXObject.createFromByteArray(doc, +// new URL(imageUrls.get(i)).openStream().readAllBytes(), "embedded-image"); +// float scale = Math.min( +// imageAvailableWidth / image.getWidth(), +// imageHeightPer / image.getHeight() +// ); +// float scaledHeight = image.getHeight() * scale; +// float scaledWidth = image.getWidth() * scale; +// +// // 检查是否需要跨页 +// if (currentY - scaledHeight - CELL_PADDING < TABLE_MARGIN) { +// // 当前页面空间不足,需要跨页 +// // 这里需要在实际应用中处理跨页逻辑 +// // 由于在单个drawImageGroup方法中无法直接创建新页面, +// // 需要在调用此方法的循环中处理跨页 +// break; // 暂停绘制剩余图片,由外部循环处理跨页 +// } +// +// // 绘制当前图片 +// cs.drawImage(image, +// TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, +// currentY - scaledHeight, +// scaledWidth, scaledHeight); +// +// // 更新下一个图片的起始Y坐标 +// currentY -= (scaledHeight + CELL_PADDING); +// } +// } +// } private static float calculateAdjustedImagesHeight(PDDocument doc, List imageUrls, float maxWidth, float maxHeight) throws IOException { if (imageUrls.isEmpty()) return 0; @@ -388,9 +777,23 @@ public class PDFGenerator { if (imageUrls.size() == 1) { // 单图片模式:占用半页高度 return maxHeight / 2; - } else { + } else if(imageUrls.size() == 2){ // 双图片模式:整页高度 return maxHeight; + }else { + // 多图片模式:根据图片数量动态计算高度 + // 每张图片预留最小高度,防止页面过长 + float minImageHeight = 50f; // 每张图片最小高度 + float imageSpacing = 5f; // 图片间距 + float totalSpacing = (imageUrls.size() - 1) * imageSpacing; + float estimatedHeight = (minImageHeight * imageUrls.size()) + totalSpacing; + + // 如果估算高度超过页面可用高度,则返回最大高度,需要分页处理 + if (estimatedHeight > maxHeight) { + return maxHeight; + } + + return estimatedHeight; } } @@ -652,13 +1055,13 @@ public class PDFGenerator { } - private static float addImageRow(PDPageContentStream cs, PDDocument doc, PDType0Font font, String title, String imageUrl, float startX, float startY) throws IOException { // 加载图片 - PDImageXObject image = PDImageXObject.createFromByteArray(doc, - new URL(imageUrl).openStream().readAllBytes(), "embedded-image"); + PDImageXObject image = loadImageFromUrl(doc, imageUrl); + if (image == null) return startY; // 如果图片加载失败,返回原位置 + // 动态计算尺寸 float imageWidth = RIGHT_COL_WIDTH - 10; float scale = imageWidth / image.getWidth(); @@ -725,6 +1128,79 @@ public class PDFGenerator { return startY - dynamicRowHeight - 10;*/ } + +// private static float addImageRow(PDPageContentStream cs, PDDocument doc, +// PDType0Font font, String title, String imageUrl, +// float startX, float startY) throws IOException { +// // 加载图片 +// PDImageXObject image = PDImageXObject.createFromByteArray(doc, +// new URL(imageUrl).openStream().readAllBytes(), "embedded-image"); +// // 动态计算尺寸 +// float imageWidth = RIGHT_COL_WIDTH - 10; +// float scale = imageWidth / image.getWidth(); +// float imageHeight = image.getHeight() * scale; +// float rowHeight = imageHeight + 20; +// +// // 绘制单元格边框 +// cs.setLineWidth(0.5f); +// cs.addRect(startX, startY - rowHeight, LEFT_COL_WIDTH, rowHeight); +// cs.addRect(startX + LEFT_COL_WIDTH, startY - rowHeight, RIGHT_COL_WIDTH, rowHeight); +// cs.stroke(); +// +// // 垂直居中文本(改进算法) +// float textY = startY - rowHeight/2 + FONT_SIZE/2; +// //String title = "安装完成探测器工作图片\n检测时间:2025-08-14"; +// +// /* if(title.contains("\n")){ +// String[] lines = title.split("\n"); +// textY = startY - rowHeight/2 + FONT_SIZE/2 + (lines.length-1)*FONT_SIZE/2; +// }*/ +// drawCenteredText(cs, font, FONT_SIZE, title, +// startX + LEFT_COL_WIDTH/2, textY); +// +// // 绘制图片(带5pt边距) +// cs.drawImage(image, +// startX + LEFT_COL_WIDTH + 5, +// startY - rowHeight + 10, +// imageWidth, imageHeight); +// +// return startY - rowHeight - 10; +// /*// 计算图片尺寸(保持比例) +// float imageWidth = RIGHT_COL_WIDTH - 10; +// float scale = imageWidth / image.getWidth(); +// float imageHeight = image.getHeight() * scale; +// +// // 动态计算行高(图片高度+上下边距20) +// float dynamicRowHeight = imageHeight + 20; +// +// // 绘制左侧标题单元格 +// cs.setLineWidth(0.5f); +// cs.addRect(TABLE_MARGIN, startY - dynamicRowHeight, LEFT_COL_WIDTH, dynamicRowHeight); +// +// // 绘制右侧图片单元格(高度与左侧同步) +// cs.addRect(TABLE_MARGIN + LEFT_COL_WIDTH, startY - dynamicRowHeight, RIGHT_COL_WIDTH, dynamicRowHeight); +// cs.stroke(); +// +// // 居中绘制标题文本 +// float titleWidth = font.getStringWidth(title) / 1000 * FONT_SIZE; +// float titleX = TABLE_MARGIN + (LEFT_COL_WIDTH - titleWidth) / 2; +// float titleY = startY - (dynamicRowHeight / 2) - (FONT_SIZE / 3); +// +// cs.beginText(); +// cs.setFont(font, FONT_SIZE); +// cs.newLineAtOffset(titleX, titleY); +// cs.showText(title); +// cs.endText(); +// +// // 居中绘制图片 +// float imageX = TABLE_MARGIN + LEFT_COL_WIDTH + 5; +// float imageY = startY - dynamicRowHeight + 10; +// cs.drawImage(image, imageX, imageY, imageWidth, imageHeight); +// +// // 返回新的Y坐标(当前位置减去行高和间距) +// return startY - dynamicRowHeight - 10;*/ +// } + /* private static float addImageRow(PDPageContentStream cs, PDDocument doc, PDType0Font font, String title, String imageUrl, float x, float y) throws IOException { // 绘制标题行 diff --git a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/bo/device/DeviceQueryBo.java b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/bo/device/DeviceQueryBo.java index ce9c22e..831e71d 100644 --- a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/bo/device/DeviceQueryBo.java +++ b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/bo/device/DeviceQueryBo.java @@ -90,5 +90,8 @@ public class DeviceQueryBo extends BaseDto { private String endTime; private String config; + //设备状态0正常1故障2报警3离线 + private Integer deviceStatus; + } diff --git a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInfoExpordVo.java b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInfoExpordVo.java index a2aaebd..2c8ac97 100644 --- a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInfoExpordVo.java +++ b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInfoExpordVo.java @@ -56,4 +56,7 @@ public class DeviceInfoExpordVo implements Serializable { private Long tenantId; @ExcelProperty(value = "设备区域") private Long deptAreaId; + + @ExcelProperty(value = "设备位置") + private String site; } diff --git a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInfoImportVo.java b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInfoImportVo.java index c17575e..731e0de 100644 --- a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInfoImportVo.java +++ b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInfoImportVo.java @@ -57,4 +57,6 @@ public class DeviceInfoImportVo implements Serializable { private Long tenantId; @ExcelProperty(value = "设备区域") private Long deptAreaId; + @ExcelProperty(value = "设备位置") + private String site; } diff --git a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInstallInfoExpordVo.java b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInstallInfoExpordVo.java index 51aaa93..057cf5e 100644 --- a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInstallInfoExpordVo.java +++ b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInstallInfoExpordVo.java @@ -61,6 +61,10 @@ public class DeviceInstallInfoExpordVo implements Serializable { // @ApiModelProperty(value = "切断阀编号") @ExcelProperty(value = "地址") private String site; + @ExcelProperty(value = "切断阀编号") + private String shutValueNumber; + @ExcelProperty(value = "报警器编号") + private String deviceName; // @ApiModelProperty(value = "燃气表号") @ExcelProperty(value = "表号") private String gasMeterNumber; diff --git a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInstallInfoVo.java b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInstallInfoVo.java index 4120b1a..e12d8f0 100644 --- a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInstallInfoVo.java +++ b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/dto/vo/deviceinfo/DeviceInstallInfoVo.java @@ -64,6 +64,10 @@ public class DeviceInstallInfoVo implements Serializable { // @ApiModelProperty(value = "切断阀编号") @ExcelProperty(value = "地址") private String site; + @ExcelProperty(value = "切断阀编号") + private String shutValueNumber; + @ExcelProperty(value = "报警器编号") + private String deviceName; // @ApiModelProperty(value = "燃气表号") @ExcelProperty(value = "表号") private String gasMeterNumber; diff --git a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/service/impl/DeviceInstallInfoServiceImpl.java b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/service/impl/DeviceInstallInfoServiceImpl.java index 79d5c95..5a1c764 100644 --- a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/service/impl/DeviceInstallInfoServiceImpl.java +++ b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/service/impl/DeviceInstallInfoServiceImpl.java @@ -223,6 +223,8 @@ public class DeviceInstallInfoServiceImpl implements IDeviceInstallInfoService { DeviceInstallInfo deviceInfo = MapstructUtils.convert(tbDeviceInfo, DeviceInstallInfo.class); DeviceInstallInfoVo deviceInstallInfoVo = MapstructUtils.convert(deviceInfo, DeviceInstallInfoVo.class); deviceInstallInfoVo.setXuhao(i); + deviceInstallInfoVo.setShutValueNumber(deviceInfo.getShutValueNumber()); + deviceInstallInfoVo.setDeviceName(deviceInfo.getDeviceName()); deviceInstallInfoVo.setSite(tbDeviceInfo.getCommunityName() + tbDeviceInfo.getBuildingUnit() + tbDeviceInfo.getRoomNo()); deviceInstallInfoVo.setManufacturer("天津费加罗"); deviceInstallInfoVo.setPosition("成功"); @@ -274,4 +276,4 @@ public class DeviceInstallInfoServiceImpl implements IDeviceInstallInfoService { return predicate.build(); }*/ -} \ No newline at end of file +} diff --git a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/service/impl/DeviceManagerServiceImpl.java b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/service/impl/DeviceManagerServiceImpl.java index 8584239..942ef7c 100644 --- a/iot-module/iot-manager/src/main/java/cc/iotkit/manager/service/impl/DeviceManagerServiceImpl.java +++ b/iot-module/iot-manager/src/main/java/cc/iotkit/manager/service/impl/DeviceManagerServiceImpl.java @@ -148,13 +148,14 @@ public class DeviceManagerServiceImpl implements IDeviceManagerService { String name = query.getName(); String deviceName = query.getDeviceName(); String pk = query.getProductKey(); + Integer deviceStatus = query.getDeviceStatus(); //关键字查询 String keyword = query.getKeyword(); String group = query.getGroup(); Boolean online = query.getOnline(); Long areaDepeId = query.getDeptAreaId(); Paging result = MapstructUtils.convert(deviceInfoData.findByConditions(name, uid, subUid, pk, group, - online, keyword, pageRequest.getPageNum(), pageRequest.getPageSize(), areaDepeId, query.getStartTime(), query.getEndTime(), deviceName), DeviceInfoVo.class); + online, keyword, pageRequest.getPageNum(), pageRequest.getPageSize(), areaDepeId, query.getStartTime(), query.getEndTime(), deviceName,deviceStatus), DeviceInfoVo.class); List categorys = categoryData.findAll(); for (DeviceInfoVo row : result.getRows()) { if (ObjectUtil.isNotEmpty(row.getProductKey())) { diff --git a/iot-module/iot-plugin/iot-plugin-main/src/main/java/cc/iotkit/plugin/main/ThingServiceImpl.java b/iot-module/iot-plugin/iot-plugin-main/src/main/java/cc/iotkit/plugin/main/ThingServiceImpl.java index a030638..dc826ab 100644 --- a/iot-module/iot-plugin/iot-plugin-main/src/main/java/cc/iotkit/plugin/main/ThingServiceImpl.java +++ b/iot-module/iot-plugin/iot-plugin-main/src/main/java/cc/iotkit/plugin/main/ThingServiceImpl.java @@ -62,6 +62,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import javax.annotation.security.PermitAll; import java.time.LocalDateTime; @@ -163,6 +164,7 @@ public class ThingServiceImpl implements IThingService { switch (type) { case REGISTER: //设备注册 + // 根据 String deviceId = registerDevice(device, (DeviceRegister) action, null); deviceUpdateLastTime(deviceId, lastTime); @@ -381,7 +383,11 @@ public class ThingServiceImpl implements IThingService { List rules = ruleMessageHandler.processMessage1(thingModelMessage); for (Rule rule : rules) { //这里也要加增加启用未启用条件查询 + List config1 = iAlertConfigData.findByRuleInfoIdAndDeviceName(rule.getId(),device.getDeviceName()); + if(CollectionUtils.isEmpty(config1)){ + config1 = iAlertConfigData.findByRuleInfoIdAndDeviceName(rule.getId(),null); + } for (int i = 0; i < config1.size(); i++) { idAlertRecordData.save(AlertRecord.builder() .level(config1.get(i).getLevel()) @@ -974,7 +980,7 @@ public class ThingServiceImpl implements IThingService { if (productKey != null && device.getProductKey() != null && !device.getProductKey().equals(productKey)) { //防止设备变更了产品key - device.setProductKey(productKey); +// device.setProductKey(productKey); // device.setModel(register.getModel()); deviceInfoData.save(device); } diff --git a/iot-module/iot-system/src/main/java/cc/iotkit/system/service/impl/SysTenantServiceImpl.java b/iot-module/iot-system/src/main/java/cc/iotkit/system/service/impl/SysTenantServiceImpl.java index c18adde..7be39bb 100644 --- a/iot-module/iot-system/src/main/java/cc/iotkit/system/service/impl/SysTenantServiceImpl.java +++ b/iot-module/iot-system/src/main/java/cc/iotkit/system/service/impl/SysTenantServiceImpl.java @@ -362,7 +362,8 @@ public class SysTenantServiceImpl implements ISysTenantService { //删除角色 SysRole querySysRole=new SysRole(); querySysRole.setTenantId(tenantId); - List roles =sysRoleData.selectRoleList(querySysRole); +// List roles =sysRoleData.selectRoleList(querySysRole); + List roles =sysRoleData.selectByTenantId(tenantId); sysRoleData.deleteByIds(roles.stream().map(SysRole::getId).collect(Collectors.toList())); //删除部门 List lsDepts = sysDeptData.findByTenantId(tenantId); diff --git a/iot-starter/src/main/resources/application-dev.yml b/iot-starter/src/main/resources/application-dev.yml index 48da79b..aa528ba 100644 --- a/iot-starter/src/main/resources/application-dev.yml +++ b/iot-starter/src/main/resources/application-dev.yml @@ -52,6 +52,7 @@ spring: hibernate: format_sql: false use_sql_comments: false # 输出SQL注释 + generate_statistics: false # 关闭统计信息 session_factory: # statement_inspector: com.example.TenantSQLInterceptor # dialect: org.hibernate.dialect.MySQL5Dialect