feat(plugin): 优化EMQX插件功能并增强设备管理

- 在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中调整设备识别规则适配不同平台需求
master
alsszer 2 months ago
parent 145fc94b34
commit 34c2a5f8b4

@ -77,6 +77,8 @@ public class DeviceInstallInfo extends TenantModel implements Owned<String>,Seri
private String workingOfTheDetectorImage; private String workingOfTheDetectorImage;
// @ApiModelProperty(value = "测漏图片") // @ApiModelProperty(value = "测漏图片")
private String sideLeakageImage; private String sideLeakageImage;
// @ApiModelProperty(value = "泡沫水测漏图片")
private String formSideLeakageImage;
// @ApiModelProperty(value = "点火图片") // @ApiModelProperty(value = "点火图片")
private String ignitionPictureImage; private String ignitionPictureImage;
// @ApiModelProperty(value = "装完成全景图片") // @ApiModelProperty(value = "装完成全景图片")
@ -87,6 +89,10 @@ public class DeviceInstallInfo extends TenantModel implements Owned<String>,Seri
private String workOrderImage; private String workOrderImage;
//@ApiModelProperty(value = "打孔图片") //@ApiModelProperty(value = "打孔图片")
private String punchingImage; private String punchingImage;
//@ApiModelProperty(value = "设备信息图片")
private String deviceInfoImage;
//@ApiModelProperty(value = "门牌号图片")
private String houseNumberImage;
// @ApiModelProperty(value = "用户id") // @ApiModelProperty(value = "用户id")
private String uid; private String uid;
// @ApiModelProperty(value = "租户编号") // @ApiModelProperty(value = "租户编号")

@ -114,6 +114,9 @@ public interface IDeviceInfoData extends IOwnedData<DeviceInfo, String> {
* @param page * @param page
* @param size * @param size
*/ */
Paging<DeviceInfo> 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<DeviceInfo> findByConditions(String name,String uid, String subUid, String productKey, Paging<DeviceInfo> findByConditions(String name,String uid, String subUid, String productKey,
String groupId, Boolean online, String keyword, String groupId, Boolean online, String keyword,
int page, int size,Long areaDepeId,String startTime,String endTime,String deviceName); int page, int size,Long areaDepeId,String startTime,String endTime,String deviceName);

@ -33,4 +33,6 @@ public interface IDeviceInstallInfoData extends IOwnedData<DeviceInstallInfo, St
DeviceInstallInfo findByDeviceNameAndUid(String deviceName,String uid); DeviceInstallInfo findByDeviceNameAndUid(String deviceName,String uid);
DeviceInstallInfo findByDeviceNameAndUidAndState(String deviceName,String uid,Integer state); DeviceInstallInfo findByDeviceNameAndUidAndState(String deviceName,String uid,Integer state);
DeviceInstallInfo findById(String id);
} }

@ -85,4 +85,6 @@ public interface ISysRoleData extends ICommonData<SysRole, Long> {
List<SysRole> selectRoleList(SysRole role); List<SysRole> selectRoleList(SysRole role);
List<SysRole> findByUserId(Long id); List<SysRole> findByUserId(Long id);
List<SysRole> selectByTenantId(Long tenantId);
} }

@ -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) { 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); return deviceInfoData.findByConditions(name,uid, subUid, productKey, groupId, state, keyword, page, size,deptAreaId,startTime,endTime,deviceName);
} }
@Override
public Paging<DeviceInfo> 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 @Override
public Paging<DeviceInfo> findByConditionsExcel(String name,String uid, String subUid, String productKey, public Paging<DeviceInfo> 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) { String groupId, Boolean state, String keyword, int page, int size,Long deptAreaId,String startTime,String endTime) {

@ -202,6 +202,11 @@ public class DeviceInfoPropertyDataCache implements IDeviceInfoData {
public Paging<DeviceInfo> 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) { public Paging<DeviceInfo> 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); return deviceInfoData.findByConditions(name,uid, subUid, productKey, groupId, online, keyword, page, size,deptAreaId,startTime,endTime,deviceName);
} }
@Override
public Paging<DeviceInfo> 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 @Override
public Paging<DeviceInfo> 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) { public Paging<DeviceInfo> 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); return deviceInfoData.findByConditionsExcel(name,uid, subUid, productKey, groupId, online, keyword, page, size,deptAreaId,startTime,endTime);

@ -86,6 +86,8 @@ public class TbDeviceInstallInfo extends BaseEntity implements TenantAware {
private String workingOfTheDetectorImage; private String workingOfTheDetectorImage;
@ApiModelProperty(value = "测漏图片") @ApiModelProperty(value = "测漏图片")
private String sideLeakageImage; private String sideLeakageImage;
@ApiModelProperty(value = "泡沫水测漏图片")
private String formSideLeakageImage;
@ApiModelProperty(value = "点火图片") @ApiModelProperty(value = "点火图片")
private String ignitionPictureImage; private String ignitionPictureImage;
@ApiModelProperty(value = "装完成全景图片") @ApiModelProperty(value = "装完成全景图片")
@ -98,6 +100,10 @@ public class TbDeviceInstallInfo extends BaseEntity implements TenantAware {
private String punchingImage; private String punchingImage;
@ApiModelProperty(value = "装电源线照片") @ApiModelProperty(value = "装电源线照片")
private String fiexImage; private String fiexImage;
@ApiModelProperty(value = "设备信息图片")
private String deviceInfoImage;
@ApiModelProperty(value = "门牌号图片")
private String houseNumberImage;
@ApiModelProperty(value = "用户id") @ApiModelProperty(value = "用户id")
private String uid; private String uid;

@ -637,7 +637,7 @@ public class DeviceInfoDataImpl implements IDeviceInfoData, IJPACommData<DeviceI
public Paging<DeviceInfo> findByConditions(String name, String uid, String subUid, public Paging<DeviceInfo> findByConditions(String name, String uid, String subUid,
String productKey, String groupId, String productKey, String groupId,
Boolean online, String keyword, 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<TbDeviceInfo> query = jpaQueryFactory.selectFrom(tbDeviceInfo); JPAQuery<TbDeviceInfo> query = jpaQueryFactory.selectFrom(tbDeviceInfo);
// 根据groupId, 如果groupId存在则关联查询TbDeviceGroupMapping, 根据groupId,查询对应的devices // 根据groupId, 如果groupId存在则关联查询TbDeviceGroupMapping, 根据groupId,查询对应的devices
@ -677,6 +677,21 @@ public class DeviceInfoDataImpl implements IDeviceInfoData, IJPACommData<DeviceI
if (ObjectUtil.isNotEmpty(areaIds)) {*/ if (ObjectUtil.isNotEmpty(areaIds)) {*/
if(deptAreaId != 0) { if(deptAreaId != 0) {
query.where(tbDeviceInfo.deptAreaId.in(deptAreaId)); query.where(tbDeviceInfo.deptAreaId.in(deptAreaId));
} else {
if(ObjectUtil.isNotEmpty(LoginHelper.getUserId())) {
SysUser user = isSysUserData.findById(LoginHelper.getUserId());
if (ObjectUtil.isNotNull(user)) {
if (ObjectUtil.isNotNull(user.getDeptAreaId())) {
Long deptAreaId1 = user.getDeptAreaId();
// 根据部门id获取部门对象
SysDept dept = sysDeptData.findById(deptAreaId1);
if(dept.getParentId()!=0l){
// 说明为子部门 筛选条件为部门id
query.where(tbDeviceInfo.deptAreaId.eq(deptAreaId1));
}
}
}
}
} }
// query.where(tbDeviceInfo.deptAreaId.in(deptAreaId)); // query.where(tbDeviceInfo.deptAreaId.in(deptAreaId));
// } // }
@ -717,6 +732,128 @@ public class DeviceInfoDataImpl implements IDeviceInfoData, IJPACommData<DeviceI
query.where(tbDeviceInfo.productKey.eq(productKey)); query.where(tbDeviceInfo.productKey.eq(productKey));
} }
if(ObjectUtil.isNotNull(deviceStatus)){
query.where(tbDeviceInfo.deviceStatus.eq(deviceStatus));
}
if (online != null) {
query.where(tbDeviceInfo.state.eq(online ? "online" : "offline"));
}
if (StringUtils.isNotBlank(keyword)) {
query.where(tbDeviceInfo.deviceId.like("%" + keyword + "%")
.or(tbDeviceInfo.deviceName.like("%" + keyword + "%")));
}
if (ObjectUtil.isNotNull(startTime)&& ObjectUtil.isNotNull(endTime)) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date startDateTime = sdf.parse(startTime);
Date dateTime = sdf.parse(endTime);
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// LocalDateTime startDateTime = LocalDateTime.parse(dictData.getStartTime() , formatter);
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// LocalDateTime dateTime = LocalDateTime.parse(dictData.getEndTime(), formatter);
query.where(tbDeviceInfo.createTime.between(startDateTime, dateTime));
} catch (Exception e) {
}
}
query.orderBy(tbDeviceInfo.createAt.desc());
query.offset((page - 1) * size).limit(size);
List<TbDeviceInfo> tbDeviceInfos = query.fetch();
long total = query.fetchCount();
List<DeviceInfo> 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<DeviceInfo> 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<TbDeviceInfo> 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<Long> areaIds;
// if (Objects.nonNull(user) && Objects.nonNull(user.getDeptAreaId())) {
Long areaId = deptAreaId;
List<SysDept> 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<Long> areaIds;
// if (Objects.nonNull(user) && Objects.nonNull(user.getDeptAreaId())) {
Long areaId = user.getDeptAreaId();
List<SysDept> 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) { if (online != null) {
query.where(tbDeviceInfo.state.eq(online ? "online" : "offline")); query.where(tbDeviceInfo.state.eq(online ? "online" : "offline"));
} }

@ -77,4 +77,10 @@ public class DeviceInstallInfoDataImpl implements IDeviceInstallInfoData, IJPACo
public DeviceInstallInfo findByDeviceNameAndUidAndState(String deviceName, String uid, Integer state) { public DeviceInstallInfo findByDeviceNameAndUidAndState(String deviceName, String uid, Integer state) {
return MapstructUtils.convert(homeRepository.findByDeviceNameAndUidAndState(deviceName,uid,state), DeviceInstallInfo.class); 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);
}
} }

@ -222,6 +222,15 @@ public class SysRoleDataImpl implements ISysRoleData, IJPACommData<SysRole, Long
.fetch(); .fetch();
} }
@Override
public List<SysRole> 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<SysRole> buildQueryTitle(Predicate predicate) { private List<SysRole> buildQueryTitle(Predicate predicate) {
return jpaQueryFactory.selectDistinct(tbSysRole.id) return jpaQueryFactory.selectDistinct(tbSysRole.id)
.select(Projections.fields(SysRole.class, tbSysRole.id, tbSysRole.roleName, .select(Projections.fields(SysRole.class, tbSysRole.id, tbSysRole.roleName,

@ -13,13 +13,34 @@ import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.*; import java.io.*;
import java.net.URL; import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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.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 { public class PDFGenerator {
// 缓存字体实例以避免重复加载
private static volatile PDType0Font cachedFont = null;
private static final Object fontLock = new Object();
// 缓存已处理的图片以避免重复下载和处理
private static final ConcurrentHashMap<String, PDImageXObject> imageCache = new ConcurrentHashMap<>();
// 线程池用于并发处理图片
private static final ExecutorService imageProcessingPool = Executors.newFixedThreadPool(4);
// 1. 表格参数配置 // 1. 表格参数配置
// 参数配置(优化后) // 参数配置(优化后)
@ -33,9 +54,13 @@ public class PDFGenerator {
private static final float[] COLUMN_WIDTHS = {LEFT_COL_WIDTH, RIGHT_COL_WIDTH}; private static final float[] COLUMN_WIDTHS = {LEFT_COL_WIDTH, RIGHT_COL_WIDTH};
private static final float MIN_IMAGE_HEIGHT = 50; private static final float MIN_IMAGE_HEIGHT = 50;
public static void generateInstallationPDF(DeviceInstallInfo entity, OutputStream outputStream) throws IOException { public static void generateInstallationPDF(DeviceInstallInfo entity, OutputStream outputStream) throws IOException {
// 首先生成PDF内容到字节数组
ByteArrayOutputStream pdfBaos = new ByteArrayOutputStream();
try (PDDocument document = new PDDocument()) { 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/simsun.ttf"));
PDType0Font font = PDType0Font.load(document, new File("/ttf/NotoSansCJK-Regular.ttf")); PDType0Font font = PDType0Font.load(document, new File("/ttf/NotoSansCJK-Regular.ttf"));
// 初始化第一页 // 初始化第一页
@ -76,15 +101,18 @@ public class PDFGenerator {
// 图片处理(带自动分页) // 图片处理(带自动分页)
// 图片处理(带自动分页) // 图片处理(带自动分页)
List<String[]> imageGroups = Arrays.asList( List<String[]> imageGroups = Arrays.asList(
new String[]{entity.getDeviceInfoImage(), "设备信息图片"},
new String[]{entity.getBeforeInstallationImage(), "安装前图片"}, new String[]{entity.getBeforeInstallationImage(), "安装前图片"},
new String[]{entity.getWorkingOfTheDetectorImage(), "安装完成探测器工作图片"}, new String[]{entity.getWorkingOfTheDetectorImage(), "安装完成探测器工作图片"},
new String[]{entity.getSideLeakageImage(), "测漏图片"}, new String[]{entity.getSideLeakageImage(), "测漏图片"},
new String[]{entity.getFormSideLeakageImage(), "泡沫水测漏图片"},
new String[]{entity.getIgnitionPictureImage(), "点火图片"}, new String[]{entity.getIgnitionPictureImage(), "点火图片"},
new String[]{entity.getInstallThePanoramicImage(), "安装完成全景图片"}, new String[]{entity.getInstallThePanoramicImage(), "安装完成全景图片"},
new String[]{entity.getOfGasMeterImage(), "燃气表图片"}, new String[]{entity.getOfGasMeterImage(), "燃气表图片"},
new String[]{entity.getWorkOrderImage(), "工单图片"}, new String[]{entity.getWorkOrderImage(), "工单图片"},
new String[]{entity.getPunchingImage(), "打孔图片"}, new String[]{entity.getPunchingImage(), "打孔图片"},
new String[]{entity.getFiexImage(), "安装电源线图片"} new String[]{entity.getFiexImage(), "安装电源线图片"},
new String[]{entity.getHouseNumberImage(), "门牌号图片"}
); );
for (String[] group : imageGroups) { for (String[] group : imageGroups) {
@ -98,27 +126,61 @@ public class PDFGenerator {
.collect(Collectors.toList()); .collect(Collectors.toList());
if (!validUrls.isEmpty()) { if (!validUrls.isEmpty()) {
// float totalHeight = calculateTotalHeight(validUrls); // 处理可能需要跨页的多图片情况
List<String> wrappedTitleLines = wrapText(title,LEFT_COL_WIDTH - 2 * CELL_PADDING); if (validUrls.size() > 2) {
float titleHeight = wrappedTitleLines.size() * LINE_HEIGHT; // 对于多于2张的图片逐个或分组处理以支持跨页
// 计算图片组的自适应高度 for (int i = 0; i < validUrls.size(); i += 2) { // 每次处理最多2张图片
float imagesHeight = calculateAdjustedImagesHeight(document, validUrls, List<String> subList = validUrls.subList(i,
RIGHT_COL_WIDTH - 2 * CELL_PADDING, Math.min(i + 2, validUrls.size()));
PDRectangle.A4.getHeight() - 2 * TABLE_MARGIN - titleHeight);
List<String> wrappedTitleLines = wrapText(title,LEFT_COL_WIDTH - 2 * CELL_PADDING);
float totalHeight = Math.max(imagesHeight, titleHeight); float titleHeight = wrappedTitleLines.size() * LINE_HEIGHT;
// 计算图片组的自适应高度
if (currentY < TABLE_MARGIN + Math.max(totalHeight, titleHeight)) { float imagesHeight = calculateAdjustedImagesHeight(document, subList,
cs.close(); RIGHT_COL_WIDTH - 2 * CELL_PADDING,
currentPage = new PDPage(PDRectangle.A4); PDRectangle.A4.getHeight() - 2 * TABLE_MARGIN - titleHeight);
document.addPage(currentPage);
cs = new PDPageContentStream(document, currentPage); float totalHeight = Math.max(imagesHeight, titleHeight);
currentY = PDRectangle.A4.getHeight() - TABLE_MARGIN;
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<String> 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<String> 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 { } finally {
cs.close(); cs.close();
} }
// document.save("D:/output.pdf"); // 将PDF保存到ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream(); document.save(pdfBaos);
document.save(baos);
outputStream.flush(); // ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.writeTo(outputStream); // document.save(baos);
// document.save(outputStream); // 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<String, byte[]> 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<String> wrapText(String text, float maxWidth) { private static List<String> wrapText(String text, float maxWidth) {
List<String> lines = new ArrayList<>(); List<String> 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(); StringBuilder currentLine = new StringBuilder();
for (String word : text.split("")) { String[] words = text.split("");
for (String word : words) {
if (currentLine.length() + word.length() <= maxCharsPerLine) { if (currentLine.length() + word.length() <= maxCharsPerLine) {
currentLine.append(word); currentLine.append(word);
} else { } else {
@ -152,6 +288,24 @@ public class PDFGenerator {
} }
return lines; return lines;
} }
// private static List<String> wrapText(String text, float maxWidth) {
// List<String> 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<String> imageUrls) throws IOException { private static float calculateTotalHeight(List<String> imageUrls) throws IOException {
@ -263,7 +417,7 @@ public class PDFGenerator {
// 绘制标题(垂直居中) // 绘制标题(垂直居中)
float titleY = startY - totalHeight/2 + (titleLines.size() * LINE_HEIGHT)/2; 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.beginText();
cs.setFont(font, FONT_SIZE); cs.setFont(font, FONT_SIZE);
cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY); cs.newLineAtOffset(TABLE_MARGIN + CELL_PADDING, titleY);
@ -275,19 +429,20 @@ public class PDFGenerator {
cs.endText(); cs.endText();
// 绘制第一行图片(自动缩放) // 绘制第一行图片(自动缩放)
PDImageXObject image1 = PDImageXObject.createFromByteArray(doc, PDImageXObject image1 = loadImageFromUrl(doc, imageUrls.get(0));
new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image"); if (image1 != null) {
float scale1 = Math.min( float scale1 = Math.min(
imageAvailableWidth / image1.getWidth(), imageAvailableWidth / image1.getWidth(),
singleImageHeight / image1.getHeight() singleImageHeight / image1.getHeight()
); );
float scaledHeight1 = image1.getHeight() * scale1; float scaledHeight1 = image1.getHeight() * scale1;
float scaledWidth1 = image1.getWidth() * scale1; float scaledWidth1 = image1.getWidth() * scale1;
cs.drawImage(image1, cs.drawImage(image1,
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
startY - CELL_PADDING - scaledHeight1, startY - CELL_PADDING - scaledHeight1,
scaledWidth1, scaledHeight1); scaledWidth1, scaledHeight1);
}
} else if (imageUrls.size() == 2) { } else if (imageUrls.size() == 2) {
// 双图片模式:改为上下布局 // 双图片模式:改为上下布局
@ -306,34 +461,37 @@ public class PDFGenerator {
cs.endText(); cs.endText();
// 绘制第一张图片(上) // 绘制第一张图片(上)
PDImageXObject image1 = PDImageXObject.createFromByteArray(doc, PDImageXObject image1 = loadImageFromUrl(doc, imageUrls.get(0));
new URL(imageUrls.get(0)).openStream().readAllBytes(), "embedded-image"); float scaledHeight1 = 0;
float scale1 = Math.min( if (image1 != null) {
imageAvailableWidth / image1.getWidth(), float scale1 = Math.min(
imageHeightPer / image1.getHeight() imageAvailableWidth / image1.getWidth(),
); imageHeightPer / image1.getHeight()
float scaledHeight1 = image1.getHeight() * scale1; );
float scaledWidth1 = image1.getWidth() * scale1; scaledHeight1 = image1.getHeight() * scale1;
float scaledWidth1 = image1.getWidth() * scale1;
cs.drawImage(image1, cs.drawImage(image1,
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
startY - CELL_PADDING - scaledHeight1, startY - CELL_PADDING - scaledHeight1,
scaledWidth1, scaledHeight1); scaledWidth1, scaledHeight1);
}
// 绘制第二张图片(下) // 绘制第二张图片(下)
PDImageXObject image2 = PDImageXObject.createFromByteArray(doc, PDImageXObject image2 = loadImageFromUrl(doc, imageUrls.get(1));
new URL(imageUrls.get(1)).openStream().readAllBytes(), "embedded-image"); if (image2 != null) {
float scale2 = Math.min( float scale2 = Math.min(
imageAvailableWidth / image2.getWidth(), imageAvailableWidth / image2.getWidth(),
imageHeightPer / image2.getHeight() imageHeightPer / image2.getHeight()
); );
float scaledHeight2 = image2.getHeight() * scale2; float scaledHeight2 = image2.getHeight() * scale2;
float scaledWidth2 = image2.getWidth() * scale2; float scaledWidth2 = image2.getWidth() * scale2;
cs.drawImage(image2, cs.drawImage(image2,
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING, TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING,
startY - CELL_PADDING - scaledHeight1 - CELL_PADDING - scaledHeight2, startY - CELL_PADDING - scaledHeight1 - CELL_PADDING - scaledHeight2,
scaledWidth2, scaledHeight2); scaledWidth2, scaledHeight2);
}
/* // 双图片模式:一行并列显示 /* // 双图片模式:一行并列显示
float imageWidthPer = (imageAvailableWidth - CELL_PADDING) / 2; float imageWidthPer = (imageAvailableWidth - CELL_PADDING) / 2;
@ -378,8 +536,239 @@ public class PDFGenerator {
TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING + imageWidthPer + CELL_PADDING, TABLE_MARGIN + LEFT_COL_WIDTH + CELL_PADDING + imageWidthPer + CELL_PADDING,
startY - CELL_PADDING - scaledHeight2, startY - CELL_PADDING - scaledHeight2,
scaledWidth2, 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<String> titleLines, List<String> 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<String> imageUrls, private static float calculateAdjustedImagesHeight(PDDocument doc, List<String> imageUrls,
float maxWidth, float maxHeight) throws IOException { float maxWidth, float maxHeight) throws IOException {
if (imageUrls.isEmpty()) return 0; if (imageUrls.isEmpty()) return 0;
@ -388,9 +777,23 @@ public class PDFGenerator {
if (imageUrls.size() == 1) { if (imageUrls.size() == 1) {
// 单图片模式:占用半页高度 // 单图片模式:占用半页高度
return maxHeight / 2; return maxHeight / 2;
} else { } else if(imageUrls.size() == 2){
// 双图片模式:整页高度 // 双图片模式:整页高度
return maxHeight; 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, private static float addImageRow(PDPageContentStream cs, PDDocument doc,
PDType0Font font, String title, String imageUrl, PDType0Font font, String title, String imageUrl,
float startX, float startY) throws IOException { float startX, float startY) throws IOException {
// 加载图片 // 加载图片
PDImageXObject image = PDImageXObject.createFromByteArray(doc, PDImageXObject image = loadImageFromUrl(doc, imageUrl);
new URL(imageUrl).openStream().readAllBytes(), "embedded-image"); if (image == null) return startY; // 如果图片加载失败,返回原位置
// 动态计算尺寸 // 动态计算尺寸
float imageWidth = RIGHT_COL_WIDTH - 10; float imageWidth = RIGHT_COL_WIDTH - 10;
float scale = imageWidth / image.getWidth(); float scale = imageWidth / image.getWidth();
@ -725,6 +1128,79 @@ public class PDFGenerator {
return startY - dynamicRowHeight - 10;*/ 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, /* private static float addImageRow(PDPageContentStream cs, PDDocument doc,
PDType0Font font, String title, String imageUrl, float x, float y) throws IOException { PDType0Font font, String title, String imageUrl, float x, float y) throws IOException {
// 绘制标题行 // 绘制标题行

@ -90,5 +90,8 @@ public class DeviceQueryBo extends BaseDto {
private String endTime; private String endTime;
private String config; private String config;
//设备状态0正常1故障2报警3离线
private Integer deviceStatus;
} }

@ -56,4 +56,7 @@ public class DeviceInfoExpordVo implements Serializable {
private Long tenantId; private Long tenantId;
@ExcelProperty(value = "设备区域") @ExcelProperty(value = "设备区域")
private Long deptAreaId; private Long deptAreaId;
@ExcelProperty(value = "设备位置")
private String site;
} }

@ -57,4 +57,6 @@ public class DeviceInfoImportVo implements Serializable {
private Long tenantId; private Long tenantId;
@ExcelProperty(value = "设备区域") @ExcelProperty(value = "设备区域")
private Long deptAreaId; private Long deptAreaId;
@ExcelProperty(value = "设备位置")
private String site;
} }

@ -61,6 +61,10 @@ public class DeviceInstallInfoExpordVo implements Serializable {
// @ApiModelProperty(value = "切断阀编号") // @ApiModelProperty(value = "切断阀编号")
@ExcelProperty(value = "地址") @ExcelProperty(value = "地址")
private String site; private String site;
@ExcelProperty(value = "切断阀编号")
private String shutValueNumber;
@ExcelProperty(value = "报警器编号")
private String deviceName;
// @ApiModelProperty(value = "燃气表号") // @ApiModelProperty(value = "燃气表号")
@ExcelProperty(value = "表号") @ExcelProperty(value = "表号")
private String gasMeterNumber; private String gasMeterNumber;

@ -64,6 +64,10 @@ public class DeviceInstallInfoVo implements Serializable {
// @ApiModelProperty(value = "切断阀编号") // @ApiModelProperty(value = "切断阀编号")
@ExcelProperty(value = "地址") @ExcelProperty(value = "地址")
private String site; private String site;
@ExcelProperty(value = "切断阀编号")
private String shutValueNumber;
@ExcelProperty(value = "报警器编号")
private String deviceName;
// @ApiModelProperty(value = "燃气表号") // @ApiModelProperty(value = "燃气表号")
@ExcelProperty(value = "表号") @ExcelProperty(value = "表号")
private String gasMeterNumber; private String gasMeterNumber;

@ -223,6 +223,8 @@ public class DeviceInstallInfoServiceImpl implements IDeviceInstallInfoService {
DeviceInstallInfo deviceInfo = MapstructUtils.convert(tbDeviceInfo, DeviceInstallInfo.class); DeviceInstallInfo deviceInfo = MapstructUtils.convert(tbDeviceInfo, DeviceInstallInfo.class);
DeviceInstallInfoVo deviceInstallInfoVo = MapstructUtils.convert(deviceInfo, DeviceInstallInfoVo.class); DeviceInstallInfoVo deviceInstallInfoVo = MapstructUtils.convert(deviceInfo, DeviceInstallInfoVo.class);
deviceInstallInfoVo.setXuhao(i); deviceInstallInfoVo.setXuhao(i);
deviceInstallInfoVo.setShutValueNumber(deviceInfo.getShutValueNumber());
deviceInstallInfoVo.setDeviceName(deviceInfo.getDeviceName());
deviceInstallInfoVo.setSite(tbDeviceInfo.getCommunityName() + tbDeviceInfo.getBuildingUnit() + tbDeviceInfo.getRoomNo()); deviceInstallInfoVo.setSite(tbDeviceInfo.getCommunityName() + tbDeviceInfo.getBuildingUnit() + tbDeviceInfo.getRoomNo());
deviceInstallInfoVo.setManufacturer("天津费加罗"); deviceInstallInfoVo.setManufacturer("天津费加罗");
deviceInstallInfoVo.setPosition("成功"); deviceInstallInfoVo.setPosition("成功");
@ -274,4 +276,4 @@ public class DeviceInstallInfoServiceImpl implements IDeviceInstallInfoService {
return predicate.build(); return predicate.build();
}*/ }*/
} }

@ -148,13 +148,14 @@ public class DeviceManagerServiceImpl implements IDeviceManagerService {
String name = query.getName(); String name = query.getName();
String deviceName = query.getDeviceName(); String deviceName = query.getDeviceName();
String pk = query.getProductKey(); String pk = query.getProductKey();
Integer deviceStatus = query.getDeviceStatus();
//关键字查询 //关键字查询
String keyword = query.getKeyword(); String keyword = query.getKeyword();
String group = query.getGroup(); String group = query.getGroup();
Boolean online = query.getOnline(); Boolean online = query.getOnline();
Long areaDepeId = query.getDeptAreaId(); Long areaDepeId = query.getDeptAreaId();
Paging<DeviceInfoVo> result = MapstructUtils.convert(deviceInfoData.findByConditions(name, uid, subUid, pk, group, Paging<DeviceInfoVo> 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<Category> categorys = categoryData.findAll(); List<Category> categorys = categoryData.findAll();
for (DeviceInfoVo row : result.getRows()) { for (DeviceInfoVo row : result.getRows()) {
if (ObjectUtil.isNotEmpty(row.getProductKey())) { if (ObjectUtil.isNotEmpty(row.getProductKey())) {

@ -62,6 +62,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.security.PermitAll; import javax.annotation.security.PermitAll;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -163,6 +164,7 @@ public class ThingServiceImpl implements IThingService {
switch (type) { switch (type) {
case REGISTER: case REGISTER:
//设备注册 //设备注册
// 根据
String deviceId = registerDevice(device, (DeviceRegister) action, null); String deviceId = registerDevice(device, (DeviceRegister) action, null);
deviceUpdateLastTime(deviceId, lastTime); deviceUpdateLastTime(deviceId, lastTime);
@ -381,7 +383,11 @@ public class ThingServiceImpl implements IThingService {
List<Rule> rules = ruleMessageHandler.processMessage1(thingModelMessage); List<Rule> rules = ruleMessageHandler.processMessage1(thingModelMessage);
for (Rule rule : rules) { for (Rule rule : rules) {
//这里也要加增加启用未启用条件查询 //这里也要加增加启用未启用条件查询
List<AlertConfig> config1 = iAlertConfigData.findByRuleInfoIdAndDeviceName(rule.getId(),device.getDeviceName()); List<AlertConfig> 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++) { for (int i = 0; i < config1.size(); i++) {
idAlertRecordData.save(AlertRecord.builder() idAlertRecordData.save(AlertRecord.builder()
.level(config1.get(i).getLevel()) .level(config1.get(i).getLevel())
@ -974,7 +980,7 @@ public class ThingServiceImpl implements IThingService {
if (productKey != null && device.getProductKey() != null if (productKey != null && device.getProductKey() != null
&& !device.getProductKey().equals(productKey)) { && !device.getProductKey().equals(productKey)) {
//防止设备变更了产品key //防止设备变更了产品key
device.setProductKey(productKey); // device.setProductKey(productKey);
// device.setModel(register.getModel()); // device.setModel(register.getModel());
deviceInfoData.save(device); deviceInfoData.save(device);
} }

@ -362,7 +362,8 @@ public class SysTenantServiceImpl implements ISysTenantService {
//删除角色 //删除角色
SysRole querySysRole=new SysRole(); SysRole querySysRole=new SysRole();
querySysRole.setTenantId(tenantId); querySysRole.setTenantId(tenantId);
List<SysRole> roles =sysRoleData.selectRoleList(querySysRole); // List<SysRole> roles =sysRoleData.selectRoleList(querySysRole);
List<SysRole> roles =sysRoleData.selectByTenantId(tenantId);
sysRoleData.deleteByIds(roles.stream().map(SysRole::getId).collect(Collectors.toList())); sysRoleData.deleteByIds(roles.stream().map(SysRole::getId).collect(Collectors.toList()));
//删除部门 //删除部门
List<SysDept> lsDepts = sysDeptData.findByTenantId(tenantId); List<SysDept> lsDepts = sysDeptData.findByTenantId(tenantId);

@ -52,6 +52,7 @@ spring:
hibernate: hibernate:
format_sql: false format_sql: false
use_sql_comments: false # 输出SQL注释 use_sql_comments: false # 输出SQL注释
generate_statistics: false # 关闭统计信息
session_factory: session_factory:
# statement_inspector: com.example.TenantSQLInterceptor # statement_inspector: com.example.TenantSQLInterceptor
# dialect: org.hibernate.dialect.MySQL5Dialect # dialect: org.hibernate.dialect.MySQL5Dialect

Loading…
Cancel
Save