package com.wok.supportbot.controller; import com.wok.supportbot.service.LogViewerService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.io.File; import java.util.List; import java.util.Map; /** * 系统日志查看接口 *

* 提供日志文件列表、尾部内容查看、关键词搜索及文件下载功能。 * 仅 admin 角色可访问,所有文件操作限制在日志目录内,防止路径遍历攻击。 */ @Slf4j @RestController @RequestMapping("/log") @PreAuthorize("hasRole('admin')") public class LogController { @Autowired private LogViewerService logViewerService; /** * 列出所有日志文件(名称、大小、修改时间等) */ @GetMapping("/files") public ResponseEntity> listFiles() { try { List> files = logViewerService.listFiles(); return ResponseEntity.ok(Map.of( "success", true, "data", files )); } catch (Exception e) { log.error("获取日志文件列表失败", e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "获取日志文件列表失败:" + e.getMessage() )); } } /** * 查看日志文件尾部内容 * * @param fileName 日志文件名 * @param lines 读取最后 N 行(默认 200,上限 5000) * @param keyword 可选搜索关键词 */ @GetMapping("/view/{fileName}") public ResponseEntity> viewLog( @PathVariable String fileName, @RequestParam(defaultValue = "200") int lines, @RequestParam(required = false) String keyword) { try { Map result = logViewerService.readLogTail(fileName, lines, keyword); return ResponseEntity.ok(Map.of( "success", true, "data", result )); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body(Map.of( "success", false, "message", e.getMessage() )); } catch (Exception e) { log.error("查看日志失败: {}", fileName, e); return ResponseEntity.status(500).body(Map.of( "success", false, "message", "查看日志失败:" + e.getMessage() )); } } /** * 下载日志文件(原始文件流) * * @param fileName 日志文件名 */ @GetMapping("/download/{fileName}") public ResponseEntity downloadLog(@PathVariable String fileName) { try { java.nio.file.Path filePath = logViewerService.resolveSafe(fileName); File file = filePath.toFile(); FileSystemResource resource = new FileSystemResource(file); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"") .contentType(MediaType.TEXT_PLAIN) .contentLength(file.length()) .body(resource); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().build(); } catch (Exception e) { log.error("下载日志失败: {}", fileName, e); return ResponseEntity.status(500).build(); } } }