设备图片
This commit is contained in:
@@ -100,11 +100,26 @@ public class PqDevController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD)
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
@GetMapping("/image/download")
|
@GetMapping("/image/preview")
|
||||||
@ApiOperation("下载设备图片")
|
@ApiOperation("下载设备图片")
|
||||||
@ApiImplicitParam(name = "id", value = "被检设备id", required = true)
|
@ApiImplicitParam(name = "id", value = "被检设备id", required = true)
|
||||||
public void downloadImage(@RequestParam("id") String id, HttpServletResponse response) {
|
public void previewImage(@RequestParam("id") String id,
|
||||||
pqDevService.downloadImage(id, response);
|
@RequestParam("fileName") String fileName,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
pqDevService.previewImage(id, fileName, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@GetMapping("/image/download")
|
||||||
|
@ApiOperation("download device image")
|
||||||
|
@ApiImplicitParams({
|
||||||
|
@ApiImplicitParam(name = "id", value = "id", required = true),
|
||||||
|
@ApiImplicitParam(name = "fileName", value = "fileName", required = true)
|
||||||
|
})
|
||||||
|
public void downloadImage(@RequestParam("id") String id,
|
||||||
|
@RequestParam("fileName") String fileName,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
pqDevService.downloadImage(id, fileName, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OperateInfo(operateType = OperateType.DELETE)
|
@OperateInfo(operateType = OperateType.DELETE)
|
||||||
|
|||||||
@@ -154,7 +154,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectByDevId" resultType="com.njcn.gather.device.pojo.vo.PqDevVO">
|
<select id="selectByDevId" resultType="com.njcn.gather.device.pojo.vo.PqDevVO">
|
||||||
SELECT dev.*, dev_sub.*
|
SELECT <include refid="DevColumnsNoImage"/>, dev_sub.*
|
||||||
FROM pq_dev dev
|
FROM pq_dev dev
|
||||||
left JOIN pq_dev_sub dev_sub ON dev.Id = dev_sub.Dev_Id
|
left JOIN pq_dev_sub dev_sub ON dev.Id = dev_sub.Dev_Id
|
||||||
WHERE dev.Id = #{devId}
|
WHERE dev.Id = #{devId}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public class PqDevParam {
|
|||||||
private Integer importFlag;
|
private Integer importFlag;
|
||||||
|
|
||||||
@ApiModelProperty("设备图片")
|
@ApiModelProperty("设备图片")
|
||||||
private MultipartFile image;
|
private List<MultipartFile> images;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新操作实体
|
* 更新操作实体
|
||||||
@@ -151,6 +151,9 @@ public class PqDevParam {
|
|||||||
@NotBlank(message = DetectionValidMessage.ID_NOT_BLANK)
|
@NotBlank(message = DetectionValidMessage.ID_NOT_BLANK)
|
||||||
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.ID_FORMAT_ERROR)
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = DetectionValidMessage.ID_FORMAT_ERROR)
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
|
@ApiModelProperty("deleteImageNames")
|
||||||
|
private List<String> deleteImageNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.njcn.gather.device.pojo.po;
|
|||||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
@@ -185,9 +184,6 @@ public class PqDev extends BaseEntity implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private Integer importFlag;
|
private Integer importFlag;
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private byte[] image;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态:0-删除 1-正常
|
* 状态:0-删除 1-正常
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.njcn.gather.device.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PqDevImageVO {
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
private String previewUrl;
|
||||||
|
|
||||||
|
private String downloadUrl;
|
||||||
|
}
|
||||||
@@ -150,7 +150,5 @@ public class PqDevVO extends PqDev {
|
|||||||
|
|
||||||
private Boolean hasImage;
|
private Boolean hasImage;
|
||||||
|
|
||||||
private String imageBase64;
|
private List<PqDevImageVO> images;
|
||||||
|
|
||||||
private String imageContentType;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ public interface IPqDevService extends IService<PqDev> {
|
|||||||
* @param id 设备id
|
* @param id 设备id
|
||||||
* @param response 响应
|
* @param response 响应
|
||||||
*/
|
*/
|
||||||
void downloadImage(String id, HttpServletResponse response);
|
void previewImage(String id, String fileName, HttpServletResponse response);
|
||||||
|
|
||||||
|
void downloadImage(String id, String fileName, HttpServletResponse response);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取装置信息和装置下监测点信息
|
* 获取装置信息和装置下监测点信息
|
||||||
|
|||||||
@@ -0,0 +1,288 @@
|
|||||||
|
package com.njcn.gather.device.service;
|
||||||
|
|
||||||
|
import com.njcn.gather.device.pojo.vo.PqDevImageVO;
|
||||||
|
import com.njcn.gather.device.util.PqDevImageSupport;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PqDevImageFileService {
|
||||||
|
|
||||||
|
public static final int MAX_IMAGE_COUNT = 5;
|
||||||
|
|
||||||
|
private static final Pattern UUID_JPG_FILE_NAME = Pattern.compile(
|
||||||
|
"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.jpg$"
|
||||||
|
);
|
||||||
|
private static final Pattern DEVICE_ID_PATTERN = Pattern.compile("^[A-Za-z0-9_-]{1,64}$");
|
||||||
|
private static final String INVALID_IMAGE_NAME = "图片文件名无效";
|
||||||
|
private static final String INVALID_IMAGE_PATH = "图片路径无效";
|
||||||
|
private static final String INVALID_DEVICE_ID = "设备ID无效";
|
||||||
|
private static final String IMAGE_NOT_EXISTS = "设备图片不存在";
|
||||||
|
private static final String IMAGE_COUNT_EXCEEDED = "设备图片最多上传5张";
|
||||||
|
private static final String READ_IMAGE_FAILED = "读取设备图片失败";
|
||||||
|
private static final String PREVIEW_URL = "/pqDev/image/preview?id=%s&fileName=%s";
|
||||||
|
private static final String DOWNLOAD_URL = "/pqDev/image/download?id=%s&fileName=%s";
|
||||||
|
|
||||||
|
private final String imageDir;
|
||||||
|
|
||||||
|
public PqDevImageFileService(@Value("${device.image.dir:}") String imageDir) {
|
||||||
|
this.imageDir = imageDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PqDevImageVO> listImages(String devId) {
|
||||||
|
Path deviceDir = deviceDir(devId);
|
||||||
|
if (!Files.isDirectory(deviceDir)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Path> imagePaths = new ArrayList<>();
|
||||||
|
try (DirectoryStream<Path> stream = Files.newDirectoryStream(deviceDir)) {
|
||||||
|
for (Path path : stream) {
|
||||||
|
if (Files.isRegularFile(path) && isValidImageFileName(path.getFileName().toString())) {
|
||||||
|
imagePaths.add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(READ_IMAGE_FAILED, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(imagePaths, new Comparator<Path>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Path left, Path right) {
|
||||||
|
int modifiedCompare = Long.compare(lastModified(left), lastModified(right));
|
||||||
|
if (modifiedCompare != 0) {
|
||||||
|
return modifiedCompare;
|
||||||
|
}
|
||||||
|
return left.getFileName().toString().compareTo(right.getFileName().toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
List<PqDevImageVO> images = new ArrayList<>();
|
||||||
|
for (Path imagePath : imagePaths) {
|
||||||
|
images.add(buildImageVO(devId, imagePath.getFileName().toString()));
|
||||||
|
}
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateAddCount(List<MultipartFile> newImages) {
|
||||||
|
if (effectiveNewImageCount(newImages) > MAX_IMAGE_COUNT) {
|
||||||
|
throw new IllegalArgumentException(IMAGE_COUNT_EXCEEDED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateFinalCount(String devId, List<String> deleteImageNames, List<MultipartFile> newImages) {
|
||||||
|
validateAddCount(newImages);
|
||||||
|
Set<String> existingNames = currentImageNameSet(devId);
|
||||||
|
validateExistingImageNames(devId, deleteImageNames);
|
||||||
|
|
||||||
|
int finalCount = existingNames.size() - uniqueCount(deleteImageNames) + effectiveNewImageCount(newImages);
|
||||||
|
if (finalCount > MAX_IMAGE_COUNT) {
|
||||||
|
throw new IllegalArgumentException(IMAGE_COUNT_EXCEEDED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PqDevImageVO> saveImages(String devId, List<MultipartFile> newImages) throws IOException {
|
||||||
|
validateAddCount(newImages);
|
||||||
|
if (newImages == null || newImages.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.createDirectories(deviceDir(devId));
|
||||||
|
List<PqDevImageVO> savedImages = new ArrayList<>();
|
||||||
|
for (MultipartFile image : newImages) {
|
||||||
|
if (image == null || image.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String fileName = UUID.randomUUID().toString().toLowerCase(Locale.ROOT) + ".jpg";
|
||||||
|
Path path = imagePath(devId, fileName);
|
||||||
|
writeImage(path, PqDevImageSupport.processUploadToJpeg(image));
|
||||||
|
savedImages.add(buildImageVO(devId, fileName));
|
||||||
|
}
|
||||||
|
return savedImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteImages(String devId, List<String> deleteImageNames) throws IOException {
|
||||||
|
validateExistingImageNames(devId, deleteImageNames);
|
||||||
|
if (deleteImageNames == null || deleteImageNames.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<String> deletedNames = new HashSet<>();
|
||||||
|
for (String fileName : deleteImageNames) {
|
||||||
|
if (deletedNames.add(fileName)) {
|
||||||
|
Files.delete(imagePath(devId, fileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateImageNames(List<String> imageNames) {
|
||||||
|
if (imageNames == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String imageName : imageNames) {
|
||||||
|
if (!isValidImageFileName(imageName)) {
|
||||||
|
throw new IllegalArgumentException(INVALID_IMAGE_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateExistingImageNames(String devId, List<String> imageNames) {
|
||||||
|
validateImageNames(imageNames);
|
||||||
|
if (imageNames == null || imageNames.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<String> existingNames = currentImageNameSet(devId);
|
||||||
|
for (String imageName : imageNames) {
|
||||||
|
if (!existingNames.contains(imageName)) {
|
||||||
|
throw new IllegalArgumentException(IMAGE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path imagePath(String devId, String fileName) {
|
||||||
|
if (!isValidImageFileName(fileName)) {
|
||||||
|
throw new IllegalArgumentException(INVALID_IMAGE_NAME);
|
||||||
|
}
|
||||||
|
Path deviceDir = deviceDir(devId);
|
||||||
|
Path path = deviceDir.resolve(fileName).normalize();
|
||||||
|
if (!path.startsWith(deviceDir)) {
|
||||||
|
throw new IllegalArgumentException(INVALID_IMAGE_PATH);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long imageSize(String devId, String fileName) throws IOException {
|
||||||
|
Path path = imagePath(devId, fileName);
|
||||||
|
if (!Files.isRegularFile(path)) {
|
||||||
|
throw new IllegalArgumentException(IMAGE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return Files.size(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeImage(String devId, String fileName, OutputStream outputStream) throws IOException {
|
||||||
|
Path path = imagePath(devId, fileName);
|
||||||
|
if (!Files.isRegularFile(path)) {
|
||||||
|
throw new IllegalArgumentException(IMAGE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
Files.copy(path, outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteDeviceDirectoryQuietly(String devId) {
|
||||||
|
Path deviceDir = deviceDir(devId);
|
||||||
|
if (!Files.exists(deviceDir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (Stream<Path> stream = Files.walk(deviceDir)) {
|
||||||
|
List<Path> paths = new ArrayList<>();
|
||||||
|
Iterator<Path> iterator = stream.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
paths.add(iterator.next());
|
||||||
|
}
|
||||||
|
Collections.sort(paths, new Comparator<Path>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Path left, Path right) {
|
||||||
|
return right.compareTo(left);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (Path path : paths) {
|
||||||
|
Files.deleteIfExists(path);
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeImage(Path imagePath, byte[] imageBytes) throws IOException {
|
||||||
|
Files.createDirectories(imagePath.getParent());
|
||||||
|
Files.write(imagePath, imageBytes, StandardOpenOption.CREATE_NEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path rootDir() {
|
||||||
|
Path root;
|
||||||
|
if (StringUtils.hasText(imageDir)) {
|
||||||
|
root = Paths.get(imageDir);
|
||||||
|
} else {
|
||||||
|
root = Paths.get(System.getProperty("user.dir"), "data", "device-images");
|
||||||
|
}
|
||||||
|
return root.toAbsolutePath().normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path deviceDir(String devId) {
|
||||||
|
if (!StringUtils.hasText(devId) || !DEVICE_ID_PATTERN.matcher(devId).matches()) {
|
||||||
|
throw new IllegalArgumentException(INVALID_DEVICE_ID);
|
||||||
|
}
|
||||||
|
Path rootDir = rootDir();
|
||||||
|
Path deviceDir = rootDir.resolve(devId).normalize();
|
||||||
|
if (!deviceDir.startsWith(rootDir)) {
|
||||||
|
throw new IllegalArgumentException(INVALID_IMAGE_PATH);
|
||||||
|
}
|
||||||
|
return deviceDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PqDevImageVO buildImageVO(String devId, String fileName) {
|
||||||
|
return new PqDevImageVO(
|
||||||
|
fileName,
|
||||||
|
String.format(PREVIEW_URL, devId, fileName),
|
||||||
|
String.format(DOWNLOAD_URL, devId, fileName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> currentImageNameSet(String devId) {
|
||||||
|
Set<String> names = new HashSet<>();
|
||||||
|
for (PqDevImageVO image : listImages(devId)) {
|
||||||
|
names.add(image.getFileName());
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int effectiveNewImageCount(List<MultipartFile> newImages) {
|
||||||
|
if (newImages == null || newImages.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int count = 0;
|
||||||
|
for (MultipartFile image : newImages) {
|
||||||
|
if (image != null && !image.isEmpty()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int uniqueCount(List<String> names) {
|
||||||
|
if (names == null || names.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return new HashSet<>(names).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidImageFileName(String fileName) {
|
||||||
|
return fileName != null && UUID_JPG_FILE_NAME.matcher(fileName).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long lastModified(Path path) {
|
||||||
|
try {
|
||||||
|
return Files.getLastModifiedTime(path).toMillis();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ import com.njcn.gather.device.pojo.po.PqDevSub;
|
|||||||
import com.njcn.gather.device.pojo.vo.*;
|
import com.njcn.gather.device.pojo.vo.*;
|
||||||
import com.njcn.gather.device.service.IPqDevService;
|
import com.njcn.gather.device.service.IPqDevService;
|
||||||
import com.njcn.gather.device.service.IPqDevSubService;
|
import com.njcn.gather.device.service.IPqDevSubService;
|
||||||
import com.njcn.gather.device.util.PqDevImageSupport;
|
import com.njcn.gather.device.service.PqDevImageFileService;
|
||||||
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
import com.njcn.gather.monitor.pojo.po.PqMonitor;
|
||||||
import com.njcn.gather.monitor.pojo.vo.PqMonitorExcel;
|
import com.njcn.gather.monitor.pojo.vo.PqMonitorExcel;
|
||||||
import com.njcn.gather.monitor.service.IPqMonitorService;
|
import com.njcn.gather.monitor.service.IPqMonitorService;
|
||||||
@@ -100,6 +100,7 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
|||||||
private final AdPlanMapper adPlanMapper;
|
private final AdPlanMapper adPlanMapper;
|
||||||
private final IPqDevCheckHistoryService pqDevCheckHistoryService;
|
private final IPqDevCheckHistoryService pqDevCheckHistoryService;
|
||||||
private final RestTemplateUtil restTemplateUtil;
|
private final RestTemplateUtil restTemplateUtil;
|
||||||
|
private final PqDevImageFileService pqDevImageFileService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<PqDevVO> listPqDevs(PqDevParam.QueryParam queryParam) {
|
public Page<PqDevVO> listPqDevs(PqDevParam.QueryParam queryParam) {
|
||||||
@@ -139,10 +140,10 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
|||||||
pqDevParam.setName(pqDevParam.getName().trim());
|
pqDevParam.setName(pqDevParam.getName().trim());
|
||||||
pqDevParam.setCreateId(pqDevParam.getCreateId().trim());
|
pqDevParam.setCreateId(pqDevParam.getCreateId().trim());
|
||||||
this.checkRepeat(pqDevParam, false);
|
this.checkRepeat(pqDevParam, false);
|
||||||
|
validateAddImages(pqDevParam.getImages());
|
||||||
|
|
||||||
PqDev pqDev = new PqDev();
|
PqDev pqDev = new PqDev();
|
||||||
BeanUtil.copyProperties(pqDevParam, pqDev, CopyOptions.create().setIgnoreProperties("image"));
|
BeanUtil.copyProperties(pqDevParam, pqDev, CopyOptions.create().setIgnoreProperties("images"));
|
||||||
fillImage(pqDev, pqDevParam);
|
|
||||||
String currrentScene = sysTestConfigService.getCurrrentScene();
|
String currrentScene = sysTestConfigService.getCurrrentScene();
|
||||||
this.checkParams(pqDev, currrentScene);
|
this.checkParams(pqDev, currrentScene);
|
||||||
|
|
||||||
@@ -170,19 +171,49 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
|||||||
pqDev.setImportFlag(0);
|
pqDev.setImportFlag(0);
|
||||||
pqDev.setState(DataStateEnum.ENABLE.getCode());
|
pqDev.setState(DataStateEnum.ENABLE.getCode());
|
||||||
pqDevSubService.save(pqDevSub);
|
pqDevSubService.save(pqDevSub);
|
||||||
return this.save(pqDev);
|
boolean saved = this.save(pqDev);
|
||||||
|
if (saved) {
|
||||||
|
saveAddImages(id, pqDevParam.getImages());
|
||||||
|
}
|
||||||
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillImage(PqDev pqDev, PqDevParam pqDevParam) {
|
private void validateAddImages(List<MultipartFile> images) {
|
||||||
try {
|
try {
|
||||||
byte[] image = PqDevImageSupport.processUpload(pqDevParam.getImage());
|
pqDevImageFileService.validateAddCount(images);
|
||||||
if (image != null) {
|
} catch (IllegalArgumentException e) {
|
||||||
pqDev.setImage(image);
|
throw new BusinessException(CommonResponseEnum.FAIL, e.getMessage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAddImages(String id, List<MultipartFile> images) {
|
||||||
|
try {
|
||||||
|
pqDevImageFileService.saveImages(id, images);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
pqDevImageFileService.deleteDeviceDirectoryQuietly(id);
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
pqDevImageFileService.deleteDeviceDirectoryQuietly(id);
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "device image save failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateUpdateImages(PqDevParam.UpdateParam updateParam) {
|
||||||
|
try {
|
||||||
|
pqDevImageFileService.validateFinalCount(updateParam.getId(), updateParam.getDeleteImageNames(), updateParam.getImages());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveUpdateImages(PqDevParam.UpdateParam updateParam) {
|
||||||
|
try {
|
||||||
|
pqDevImageFileService.saveImages(updateParam.getId(), updateParam.getImages());
|
||||||
|
pqDevImageFileService.deleteImages(updateParam.getId(), updateParam.getDeleteImageNames());
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new BusinessException(CommonResponseEnum.FAIL, e.getMessage());
|
throw new BusinessException(CommonResponseEnum.FAIL, e.getMessage());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new BusinessException(CommonResponseEnum.FAIL, "图片处理失败");
|
throw new BusinessException(CommonResponseEnum.FAIL, "device image save failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,10 +257,10 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
|||||||
updateParam.setName(updateParam.getName().trim());
|
updateParam.setName(updateParam.getName().trim());
|
||||||
updateParam.setCreateId(updateParam.getCreateId().trim());
|
updateParam.setCreateId(updateParam.getCreateId().trim());
|
||||||
this.checkRepeat(updateParam, true);
|
this.checkRepeat(updateParam, true);
|
||||||
|
validateUpdateImages(updateParam);
|
||||||
|
|
||||||
PqDev pqDev = new PqDev();
|
PqDev pqDev = new PqDev();
|
||||||
BeanUtil.copyProperties(updateParam, pqDev, CopyOptions.create().setIgnoreProperties("image"));
|
BeanUtil.copyProperties(updateParam, pqDev, CopyOptions.create().setIgnoreProperties("images", "deleteImageNames"));
|
||||||
fillImage(pqDev, updateParam);
|
|
||||||
String currrentScene = sysTestConfigService.getCurrrentScene();
|
String currrentScene = sysTestConfigService.getCurrrentScene();
|
||||||
this.checkParams(pqDev, currrentScene);
|
this.checkParams(pqDev, currrentScene);
|
||||||
|
|
||||||
@@ -237,7 +268,11 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
|||||||
if (PatternEnum.CONTRAST.getValue().equals(dictDataService.getDictDataById(updateParam.getPattern()).getCode())) {
|
if (PatternEnum.CONTRAST.getValue().equals(dictDataService.getDictDataById(updateParam.getPattern()).getCode())) {
|
||||||
pqMonitorService.updatePqMonitorByDevId(updateParam.getId(), updateParam.getMonitorList());
|
pqMonitorService.updatePqMonitorByDevId(updateParam.getId(), updateParam.getMonitorList());
|
||||||
}
|
}
|
||||||
return this.updateById(pqDev);
|
boolean updated = this.updateById(pqDev);
|
||||||
|
if (updated) {
|
||||||
|
saveUpdateImages(updateParam);
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -282,7 +317,13 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
|||||||
throw new BusinessException(DetectionResponseEnum.DEVICE_DELETE, str.toString());
|
throw new BusinessException(DetectionResponseEnum.DEVICE_DELETE, str.toString());
|
||||||
}
|
}
|
||||||
pqDevSubService.remove(new QueryWrapper<PqDevSub>().lambda().in(PqDevSub::getDevId, param.getIds()));
|
pqDevSubService.remove(new QueryWrapper<PqDevSub>().lambda().in(PqDevSub::getDevId, param.getIds()));
|
||||||
return this.lambdaUpdate().set(PqDev::getState, DataStateEnum.DELETED.getCode()).in(PqDev::getId, param.getIds()).update();
|
boolean deleted = this.lambdaUpdate().set(PqDev::getState, DataStateEnum.DELETED.getCode()).in(PqDev::getId, param.getIds()).update();
|
||||||
|
if (deleted) {
|
||||||
|
for (String id : param.getIds()) {
|
||||||
|
pqDevImageFileService.deleteDeviceDirectoryQuietly(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -399,7 +440,7 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
|||||||
if (ObjectUtil.isNull(pqDevVO)) {
|
if (ObjectUtil.isNull(pqDevVO)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
setImageEchoFields(pqDevVO);
|
setImageListFields(pqDevVO);
|
||||||
if (StrUtil.isNotBlank(pqDevVO.getSeries())) {
|
if (StrUtil.isNotBlank(pqDevVO.getSeries())) {
|
||||||
pqDevVO.setSeries(EncryptionUtil.decoderString(1, pqDevVO.getSeries()));
|
pqDevVO.setSeries(EncryptionUtil.decoderString(1, pqDevVO.getSeries()));
|
||||||
}
|
}
|
||||||
@@ -429,36 +470,38 @@ public class PqDevServiceImpl extends ServiceImpl<PqDevMapper, PqDev> implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void downloadImage(String id, HttpServletResponse response) {
|
public void previewImage(String id, String fileName, HttpServletResponse response) {
|
||||||
PqDev pqDev = this.getById(id);
|
writeDeviceImage(id, fileName, response, "inline");
|
||||||
if (ObjectUtil.isNull(pqDev) || pqDev.getImage() == null || pqDev.getImage().length == 0) {
|
|
||||||
throw new BusinessException(CommonResponseEnum.FAIL, "设备图片不存在");
|
|
||||||
}
|
|
||||||
String contentType = PqDevImageSupport.detectContentType(pqDev.getImage());
|
|
||||||
String filename = "device-image-" + id + "." + PqDevImageSupport.getExtension(contentType);
|
|
||||||
response.setContentType(contentType);
|
|
||||||
response.setContentLength(pqDev.getImage().length);
|
|
||||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
|
||||||
try {
|
|
||||||
response.getOutputStream().write(pqDev.getImage());
|
|
||||||
response.flushBuffer();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new BusinessException(CommonResponseEnum.FAIL, "设备图片下载失败");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setImageEchoFields(PqDevVO pqDevVO) {
|
@Override
|
||||||
|
public void downloadImage(String id, String fileName, HttpServletResponse response) {
|
||||||
|
writeDeviceImage(id, fileName, response, "attachment");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeDeviceImage(String id, String fileName, HttpServletResponse response, String disposition) {
|
||||||
|
PqDev pqDev = this.getById(id);
|
||||||
|
if (ObjectUtil.isNull(pqDev)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "device not found");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
PqDevImageSupport.ImageEchoPayload payload = PqDevImageSupport.buildEchoPayload(pqDevVO.getImage());
|
response.setContentType("image/jpeg");
|
||||||
pqDevVO.setHasImage(payload.isHasImage());
|
response.setContentLength((int) pqDevImageFileService.imageSize(id, fileName));
|
||||||
pqDevVO.setImageBase64(payload.getImageBase64());
|
response.setHeader("Content-Disposition", disposition + "; filename=\"" + fileName + "\"");
|
||||||
pqDevVO.setImageContentType(payload.getImageContentType());
|
pqDevImageFileService.writeImage(id, fileName, response.getOutputStream());
|
||||||
pqDevVO.setImage(null);
|
response.flushBuffer();
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new BusinessException(CommonResponseEnum.FAIL, e.getMessage());
|
throw new BusinessException(CommonResponseEnum.FAIL, e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "device image output failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setImageListFields(PqDevVO pqDevVO) {
|
||||||
|
List<PqDevImageVO> images = pqDevImageFileService.listImages(pqDevVO.getId());
|
||||||
|
pqDevVO.setImages(images);
|
||||||
|
pqDevVO.setHasImage(CollUtil.isNotEmpty(images));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取查询条件wrapper
|
* 获取查询条件wrapper
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.njcn.gather.device.util;
|
package com.njcn.gather.device.util;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.imageio.IIOImage;
|
import javax.imageio.IIOImage;
|
||||||
@@ -9,35 +7,53 @@ import javax.imageio.ImageIO;
|
|||||||
import javax.imageio.ImageWriteParam;
|
import javax.imageio.ImageWriteParam;
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.ImageWriter;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public final class PqDevImageSupport {
|
public final class PqDevImageSupport {
|
||||||
|
|
||||||
private static final long MAX_FILE_SIZE = 10L * 1024L * 1024L;
|
public static final long MAX_FILE_SIZE = 10L * 1024L * 1024L;
|
||||||
private static final int MAX_EDGE = 1600;
|
public static final int MAX_EDGE = 1600;
|
||||||
|
private static final String INVALID_IMAGE_CONTENT = "图片内容无效";
|
||||||
|
private static final String UNSUPPORTED_IMAGE_TYPE = "仅支持 JPG、JPEG、PNG 图片";
|
||||||
|
private static final String IMAGE_TOO_LARGE = "图片大小不能超过 10MB";
|
||||||
private static final float JPEG_QUALITY = 0.82f;
|
private static final float JPEG_QUALITY = 0.82f;
|
||||||
|
|
||||||
private PqDevImageSupport() {
|
private PqDevImageSupport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageEchoPayload buildEchoPayload(byte[] imageBytes) {
|
public static byte[] processUploadToJpeg(MultipartFile image) throws IOException {
|
||||||
if (imageBytes == null || imageBytes.length == 0) {
|
if (image == null || image.isEmpty()) {
|
||||||
return new ImageEchoPayload(false, null, null);
|
throw new IllegalArgumentException(INVALID_IMAGE_CONTENT);
|
||||||
}
|
}
|
||||||
return new ImageEchoPayload(true, Base64.getEncoder().encodeToString(imageBytes), detectContentType(imageBytes));
|
if (image.getSize() > MAX_FILE_SIZE) {
|
||||||
|
throw new IllegalArgumentException(IMAGE_TOO_LARGE);
|
||||||
|
}
|
||||||
|
if (!hasAllowedDeclaredType(image)) {
|
||||||
|
throw new IllegalArgumentException(UNSUPPORTED_IMAGE_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] originalBytes = image.getBytes();
|
||||||
|
detectContentType(originalBytes);
|
||||||
|
|
||||||
|
BufferedImage source = ImageIO.read(new ByteArrayInputStream(originalBytes));
|
||||||
|
if (source == null) {
|
||||||
|
throw new IllegalArgumentException(INVALID_IMAGE_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeJpeg(scaleIfNeeded(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String detectContentType(byte[] imageBytes) {
|
public static String detectContentType(byte[] imageBytes) {
|
||||||
if (imageBytes == null || imageBytes.length < 4) {
|
if (imageBytes == null || imageBytes.length < 4) {
|
||||||
throw new IllegalArgumentException("图片内容无效");
|
throw new IllegalArgumentException(INVALID_IMAGE_CONTENT);
|
||||||
}
|
}
|
||||||
if (isPng(imageBytes)) {
|
if (isPng(imageBytes)) {
|
||||||
return "image/png";
|
return "image/png";
|
||||||
@@ -45,47 +61,31 @@ public final class PqDevImageSupport {
|
|||||||
if (isJpeg(imageBytes)) {
|
if (isJpeg(imageBytes)) {
|
||||||
return "image/jpeg";
|
return "image/jpeg";
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("仅支持 JPG、PNG 图片");
|
throw new IllegalArgumentException(UNSUPPORTED_IMAGE_TYPE);
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] processUpload(MultipartFile image) throws IOException {
|
|
||||||
if (image == null || image.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (image.getSize() > MAX_FILE_SIZE) {
|
|
||||||
throw new IllegalArgumentException("图片大小不能超过 10MB");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean hasAllowedDeclaredType(MultipartFile image) {
|
||||||
String normalizedName = normalize(image.getOriginalFilename());
|
String normalizedName = normalize(image.getOriginalFilename());
|
||||||
String normalizedContentType = normalize(image.getContentType());
|
String normalizedContentType = normalize(image.getContentType());
|
||||||
boolean allowedType = normalizedContentType.equals("image/jpeg")
|
boolean hasContentType = !normalizedContentType.isEmpty();
|
||||||
|| normalizedContentType.equals("image/png")
|
boolean hasKnownExtension = normalizedName.endsWith(".jpg")
|
||||||
|| normalizedName.endsWith(".jpg")
|
|| normalizedName.endsWith(".jpeg")
|
||||||
|
|| normalizedName.endsWith(".png")
|
||||||
|
|| normalizedName.matches(".*\\.[^.]+$");
|
||||||
|
boolean contentTypeAllowed = normalizedContentType.equals("image/jpeg")
|
||||||
|
|| normalizedContentType.equals("image/jpg")
|
||||||
|
|| normalizedContentType.equals("image/png");
|
||||||
|
boolean extensionAllowed = normalizedName.endsWith(".jpg")
|
||||||
|| normalizedName.endsWith(".jpeg")
|
|| normalizedName.endsWith(".jpeg")
|
||||||
|| normalizedName.endsWith(".png");
|
|| normalizedName.endsWith(".png");
|
||||||
if (!allowedType) {
|
|
||||||
throw new IllegalArgumentException("仅支持 JPG、PNG 图片");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] originalBytes = image.getBytes();
|
if (hasContentType && !contentTypeAllowed) {
|
||||||
String contentType = detectContentType(originalBytes);
|
return false;
|
||||||
BufferedImage source;
|
|
||||||
try (InputStream inputStream = image.getInputStream()) {
|
|
||||||
source = ImageIO.read(inputStream);
|
|
||||||
}
|
}
|
||||||
if (source == null) {
|
if (hasKnownExtension && !extensionAllowed) {
|
||||||
throw new IllegalArgumentException("图片内容无效");
|
return false;
|
||||||
}
|
}
|
||||||
|
return contentTypeAllowed || extensionAllowed;
|
||||||
BufferedImage target = scaleIfNeeded(source);
|
|
||||||
if ("image/png".equals(contentType)) {
|
|
||||||
return writePng(target);
|
|
||||||
}
|
|
||||||
return writeJpeg(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getExtension(String contentType) {
|
|
||||||
return "image/png".equals(contentType) ? "png" : "jpg";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BufferedImage scaleIfNeeded(BufferedImage source) {
|
private static BufferedImage scaleIfNeeded(BufferedImage source) {
|
||||||
@@ -99,8 +99,7 @@ public final class PqDevImageSupport {
|
|||||||
double scale = (double) MAX_EDGE / max;
|
double scale = (double) MAX_EDGE / max;
|
||||||
int targetWidth = Math.max(1, (int) Math.round(width * scale));
|
int targetWidth = Math.max(1, (int) Math.round(width * scale));
|
||||||
int targetHeight = Math.max(1, (int) Math.round(height * scale));
|
int targetHeight = Math.max(1, (int) Math.round(height * scale));
|
||||||
int type = source.getType() == BufferedImage.TYPE_CUSTOM ? BufferedImage.TYPE_INT_ARGB : source.getType();
|
BufferedImage target = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
BufferedImage target = new BufferedImage(targetWidth, targetHeight, type);
|
|
||||||
Graphics2D graphics = target.createGraphics();
|
Graphics2D graphics = target.createGraphics();
|
||||||
try {
|
try {
|
||||||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
@@ -113,16 +112,12 @@ public final class PqDevImageSupport {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] writePng(BufferedImage image) throws IOException {
|
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
||||||
ImageIO.write(image, "png", outputStream);
|
|
||||||
return outputStream.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] writeJpeg(BufferedImage image) throws IOException {
|
private static byte[] writeJpeg(BufferedImage image) throws IOException {
|
||||||
BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
|
BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||||
Graphics2D graphics = rgbImage.createGraphics();
|
Graphics2D graphics = rgbImage.createGraphics();
|
||||||
try {
|
try {
|
||||||
|
graphics.setColor(Color.WHITE);
|
||||||
|
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
|
||||||
graphics.drawImage(image, 0, 0, null);
|
graphics.drawImage(image, 0, 0, null);
|
||||||
} finally {
|
} finally {
|
||||||
graphics.dispose();
|
graphics.dispose();
|
||||||
@@ -130,7 +125,7 @@ public final class PqDevImageSupport {
|
|||||||
|
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
|
||||||
if (!writers.hasNext()) {
|
if (!writers.hasNext()) {
|
||||||
throw new IOException("未找到 JPG 图片编码器");
|
throw new IOException("JPG image writer not found");
|
||||||
}
|
}
|
||||||
ImageWriter writer = writers.next();
|
ImageWriter writer = writers.next();
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
@@ -170,12 +165,4 @@ public final class PqDevImageSupport {
|
|||||||
private static String normalize(String value) {
|
private static String normalize(String value) {
|
||||||
return value == null ? "" : value.toLowerCase(Locale.ROOT);
|
return value == null ? "" : value.toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
public static class ImageEchoPayload {
|
|
||||||
private boolean hasImage;
|
|
||||||
private String imageBase64;
|
|
||||||
private String imageContentType;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,203 @@
|
|||||||
package com.njcn.gather.device.util;
|
package com.njcn.gather.device.util;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class PqDevImageSupportTest {
|
public class PqDevImageSupportTest {
|
||||||
|
|
||||||
@Test
|
private static final String INVALID_IMAGE_CONTENT = "图片内容无效";
|
||||||
public void buildEchoPayloadReturnsEmptyFieldsWhenImageMissing() {
|
private static final String UNSUPPORTED_IMAGE_TYPE = "仅支持 JPG、JPEG、PNG 图片";
|
||||||
PqDevImageSupport.ImageEchoPayload payload = PqDevImageSupport.buildEchoPayload(null);
|
private static final String IMAGE_TOO_LARGE = "图片大小不能超过 10MB";
|
||||||
|
|
||||||
assertFalse(payload.isHasImage());
|
@Test
|
||||||
assertNull(payload.getImageBase64());
|
public void processUploadConvertsPngToJpeg() throws Exception {
|
||||||
assertNull(payload.getImageContentType());
|
byte[] pngBytes = createImageBytes("png", 16, 10, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "test.png", "image/png", pngBytes);
|
||||||
|
|
||||||
|
byte[] result = PqDevImageSupport.processUploadToJpeg(image);
|
||||||
|
|
||||||
|
assertEquals("image/jpeg", PqDevImageSupport.detectContentType(result));
|
||||||
|
BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(result));
|
||||||
|
assertNotNull(decoded);
|
||||||
|
assertEquals(16, decoded.getWidth());
|
||||||
|
assertEquals(10, decoded.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void buildEchoPayloadDetectsPngAndCreatesBase64() throws Exception {
|
public void processUploadConvertsTransparentPngToWhiteJpegBackground() throws Exception {
|
||||||
byte[] imageBytes = createImageBytes("png");
|
byte[] pngBytes = createTransparentImageBytes();
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "transparent.png", "image/png", pngBytes);
|
||||||
|
|
||||||
PqDevImageSupport.ImageEchoPayload payload = PqDevImageSupport.buildEchoPayload(imageBytes);
|
byte[] result = PqDevImageSupport.processUploadToJpeg(image);
|
||||||
|
|
||||||
assertTrue(payload.isHasImage());
|
BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(result));
|
||||||
assertEquals("image/png", payload.getImageContentType());
|
assertNotNull(decoded);
|
||||||
assertNotNull(payload.getImageBase64());
|
Color background = new Color(decoded.getRGB(0, 0));
|
||||||
assertFalse(payload.getImageBase64().isEmpty());
|
assertTrue(background.getRed() >= 245);
|
||||||
|
assertTrue(background.getGreen() >= 245);
|
||||||
|
assertTrue(background.getBlue() >= 245);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
|
public void processUploadKeepsJpegReadable() throws Exception {
|
||||||
|
byte[] jpegBytes = createImageBytes("jpg", 12, 8, BufferedImage.TYPE_INT_RGB);
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "test.jpeg", "image/jpeg", jpegBytes);
|
||||||
|
|
||||||
|
byte[] result = PqDevImageSupport.processUploadToJpeg(image);
|
||||||
|
|
||||||
|
assertEquals("image/jpeg", PqDevImageSupport.detectContentType(result));
|
||||||
|
BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(result));
|
||||||
|
assertNotNull(decoded);
|
||||||
|
assertEquals(12, decoded.getWidth());
|
||||||
|
assertEquals(8, decoded.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadRejectsInvalidBytes() throws Exception {
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "test.png", "image/png", new byte[]{1, 2, 3});
|
||||||
|
|
||||||
|
assertIllegalArgumentMessage(INVALID_IMAGE_CONTENT, () -> PqDevImageSupport.processUploadToJpeg(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadRejectsNullImage() {
|
||||||
|
assertIllegalArgumentMessage(INVALID_IMAGE_CONTENT, () -> PqDevImageSupport.processUploadToJpeg(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadRejectsEmptyImage() {
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "empty.png", "image/png", new byte[0]);
|
||||||
|
|
||||||
|
assertIllegalArgumentMessage(INVALID_IMAGE_CONTENT, () -> PqDevImageSupport.processUploadToJpeg(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadRejectsOversizedImage() {
|
||||||
|
MockMultipartFile image = new MockMultipartFile(
|
||||||
|
"image",
|
||||||
|
"large.jpg",
|
||||||
|
"image/jpeg",
|
||||||
|
new byte[(int) PqDevImageSupport.MAX_FILE_SIZE + 1]
|
||||||
|
);
|
||||||
|
|
||||||
|
assertIllegalArgumentMessage(IMAGE_TOO_LARGE, () -> PqDevImageSupport.processUploadToJpeg(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadRejectsUnsupportedType() throws Exception {
|
||||||
|
byte[] pngBytes = createImageBytes("png", 2, 2, BufferedImage.TYPE_INT_RGB);
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "test.gif", "image/gif", pngBytes);
|
||||||
|
|
||||||
|
assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.processUploadToJpeg(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadRejectsUnsupportedContentTypeEvenWithAllowedFileName() throws Exception {
|
||||||
|
byte[] pngBytes = createImageBytes("png", 2, 2, BufferedImage.TYPE_INT_RGB);
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "test.png", "image/gif", pngBytes);
|
||||||
|
|
||||||
|
assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.processUploadToJpeg(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadRejectsUnsupportedFileNameEvenWithAllowedContentType() throws Exception {
|
||||||
|
byte[] pngBytes = createImageBytes("png", 2, 2, BufferedImage.TYPE_INT_RGB);
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "test.gif", "image/png", pngBytes);
|
||||||
|
|
||||||
|
assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.processUploadToJpeg(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadRejectsUnsupportedImageBytes() throws Exception {
|
||||||
|
byte[] gifBytes = new byte[]{0x47, 0x49, 0x46, 0x38, 0x39, 0x61};
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "test.png", "image/png", gifBytes);
|
||||||
|
|
||||||
|
assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.processUploadToJpeg(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processUploadScalesLargeImage() throws Exception {
|
||||||
|
byte[] jpegBytes = createImageBytes("jpg", 3200, 1200, BufferedImage.TYPE_INT_RGB);
|
||||||
|
MockMultipartFile image = new MockMultipartFile("image", "large.jpg", "image/jpeg", jpegBytes);
|
||||||
|
|
||||||
|
byte[] result = PqDevImageSupport.processUploadToJpeg(image);
|
||||||
|
|
||||||
|
BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(result));
|
||||||
|
assertNotNull(decoded);
|
||||||
|
assertEquals(PqDevImageSupport.MAX_EDGE, Math.max(decoded.getWidth(), decoded.getHeight()));
|
||||||
|
assertEquals(1600, decoded.getWidth());
|
||||||
|
assertEquals(600, decoded.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void detectContentTypeRejectsInvalidImageBytes() {
|
public void detectContentTypeRejectsInvalidImageBytes() {
|
||||||
PqDevImageSupport.detectContentType(new byte[]{1, 2, 3, 4});
|
assertIllegalArgumentMessage(INVALID_IMAGE_CONTENT, () -> PqDevImageSupport.detectContentType(new byte[]{1, 2, 3}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createImageBytes(String format) throws Exception {
|
@Test
|
||||||
BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
|
public void detectContentTypeRejectsUnsupportedImageBytes() {
|
||||||
image.setRGB(0, 0, Color.RED.getRGB());
|
assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.detectContentType(new byte[]{1, 2, 3, 4}));
|
||||||
image.setRGB(1, 0, Color.GREEN.getRGB());
|
}
|
||||||
image.setRGB(0, 1, Color.BLUE.getRGB());
|
|
||||||
image.setRGB(1, 1, Color.WHITE.getRGB());
|
private byte[] createImageBytes(String format, int width, int height, int type) throws Exception {
|
||||||
|
BufferedImage image = new BufferedImage(width, height, type);
|
||||||
|
Graphics2D graphics = image.createGraphics();
|
||||||
|
try {
|
||||||
|
graphics.setColor(new Color(255, 0, 0, type == BufferedImage.TYPE_INT_ARGB ? 120 : 255));
|
||||||
|
graphics.fillRect(0, 0, width, height);
|
||||||
|
graphics.setColor(Color.BLUE);
|
||||||
|
graphics.fillRect(width / 2, height / 2, Math.max(1, width / 2), Math.max(1, height / 2));
|
||||||
|
} finally {
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
ImageIO.write(image, format, outputStream);
|
assertTrue(ImageIO.write(image, format, outputStream));
|
||||||
return outputStream.toByteArray();
|
return outputStream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] createTransparentImageBytes() throws Exception {
|
||||||
|
BufferedImage image = new BufferedImage(4, 4, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D graphics = image.createGraphics();
|
||||||
|
try {
|
||||||
|
graphics.setComposite(java.awt.AlphaComposite.Clear);
|
||||||
|
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
|
||||||
|
graphics.setComposite(java.awt.AlphaComposite.SrcOver);
|
||||||
|
graphics.setColor(new Color(0, 0, 255, 128));
|
||||||
|
graphics.fillRect(2, 2, 2, 2);
|
||||||
|
} finally {
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
assertTrue(ImageIO.write(image, "png", outputStream));
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIllegalArgumentMessage(String expectedMessage, ThrowingRunnable runnable) {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
assertEquals(expectedMessage, ex.getMessage());
|
||||||
|
return;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new AssertionError("Expected IllegalArgumentException", ex);
|
||||||
|
}
|
||||||
|
throw new AssertionError("Expected IllegalArgumentException");
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ThrowingRunnable {
|
||||||
|
void run() throws Exception;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,12 +52,7 @@ socket:
|
|||||||
device:
|
device:
|
||||||
ip: 127.0.0.1
|
ip: 127.0.0.1
|
||||||
port: 61000
|
port: 61000
|
||||||
# source:
|
|
||||||
# ip: 192.168.1.121
|
|
||||||
# port: 10086
|
|
||||||
# device:
|
|
||||||
# ip: 192.168.1.121
|
|
||||||
# port: 61000
|
|
||||||
|
|
||||||
webSocket:
|
webSocket:
|
||||||
port: 7778
|
port: 7778
|
||||||
@@ -66,33 +61,15 @@ webSocket:
|
|||||||
sntp:
|
sntp:
|
||||||
port: 123
|
port: 123
|
||||||
|
|
||||||
#Dip:
|
|
||||||
# 暂态前时间(s)
|
|
||||||
# fPreTime: 2f
|
|
||||||
#写入时间(s)
|
|
||||||
# fRampIn: 0.001f
|
|
||||||
#写出时间(s)
|
|
||||||
# fRampOut: 0.001f
|
|
||||||
# 暂态后时间(s)
|
|
||||||
# fAfterTime: 3f
|
|
||||||
|
|
||||||
|
|
||||||
#Flicker:
|
|
||||||
# waveFluType: CPM
|
|
||||||
# waveType: SQU
|
|
||||||
# fDutyCycle: 50f
|
|
||||||
|
|
||||||
#log:
|
|
||||||
# homeDir: D:\logs
|
|
||||||
# commonLevel: info
|
|
||||||
report:
|
report:
|
||||||
# template: D:\template
|
# template: D:\template
|
||||||
# reportDir: D:\report
|
# reportDir: D:\report
|
||||||
dateFormat: yyyy年MM月dd日
|
dateFormat: yyyy年MM月dd日
|
||||||
#data:
|
device:
|
||||||
# homeDir: D:\data
|
image:
|
||||||
#resource:
|
dir: D:\device-images
|
||||||
# videoDir: ${data.homeDir}\resources\videos
|
|
||||||
qr:
|
qr:
|
||||||
cloud: http://pqmcc.com:18082/api/file
|
cloud: http://pqmcc.com:18082/api/file
|
||||||
dev:
|
dev:
|
||||||
|
|||||||
Reference in New Issue
Block a user