add:新增日志管理,日志下载功能。2.二维码下载。3.异常信息的导出导出功能。

This commit is contained in:
2026-02-04 17:44:33 +08:00
parent dd6f7ac79d
commit 587308951a
14 changed files with 332 additions and 4 deletions

View File

@@ -2,9 +2,11 @@ package org.nl.sys.modular.anomalyInfo.controller;
import com.alibaba.excel.EasyExcel;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.nl.config.language.LangProcess;
import org.nl.exception.BadRequestException;
import org.nl.logging.annotation.Log;
import org.nl.response.WebResponse;
import org.nl.sys.modular.anomalyInfo.dao.ErrorHandling;
import org.nl.sys.modular.anomalyInfo.dao.ErrorInfo;
import org.nl.sys.modular.anomalyInfo.listener.ErrorHandlingListener;
@@ -23,6 +25,8 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
/**
* @author dsh
@@ -66,7 +70,7 @@ public class AnomalyInfoController {
EasyExcel.read(file.getInputStream(), ErrorInfo.class, listener)
.sheet() // 默认读取第一个sheet
.doRead();
return new ResponseEntity<>("操作成功",HttpStatus.OK);
return new ResponseEntity<>(WebResponse.requestOk(),HttpStatus.OK);
}
@PostMapping("/importErrorHandlingExcel")
@@ -85,7 +89,7 @@ public class AnomalyInfoController {
EasyExcel.read(file.getInputStream(), ErrorHandling.class, listener)
.sheet() // 默认读取第一个sheet
.doRead();
return new ResponseEntity<>("操作成功",HttpStatus.OK);
return new ResponseEntity<>(WebResponse.requestOk(),HttpStatus.OK);
}
@PostMapping("/importErrorImage")
@@ -112,7 +116,7 @@ public class AnomalyInfoController {
parseZip.processCompressedFile(file);
return new ResponseEntity<>("上传成功!",HttpStatus.OK);
return new ResponseEntity<>(LangProcess.msg("successful"),HttpStatus.OK);
} catch (Exception e) {
throw new BadRequestException(LangProcess.msg("anomaly_file_parse_failed",e.getMessage()));
@@ -131,4 +135,36 @@ public class AnomalyInfoController {
return new ResponseEntity<>(errorInfoService.updateErrorInfo(errorInfo),HttpStatus.OK);
}
@GetMapping("/exportErrorInfoExcel")
@Log("导出异常信息")
public void exportErrorInfoExcel(HttpServletResponse response) throws IOException {
List<ErrorInfo> taskInfoList = errorInfoService.exportErrorInfoExcel();
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(LangProcess.msg("error_info")+".xlsx", "UTF-8"));
// 使用EasyExcel写入数据到响应输出流
EasyExcel.write(response.getOutputStream(), ErrorInfo.class)
.sheet(LangProcess.msg("error_info"))
.doWrite(taskInfoList);
}
@GetMapping("/exportErrorHandlingInfoExcel")
@Log("导出异常处理信息")
public void exportErrorHandlingInfoExcel(HttpServletResponse response) throws IOException {
List<ErrorHandling> taskInfoList = errorHandlingService.exportErrorHandlingInfoExcel();
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(LangProcess.msg("error_handling_info")+".xlsx", "UTF-8"));
// 使用EasyExcel写入数据到响应输出流
EasyExcel.write(response.getOutputStream(), ErrorHandling.class)
.sheet(LangProcess.msg("error_handling_info"))
.doWrite(taskInfoList);
}
}

View File

@@ -3,10 +3,16 @@ package org.nl.sys.modular.anomalyInfo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.nl.sys.modular.anomalyInfo.dao.ErrorHandling;
import java.util.List;
/**
* @author dsh
* 2025/12/29
*/
public interface ErrorHandlingService extends IService<ErrorHandling> {
/**
* 导出异常处理信息
* @return
*/
List<ErrorHandling> exportErrorHandlingInfoExcel();
}

View File

@@ -5,6 +5,8 @@ import org.nl.response.WebResponse;
import org.nl.sys.modular.anomalyInfo.dao.ErrorInfo;
import org.nl.sys.modular.anomalyInfo.param.QueryErrorInfoPageParam;
import java.util.List;
/**
* @author dsh
* 2025/12/29
@@ -24,4 +26,11 @@ public interface ErrorInfoService extends IService<ErrorInfo> {
* @return
*/
WebResponse updateErrorInfo(ErrorInfo errorInfo);
/**
* 导出异常信息
* @return
*/
List<ErrorInfo> exportErrorInfoExcel();
}

View File

@@ -1,15 +1,28 @@
package org.nl.sys.modular.anomalyInfo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.nl.sys.modular.anomalyInfo.dao.ErrorHandling;
import org.nl.sys.modular.anomalyInfo.dao.ErrorInfo;
import org.nl.sys.modular.anomalyInfo.service.ErrorHandlingService;
import org.nl.sys.modular.anomalyInfo.mapper.ErrorHandlingMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author dsh
* 2025/12/29
*/
@Service
public class ErrorHandlingServiceImpl extends ServiceImpl<ErrorHandlingMapper, ErrorHandling> implements ErrorHandlingService {
@Resource
private ErrorHandlingMapper errorHandlingMapper;
@Override
public List<ErrorHandling> exportErrorHandlingInfoExcel() {
return errorHandlingMapper.selectList(new LambdaQueryWrapper<>(ErrorHandling.class));
}
}

View File

@@ -16,6 +16,8 @@ import org.nl.sys.modular.anomalyInfo.service.ErrorInfoService;
import org.nl.sys.modular.anomalyInfo.mapper.ErrorInfoMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author dsh
* 2025/12/29
@@ -45,4 +47,10 @@ public class ErrorInfoServiceImpl extends ServiceImpl<ErrorInfoMapper, ErrorInfo
}
return WebResponse.requestOk();
}
@Override
public List<ErrorInfo> exportErrorInfoExcel() {
return errorInfoMapper.selectList(new LambdaQueryWrapper<>(ErrorInfo.class));
}
}

View File

@@ -0,0 +1,71 @@
package org.nl.sys.modular.logging.controller;
import org.nl.exception.BadRequestException;
import org.nl.logging.annotation.Log;
import org.nl.sys.modular.logging.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* @author dsh
* 2025/11/3
*/
@RestController
@RequestMapping("/api/logs")
public class LogController {
@Autowired
private LogService logService;
/**
* 获取日志文件列表
*/
@Log("获取日志文件列表")
@GetMapping("/list")
public ResponseEntity<Object> getLogFiles() {
try {
return new ResponseEntity<>(logService.getLogFiles(), HttpStatus.OK);
} catch (Exception e) {
throw new BadRequestException("获取日志文件失败:"+e.getMessage());
}
}
/**
* 下载日志文件
*/
@Log("下载日志文件")
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadLogFile(@PathVariable String fileName) {
try {
File file = logService.getLogFile(fileName);
Path filePath = file.toPath();
Resource resource = new InputStreamResource(Files.newInputStream(filePath));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getName() + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(file.length())
.body(resource);
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}

View File

@@ -0,0 +1,34 @@
package org.nl.sys.modular.logging.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author dsh
* 2025/11/3
*/
@Data
public class LogFileDTO {
private String fileName;
private String filePath;
private Long fileSize;
private LocalDateTime lastModified;
private String readableSize;
public LogFileDTO(String fileName, String filePath, Long fileSize, LocalDateTime lastModified) {
this.fileName = fileName;
this.filePath = filePath;
this.fileSize = fileSize;
this.lastModified = lastModified;
this.readableSize = formatFileSize(fileSize);
}
private String formatFileSize(long size) {
if (size <= 0) return "0 B";
final String[] units = new String[]{"B", "KB", "MB", "GB"};
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
return String.format("%.1f %s", size / Math.pow(1024, digitGroups), units[digitGroups]);
}
}

View File

@@ -0,0 +1,81 @@
package org.nl.sys.modular.logging.service;
import org.nl.sys.modular.logging.dto.LogFileDTO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.File;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
/**
* @author dsh
* 2025/11/3
*/
@Service
public class LogService {
@Value("${customize.log-dir}")
private String logDir;
/**
* 获取日志文件列表
*/
public List<LogFileDTO> getLogFiles() {
List<LogFileDTO> logFiles = new ArrayList<>();
File directory = new File(logDir);
if (!directory.exists() || !directory.isDirectory()) {
return logFiles;
}
File[] files = directory.listFiles((dir, name) ->
name.endsWith(".log") || name.endsWith(".txt"));
if (files != null) {
for (File file : files) {
if (file.isFile()) {
LogFileDTO logFile = new LogFileDTO(
file.getName(),
file.getAbsolutePath(),
file.length(),
LocalDateTime.ofInstant(
Instant.ofEpochMilli(file.lastModified()),
ZoneId.systemDefault()
)
);
logFiles.add(logFile);
}
}
}
// 按修改时间倒序排列
logFiles.sort((a, b) -> b.getLastModified().compareTo(a.getLastModified()));
return logFiles;
}
/**
* 获取日志文件
*/
public File getLogFile(String fileName) {
// 防止路径遍历攻击
if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
throw new IllegalArgumentException("Invalid file name");
}
File file = new File(logDir, fileName);
if (!file.exists() || !file.isFile()) {
throw new RuntimeException("File not found: " + fileName);
}
// 确保文件在日志目录内
if (!file.getAbsolutePath().startsWith(new File(logDir).getAbsolutePath())) {
throw new SecurityException("Access denied");
}
return file;
}
}

View File

@@ -9,11 +9,18 @@ import org.nl.sys.modular.qrcode.param.GenerateQRCodeParam;
import org.nl.sys.modular.qrcode.param.QueryQRCodeParam;
import org.nl.sys.modular.qrcode.param.UpdateQRCodeParam;
import org.nl.sys.modular.qrcode.service.QRCodeService;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* @author dsh
* 2025/12/22
@@ -72,4 +79,30 @@ public class QRCodeController {
public ResponseEntity<Object> taskOperationConfirm(@RequestParam String taskCode){
return new ResponseEntity<>(qrCodeService.taskOperationConfirm(taskCode),HttpStatus.OK);
}
/**
* 下载二维码文件
*/
@Log("下载二维码文件")
@GetMapping("/download/{fileName}")
public ResponseEntity<org.springframework.core.io.Resource> downloadQRCodeFile(@PathVariable String fileName) {
try {
File file = qrCodeService.getQRCdoeFile(fileName);
Path filePath = file.toPath();
org.springframework.core.io.Resource resource = new InputStreamResource(Files.newInputStream(filePath));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getName() + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(file.length())
.body(resource);
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}

View File

@@ -7,6 +7,8 @@ import org.nl.sys.modular.qrcode.param.GenerateQRCodeParam;
import org.nl.sys.modular.qrcode.param.QueryQRCodeParam;
import org.nl.sys.modular.qrcode.param.UpdateQRCodeParam;
import java.io.File;
/**
* @author dsh
* 2025/12/22
@@ -74,4 +76,11 @@ public interface QRCodeService {
* @return
*/
boolean verifyQRCodeByRoom(String room);
/**
* 获取二维码文件
* @param fileName
* @return
*/
File getQRCdoeFile(String fileName);
}

View File

@@ -28,6 +28,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.File;
/**
* @author dsh
* 2025/12/22
@@ -176,4 +178,24 @@ public class QRCodeServiceImpl implements QRCodeService {
.eq(QRcodeInfo::getRoom_code,room)
)>0;
}
@Override
public File getQRCdoeFile(String fileName) {
// 防止路径遍历攻击
if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
throw new IllegalArgumentException("Invalid file name");
}
File file = new File(fileProperties.getPath().getQrcode(), fileName);
if (!file.exists() || !file.isFile()) {
throw new RuntimeException("File not found: " + fileName);
}
// 确保文件在日志目录内
if (!file.getAbsolutePath().startsWith(new File(fileProperties.getPath().getQrcode()).getAbsolutePath())) {
throw new SecurityException("Access denied");
}
return file;
}
}

View File

@@ -47,6 +47,8 @@ task_confirm_arrival_failed = 任务到达确认失败
task_next_station_failed = 任务下一站失败:{0}
# 异常信息相关
error_info = 异常信息
error_handling_info = 异常处理信息
anomaly_file_not_selected = 请选择文件上传
anomaly_file_name_empty = 文件名称为空
anomaly_file_format_error = 目前只支持ZIP格式

View File

@@ -47,6 +47,8 @@ task_confirm_arrival_failed = Failed to confirm task arrival
task_next_station_failed = Failed to go to next station:{0}
# Anomaly related
error_info = Anomaly information
error_handling_info = Exception handling information
anomaly_file_not_selected = Please select a file to upload
anomaly_file_name_empty = File name is empty
anomaly_file_format_error = Currently only ZIP format is supported

View File

@@ -47,6 +47,8 @@ task_confirm_arrival_failed = 任务到达确认失败
task_next_station_failed = 任务下一站失败:{0}
# 异常信息相关
error_info = 异常信息
error_handling_info = 异常处理信息
anomaly_file_not_selected = 请选择文件上传
anomaly_file_name_empty = 文件名称为空
anomaly_file_format_error = 目前只支持ZIP格式