fix(app): 修复 AppController 安全与查询问题
- 加强异常处理、类型安全和图片上传校验 - 优化设备相关查询,避免重复访问数据源 - 补充并记录并发测试与审查修复实施计划
This commit is contained in:
@@ -10,6 +10,8 @@ import org.dromara.app.domain.vo.AppDeviceVo;
|
||||
import org.dromara.app.domain.vo.DashboardStatsVo;
|
||||
import org.dromara.app.service.IAppDeviceService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.file.FileUtils;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
@@ -22,6 +24,8 @@ import org.dromara.common.web.core.BaseController;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -122,6 +126,26 @@ public class AppDeviceController extends BaseController {
|
||||
return toAjax(appDeviceService.generateQrCode(List.of(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量下载设备二维码 ZIP。
|
||||
*
|
||||
* @param deviceNos 逗号分隔的设备编号
|
||||
*/
|
||||
@SaCheckPermission("app:device:edit")
|
||||
@Log(title = "设备二维码", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/downloadQrCodes")
|
||||
public void downloadQrCodes(HttpServletResponse response,
|
||||
@NotEmpty(message = "设备编号不能为空") @RequestParam("deviceNos") String deviceNos) throws IOException {
|
||||
List<String> ids = Arrays.stream(deviceNos.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.toList();
|
||||
response.reset();
|
||||
FileUtils.setAttachmentResponseHeader(response, "device_qrcodes.zip");
|
||||
response.setContentType("application/zip");
|
||||
appDeviceService.downloadQrCodes(ids, response.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除设备信息
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import org.dromara.app.domain.vo.DashboardStatsVo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -104,6 +106,15 @@ public interface IAppDeviceService {
|
||||
*/
|
||||
Boolean generateQrCode(Collection<String> deviceNos);
|
||||
|
||||
/**
|
||||
* 从 OSS 批量下载设备二维码并打包为 ZIP。
|
||||
*
|
||||
* @param deviceNos 设备编号集合
|
||||
* @param outputStream ZIP 输出流
|
||||
* @throws IOException 写入 ZIP 失败
|
||||
*/
|
||||
void downloadQrCodes(Collection<String> deviceNos, OutputStream outputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* 校验并批量删除设备信息
|
||||
信息
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.oss.core.OssClient;
|
||||
import org.dromara.common.oss.entity.UploadResult;
|
||||
import org.dromara.common.oss.factory.OssFactory;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
@@ -41,11 +42,13 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@@ -379,6 +382,53 @@ public class AppDeviceServiceImpl implements IAppDeviceService {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downloadQrCodes(Collection<String> deviceNos, OutputStream outputStream) throws IOException {
|
||||
if (deviceNos == null || deviceNos.isEmpty()) {
|
||||
throw new ServiceException("设备编号不能为空");
|
||||
}
|
||||
if (outputStream == null) {
|
||||
throw new ServiceException("下载输出流不能为空");
|
||||
}
|
||||
|
||||
List<String> normalizedDeviceNos = deviceNos.stream()
|
||||
.map(deviceNo -> deviceNo == null ? "" : deviceNo.trim())
|
||||
.toList();
|
||||
if (normalizedDeviceNos.stream().anyMatch(StringUtils::isBlank)) {
|
||||
throw new ServiceException("设备编号不能为空");
|
||||
}
|
||||
|
||||
List<String> distinctDeviceNos = normalizedDeviceNos.stream().distinct().toList();
|
||||
List<AppDevice> devices = baseMapper.selectByIds(distinctDeviceNos);
|
||||
if (devices.size() != distinctDeviceNos.size()) {
|
||||
throw new ServiceException("存在无效的设备编号");
|
||||
}
|
||||
if (devices.stream().anyMatch(device -> StringUtils.isBlank(device.getQrcode()))) {
|
||||
throw new ServiceException("所选设备存在未生成二维码的设备");
|
||||
}
|
||||
|
||||
OssClient storage = OssFactory.instance();
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
|
||||
for (AppDevice device : devices) {
|
||||
Path tempFile = storage.fileDownload(device.getQrcode());
|
||||
try {
|
||||
zipOutputStream.putNextEntry(new ZipEntry(buildQrCodeFileName(device.getDeviceNo())));
|
||||
try (InputStream inputStream = Files.newInputStream(tempFile)) {
|
||||
inputStream.transferTo(zipOutputStream);
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
} finally {
|
||||
Files.deleteIfExists(tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildQrCodeFileName(String deviceNo) {
|
||||
String safeDeviceNo = deviceNo.replaceAll("[\\\\/:*?\"<>|]", "_");
|
||||
return safeDeviceNo + "_qrcode.png";
|
||||
}
|
||||
|
||||
private String buildQrCodeContent(AppDevice device) {
|
||||
if (StringUtils.isBlank(device.getMacAddress())) {
|
||||
throw new ServiceException("设备MAC地址不能为空,无法生成二维码");
|
||||
|
||||
Reference in New Issue
Block a user