1
0

WEB:导出数据设备

This commit is contained in:
2025-07-03 17:53:10 +08:00
parent 0271a84bc2
commit e8aee3039a
11 changed files with 339 additions and 98 deletions

View File

@ -116,12 +116,12 @@
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.1</version>
<exclusions>
<exclusion>
<artifactId>poi-ooxml-schemas</artifactId>
<groupId>org.apache.poi</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
</dependency>
</dependencies>

View File

@ -3,7 +3,6 @@ package com.fuyuanshen.equipment.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fuyuanshen.common.core.constant.ResponseMessageConstants;
import com.fuyuanshen.common.core.domain.PageResult;
import com.fuyuanshen.common.core.domain.ResponseVO;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.equipment.domain.Device;
@ -12,6 +11,7 @@ import com.fuyuanshen.equipment.domain.form.DeviceForm;
import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria;
import com.fuyuanshen.equipment.domain.vo.CustomerVo;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.equipment.service.impl.DeviceExportService;
import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.service.ISysOssService;
import io.swagger.v3.oas.annotations.Operation;
@ -43,19 +43,13 @@ public class DeviceController {
private final ISysOssService ossService;
private final DeviceService deviceService;
private final DeviceExportService exportService;
@Operation(summary = "分页查询设备列表", security = {@SecurityRequirement(name = "bearer-key")})
@GetMapping
public TableDataInfo<Device> queryDevice(DeviceQueryCriteria criteria) throws IOException {
Page<Device> page = new Page<>(criteria.getPage(), criteria.getSize());
// PageResult<Device> devices = null;
// try {
// devices = deviceService.queryAll(criteria, page);
// } catch (IOException e) {
// log.error("queryDevice error: " + e.getMessage());
// return ResponseVO.fail("");
// }
// return ResponseVO.success(devices);
return deviceService.queryAll(criteria, page);
}
@ -102,7 +96,6 @@ public class DeviceController {
@DeleteMapping(value = "/delete")
public ResponseVO<Object> deleteDevice(@Parameter(name = "传ID数组[]") @RequestBody List<Long> ids) {
deviceService.deleteAll(ids);
// deviceService.deleteAssign(ids);
return ResponseVO.success(ResponseMessageConstants.DELETE_SUCCESS);
}
@ -156,7 +149,8 @@ public class DeviceController {
@Operation(summary = "导出数据设备")
@GetMapping(value = "/download")
public void exportDevice(HttpServletResponse response, DeviceQueryCriteria criteria) throws IOException {
List<Device> devices = deviceService.queryAll(criteria);
exportService.export(devices, response);
}

View File

@ -1,7 +1,6 @@
package com.fuyuanshen.equipment.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fuyuanshen.common.core.domain.PageResult;
import com.fuyuanshen.common.core.domain.ResponseVO;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.equipment.domain.DeviceType;

View File

@ -0,0 +1,76 @@
package com.fuyuanshen.equipment.converter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
/**
* @author: 默苍璃
* @date: 2025-06-0618:56
*/
public class IgnoreFailedImageConverter implements Converter<URL> {
private static final Logger logger = LoggerFactory.getLogger(IgnoreFailedImageConverter.class);
@Override
public Class<?> supportJavaTypeKey() {
return URL.class;
}
// @Override
// public CellDataTypeEnum supportExcelTypeKey() {
// return CellDataTypeEnum.STRING;
// }
@Override
public WriteCellData<?> convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (value == null) {
return null;
}
try {
URLConnection conn = value.openConnection();
conn.setConnectTimeout(2000); // 2秒超时
conn.setReadTimeout(3000); // 3秒超时
try (InputStream inputStream = conn.getInputStream()) {
// byte[] bytes = FileUtils.readInputStream(inputStream, value.toString());
// 替代 FileUtils.readInputStream 的自定义方法
byte[] bytes = readInputStream(inputStream);
return new WriteCellData<>(bytes);
}
} catch (Exception e) {
// 静默忽略错误,只记录日志
logger.debug("忽略图片加载失败: {}, 原因: {}", value, e.getMessage());
// return null; // 返回null表示不写入图片
return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null
}
}
/**
* 替代 FileUtils.readInputStream 的自定义方法
*
* @param inputStream 输入流
* @return 字节数组
* @throws Exception 读取异常
*/
private byte[] readInputStream(InputStream inputStream) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return outputStream.toByteArray();
}
}

View File

@ -0,0 +1,44 @@
package com.fuyuanshen.equipment.converter;// package com.fuyuanshen.modules.system.converter;
//
// import com.alibaba.excel.converters.Converter;
// import com.alibaba.excel.enums.CellDataTypeEnum;
// import com.alibaba.excel.metadata.GlobalConfiguration;
// import com.alibaba.excel.metadata.data.ReadCellData;
// import com.alibaba.excel.metadata.property.ExcelContentProperty;
//
// import java.io.ByteArrayOutputStream;
// import java.io.IOException;
//
// /**
// * @author: 默苍璃
// * @date: 2025-06-0710:01
// */
//
// public class ImageReadConverter implements Converter<byte[]> {
//
// @Override
// public Class<?> supportJavaTypeKey() {
// return byte[].class;
// }
//
// // @Override
// // public CellDataTypeEnum supportExcelTypeKey() {
// // return CellDataTypeEnum.IMAGE;
// // }
//
// @Override
// public byte[] convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws IOException {
// if (cellData.getType() == CellDataTypeEnum.IMAGE) {
// ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// cellData.getImageValueList().forEach(image -> {
// try {
// outputStream.write(image.getImageBytes());
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// });
// return outputStream.toByteArray();
// }
// return null;
// }
// }

View File

@ -99,4 +99,10 @@ public class Device extends TenantEntity {
@Schema(name = "绑定状态")
private Integer bindingStatus;
/**
* 创建人名称
*/
private String createByName;
}

View File

@ -0,0 +1,80 @@
package com.fuyuanshen.equipment.domain.dto;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.fuyuanshen.equipment.converter.IgnoreFailedImageConverter;
import lombok.Data;
import java.net.URL;
/**
* @author: 默苍璃
* @date: 2025-06-0618:16
*/
@Data
@HeadRowHeight(20) // 表头行高
@ContentRowHeight(100) // 内容行高
public class DeviceExcelExportDTO {
@ExcelProperty("ID")
private Long id;
@ExcelProperty("设备类型")
private Long deviceType;
// @ExcelProperty("客户号")
// private Long customerId;
@ExcelProperty("所属客户")
private String customerName;
@ExcelProperty("设备名称")
@ColumnWidth(20)
private String deviceName;
@ExcelProperty(value = "设备图片", converter = IgnoreFailedImageConverter.class)
@ColumnWidth(15) // 设置图片列宽度
private URL devicePic; // 使用URL类型
@ExcelProperty("设备MAC")
@ColumnWidth(20)
private String deviceMac;
@ExcelProperty("设备IMEI")
@ColumnWidth(20)
private String deviceImei;
@ExcelProperty("经度")
private String longitude;
@ExcelProperty("纬度")
private String latitude;
@ExcelProperty("备注")
@ColumnWidth(30)
private String remark;
@ExcelProperty("设备类型名称")
@ColumnWidth(20)
private String typeName;
/**
* 设备状态
* 0 失效
* 1 正常
*/
@ExcelProperty("设备状态")
@ColumnWidth(20)
private String deviceStatus;
@ExcelProperty("创建时间")
@ColumnWidth(20)
private String createTime;
@ExcelProperty("创建人")
@ColumnWidth(20)
private String createBy;
}

View File

@ -28,6 +28,14 @@ public interface DeviceService extends IService<Device> {
*/
TableDataInfo<Device> queryAll(DeviceQueryCriteria criteria, Page<Device> page) throws IOException;
/**
* 查询所有数据不分页
*
* @param criteria 条件参数
* @return List<DeviceDto>
*/
List<Device> queryAll(DeviceQueryCriteria criteria);
/**
* 查询所有设备信息
*

View File

@ -0,0 +1,86 @@
package com.fuyuanshen.equipment.service.impl;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.DateUtils;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.dto.DeviceExcelExportDTO;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author: 默苍璃
* @date: 2025-06-0618:22
*/
@Service
public class DeviceExportService {
public void export(List<Device> devices, HttpServletResponse response) {
try {
String fileName = "设备列表_" + System.currentTimeMillis() + ".xlsx";
// 使用URLEncoder进行RFC 5987编码
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 使用RFC 5987标准编码文件名
response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + encodedFileName);
// 转换为DTO列表
List<DeviceExcelExportDTO> dtoList = devices.stream().map(device -> {
DeviceExcelExportDTO dto = new DeviceExcelExportDTO();
dto.setId(device.getId());
dto.setDeviceType(device.getDeviceType());
dto.setCustomerName(device.getCustomerName());
dto.setDeviceName(device.getDeviceName());
dto.setDeviceMac(device.getDeviceMac());
// 设备IMEI
dto.setDeviceImei(device.getDeviceImei());
dto.setLongitude(device.getLongitude());
dto.setLatitude(device.getLatitude());
dto.setRemark(device.getRemark());
dto.setTypeName(device.getTypeName());
dto.setCreateBy(device.getCreateByName());
Integer deviceStatus = device.getDeviceStatus();
dto.setDeviceStatus(deviceStatus == 1 ? "正常" : "失效");
// 时间戳转换
dto.setCreateTime(DateUtils.format(device.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
// 处理图片URL转换
handleDevicePic(device, dto);
return dto;
}).collect(Collectors.toList());
// 写入Excel
EasyExcel.write(response.getOutputStream(), DeviceExcelExportDTO.class).sheet("设备数据").doWrite(dtoList);
} catch (IOException e) {
throw new RuntimeException("导出Excel失败", e);
}
}
private void handleDevicePic(Device device, DeviceExcelExportDTO dto) {
String picUrl = device.getDevicePic();
if (picUrl != null && !picUrl.trim().isEmpty()) {
try {
// 尝试创建URL对象会自动验证格式
dto.setDevicePic(new URL(picUrl));
} catch (MalformedURLException e) {
// 不是有效URL时设置为null
dto.setDevicePic(null);
}
} else {
dto.setDevicePic(null);
}
}
}

View File

@ -4,11 +4,11 @@ import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fuyuanshen.common.core.domain.PageResult;
import com.fuyuanshen.common.core.utils.PageUtil;
import com.fuyuanshen.common.core.domain.model.LoginUser;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.customer.domain.vo.ConsumerVo;
import com.fuyuanshen.customer.domain.Customer;
import com.fuyuanshen.customer.mapper.CustomerMapper;
import com.fuyuanshen.equipment.constants.DeviceConstants;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceType;
@ -19,8 +19,8 @@ import com.fuyuanshen.equipment.domain.vo.CustomerVo;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.customer.domain.Customer;
import com.fuyuanshen.customer.mapper.CustomerMapper;
import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.service.ISysOssService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -55,6 +55,8 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
private final CustomerMapper customerMapper;
private final ISysOssService ossService;
/**
* 分页查询设备
@ -83,6 +85,12 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
}
@Override
public List<Device> queryAll(DeviceQueryCriteria criteria) {
return deviceMapper.findAll(criteria);
}
/**
* 查询所有设备
*
@ -113,13 +121,16 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
}
// 保存图片并获取URL
String imageUrl = saveDeviceImage(deviceForm.getFile(), deviceForm.getDeviceName());
// String imageUrl = saveDeviceImage(deviceForm.getFile(), deviceForm.getDeviceName());
SysOssVo upload = ossService.upload(deviceForm.getFile());
// 设置图片路径
deviceForm.setDevicePic(imageUrl);
deviceForm.setDevicePic(upload.getUrl());
// 转换对象并插入数据库
Device device = new Device();
device.setCurrentOwnerId(LoginHelper.getUserId());
LoginUser loginUser = LoginHelper.getLoginUser();
device.setCurrentOwnerId(loginUser.getUserId());
device.setCreateByName(loginUser.getNickname());
BeanUtil.copyProperties(deviceForm, device, true);
deviceMapper.insert(device);
@ -155,8 +166,10 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
Device device = devices.get(0);
// 处理上传的图片
String imageUrl = saveDeviceImage(deviceForm.getFile(), device.getDeviceName());
deviceForm.setDevicePic(imageUrl);
// String imageUrl = saveDeviceImage(deviceForm.getFile(), device.getDeviceName());
SysOssVo upload = ossService.upload(deviceForm.getFile());
// 设置图片路径
deviceForm.setDevicePic(upload.getUrl());
// 更新字段
BeanUtil.copyProperties(deviceForm, device, true);
@ -253,14 +266,14 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
devices.forEach((device) -> {
deviceMapper.updateById(device);
device.setId( null);
device.setId(null);
device.setCurrentOwnerId(customerId);
deviceMapper.insert( device);
deviceMapper.insert(device);
DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType());
deviceType.setId( null);
deviceType.setId(null);
device.setCurrentOwnerId(customerId);
deviceTypeMapper.insert( deviceType);
deviceTypeMapper.insert(deviceType);
});
}

View File

@ -38,10 +38,7 @@
<!-- 分页查询设备 -->
<select id="findAll" resultType="com.fuyuanshen.equipment.domain.Device">
select
d.id,d.device_name,
d.device_pic, d.device_mac, d.device_sn, d.update_by,d.device_imei,
d.update_time, d.device_type, d.remark, d.binding_status,t.type_name
select d.* ,t.type_name
FROM device d
LEFT JOIN device_type t ON d.device_type = t.id
<where>
@ -73,68 +70,6 @@
</where>
</select>
<select id="findAll1" resultType="com.fuyuanshen.equipment.domain.Device">
SELECT
d.id, d.customer_id, d.customer_name, d.device_name,
d.device_pic, d.device_mac, d.device_sn, d.create_by, d.update_by,
d.create_time, d.update_time, d.device_type, d.remark, d.device_status, t.type_name
from device d
left join device_type t
on d.device_type = t.id
<where>
<!-- 时间范围等其他条件保持原样 -->
<if test="criteria.deviceName != null and criteria.deviceName.trim() != ''">
and d.device_name like concat('%', TRIM(#{criteria.deviceName}), '%')
</if>
<if test="criteria.deviceMac != null">
and d.device_mac = #{criteria.deviceMac}
</if>
<if test="criteria.deviceSn != null">
and d.device_sn = #{criteria.deviceSn}
</if>
<if test="criteria.deviceType != null">
and d.device_type = #{criteria.deviceType}
</if>
<if test="criteria.createTime != null and criteria.createTime.size() != 0">
and d.create_time between #{criteria.createTime[0]} and #{criteria.createTime[1]}
</if>
<!-- 将两个客户相关条件合并成一个逻辑组 -->
<if test="criteria.customerId != null or (criteria.customerIds != null and !criteria.customerIds.isEmpty())">
AND (
<!-- 条件1: 用户或其下属创建,且 customer_id 为空 -->
<if test="criteria.customerId != null">
d.create_by IN (
SELECT username FROM sys_user
WHERE user_id = #{criteria.customerId} OR pid = #{criteria.customerId}
) AND d.customer_id IS NULL
</if>
<!-- 如果前面的条件不存在,则只保留第二个条件 -->
<choose>
<when test="criteria.customerId != null and (criteria.customerIds != null and !criteria.customerIds.isEmpty())">
OR
</when>
<when test="criteria.customerId == null and (criteria.customerIds != null and !criteria.customerIds.isEmpty())">
<!-- 仅当第一个条件不存在时才开始括号 -->
</when>
</choose>
<!-- 条件2: 客户ID在列表中 -->
<if test="criteria.customerIds != null and !criteria.customerIds.isEmpty()">
(d.customer_id IN
<foreach item="customerId" collection="criteria.customerIds" open="(" separator="," close=")">
#{customerId}
</foreach>
)
</if>
)
</if>
</where>
order by d.id desc
</select>
<select id="findAllDevices" resultType="com.fuyuanshen.equipment.domain.Device">
select
d.id, d.customer_id, d.device_name,