日志文件下载功能,支持多实例日志
This commit is contained in:
-256
@@ -1,256 +0,0 @@
|
||||
package org.jeecg.modules.api.controller;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.log.ApiLog;
|
||||
import org.jeecg.common.log.ErrorLog;
|
||||
import org.jeecg.common.log.ErrorLogRepository;
|
||||
import org.jeecg.common.log.TaskLog;
|
||||
import org.jeecg.modules.api.bean.LogFilter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Shunzhi Jiang
|
||||
* @since 2023/10/13
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/service/log")
|
||||
@Slf4j
|
||||
@Tag(name = "system/日志")
|
||||
public class LogController {
|
||||
|
||||
@Autowired
|
||||
private ElasticsearchOperations elasticsearchOperations;
|
||||
|
||||
@Autowired
|
||||
private ErrorLogRepository errorLogRepository;
|
||||
|
||||
@Value("${logging.file.path:}")
|
||||
private String loggingFilePath;
|
||||
|
||||
@Operation(description = "接口日志列表")
|
||||
@PostMapping("/list")
|
||||
@RequiresRoles("admin")
|
||||
public Result<IPage<ApiLog>> pageList(@RequestBody LogFilter logFilter,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
|
||||
Pageable pageable = PageRequest.of(pageNo - 1, pageSize);
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
|
||||
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
|
||||
BoolQueryBuilder mustQueryBuilder = QueryBuilders.boolQuery();
|
||||
List<QueryBuilder> must = mustQueryBuilder.must();
|
||||
if (StringUtils.hasText(logFilter.getService())) {
|
||||
must.add(QueryBuilders.termQuery("service.keyword", logFilter.getService()));
|
||||
}
|
||||
if (Objects.nonNull(logFilter.getIsEx())) {
|
||||
must.add(QueryBuilders.termQuery("isEx", logFilter.getIsEx()));
|
||||
}
|
||||
if (Objects.nonNull(logFilter.getStart()) && Objects.nonNull(logFilter.getEnd())) {
|
||||
must.add(QueryBuilders.rangeQuery("createTime").gte(logFilter.getStart()).lte(logFilter.getEnd()));
|
||||
}
|
||||
if (Objects.nonNull(logFilter.getSpend())) {
|
||||
must.add(QueryBuilders.rangeQuery("spend").gte(logFilter.getSpend()));
|
||||
}
|
||||
if (StringUtils.hasText(logFilter.getKeyword())) {
|
||||
BoolQueryBuilder shouldQueryBuilder = QueryBuilders.boolQuery();
|
||||
List<QueryBuilder> should = shouldQueryBuilder.should();
|
||||
should.add(QueryBuilders.matchQuery("method", logFilter.getKeyword()));
|
||||
should.add(QueryBuilders.matchQuery("requestUrl", logFilter.getKeyword()));
|
||||
must.add(shouldQueryBuilder);
|
||||
}
|
||||
Query query = queryBuilder.withQuery(mustQueryBuilder)
|
||||
.withPageable(pageable)
|
||||
.withSort(sort)
|
||||
.withTrackTotalHits(Boolean.TRUE)
|
||||
.build();
|
||||
SearchHits<ApiLog> search = elasticsearchOperations.search(query, ApiLog.class);
|
||||
List<ApiLog> apiLogs = search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
|
||||
IPage<ApiLog> pageApiLog = new Page<>(pageNo, pageSize);
|
||||
pageApiLog.setRecords(apiLogs);
|
||||
pageApiLog.setTotal(search.getTotalHits());
|
||||
return Result.ok(pageApiLog);
|
||||
}
|
||||
|
||||
@Operation(description = "接口日志列表")
|
||||
@PostMapping("/taskList")
|
||||
@RequiresRoles("admin")
|
||||
public Result<IPage<TaskLog>> taskList(@RequestBody LogFilter logFilter,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
|
||||
Pageable pageable = PageRequest.of(pageNo - 1, pageSize);
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
|
||||
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
|
||||
BoolQueryBuilder mustQueryBuilder = QueryBuilders.boolQuery();
|
||||
List<QueryBuilder> must = mustQueryBuilder.must();
|
||||
if (StringUtils.hasText(logFilter.getService())) {
|
||||
must.add(QueryBuilders.termQuery("service", logFilter.getService()));
|
||||
}
|
||||
if (StringUtils.hasText(logFilter.getTaskType())) {
|
||||
must.add(QueryBuilders.termQuery("taskType", logFilter.getTaskType()));
|
||||
}
|
||||
if (StringUtils.hasText(logFilter.getTrigger())) {
|
||||
must.add(QueryBuilders.termQuery("trigger", logFilter.getTrigger()));
|
||||
}
|
||||
if (Objects.nonNull(logFilter.getIsEx())) {
|
||||
must.add(QueryBuilders.termQuery("isEx", logFilter.getIsEx()));
|
||||
}
|
||||
if (Objects.nonNull(logFilter.getStart()) && Objects.nonNull(logFilter.getEnd())) {
|
||||
must.add(QueryBuilders.rangeQuery("createTime").gte(logFilter.getStart()).lte(logFilter.getEnd()));
|
||||
}
|
||||
if (Objects.nonNull(logFilter.getSpend())) {
|
||||
must.add(QueryBuilders.rangeQuery("spend").gte(logFilter.getSpend()));
|
||||
}
|
||||
if (StringUtils.hasText(logFilter.getKeyword())) {
|
||||
BoolQueryBuilder shouldQueryBuilder = QueryBuilders.boolQuery();
|
||||
List<QueryBuilder> should = shouldQueryBuilder.should();
|
||||
should.add(QueryBuilders.matchQuery("taskName", logFilter.getKeyword()));
|
||||
should.add(QueryBuilders.matchQuery("method", logFilter.getKeyword()));
|
||||
must.add(shouldQueryBuilder);
|
||||
}
|
||||
Query query = queryBuilder.withQuery(mustQueryBuilder)
|
||||
.withPageable(pageable)
|
||||
.withSort(sort)
|
||||
.withTrackTotalHits(Boolean.TRUE)
|
||||
.build();
|
||||
SearchHits<TaskLog> search = elasticsearchOperations.search(query, TaskLog.class);
|
||||
List<TaskLog> taskLogs = search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
|
||||
IPage<TaskLog> pageTaskLog = new Page<>(pageNo, pageSize);
|
||||
pageTaskLog.setRecords(taskLogs);
|
||||
pageTaskLog.setTotal(search.getTotalHits());
|
||||
return Result.ok(pageTaskLog);
|
||||
}
|
||||
|
||||
@Operation(description = "错误日志ID查询")
|
||||
@GetMapping("/error/{id}")
|
||||
@RequiresRoles("admin")
|
||||
public Result<ErrorLog> getErrorLogById(@PathVariable String id) {
|
||||
return Result.ok(errorLogRepository.findById(id).orElseThrow(() -> new RuntimeException("未找到错误日志")));
|
||||
}
|
||||
|
||||
@Operation(description = "日志文件列表")
|
||||
@GetMapping("/files")
|
||||
@RequiresRoles("admin")
|
||||
public Result<IPage<String>> pageFiles(String service,
|
||||
@RequestParam(defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
Page<String> resultPage = Page.of(pageNo, pageSize);
|
||||
if (!checkPathExist()) {
|
||||
return Result.ok(resultPage);
|
||||
}
|
||||
File file = getLogPath(service).normalize().toFile();
|
||||
File[] files = file.listFiles();
|
||||
if (ArrayUtil.isEmpty(files)) {
|
||||
return Result.ok(resultPage);
|
||||
}
|
||||
List<String> fileNames = Arrays.stream(files)
|
||||
.filter(File::isFile)
|
||||
.sorted(Comparator.comparing(this::getLastModifiedTime).reversed())
|
||||
.map(File::getName)
|
||||
.collect(Collectors.toList());
|
||||
List<String> results = fileNames.subList(
|
||||
getStartIndex(pageNo, pageSize, fileNames.size()),
|
||||
getEndIndex(pageNo, pageSize, fileNames.size())
|
||||
);
|
||||
resultPage.setTotal(fileNames.size());
|
||||
resultPage.setRecords(results);
|
||||
return Result.ok(resultPage);
|
||||
}
|
||||
|
||||
private int getStartIndex(Integer pageNo, Integer pageSize, Integer listSize) {
|
||||
return Math.min(Math.max(0, pageNo - 1) * pageSize, Math.max(listSize, 0));
|
||||
}
|
||||
|
||||
private int getEndIndex(Integer pageNo, Integer pageSize, Integer listSize) {
|
||||
return Math.min(Math.max(1, pageNo) * pageSize, listSize);
|
||||
}
|
||||
|
||||
private Long getLastModifiedTime(File file) {
|
||||
try {
|
||||
Path path = file.toPath();
|
||||
BasicFileAttributes basicFileAttributes = Files.readAttributes(path, BasicFileAttributes.class);
|
||||
return basicFileAttributes.lastModifiedTime().toMillis();
|
||||
} catch (IOException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPathExist() {
|
||||
return StrUtil.isNotBlank(loggingFilePath);
|
||||
}
|
||||
|
||||
private Path getLogPath(String service) {
|
||||
String validService = validFilter(service);
|
||||
return Paths.get(loggingFilePath).getParent().resolve(validService);
|
||||
}
|
||||
|
||||
private String validFilter(String str) {
|
||||
return StrUtil.strip(StrUtil.strip(StrUtil.replace(str, "..", ""), "/"), "\\");
|
||||
}
|
||||
|
||||
@Operation(description = "日志文件下载")
|
||||
@GetMapping("/file/down")
|
||||
@RequiresRoles("admin")
|
||||
public ResponseEntity<FileSystemResource> fileDown(String service, String filename) {
|
||||
log.info("文件下载service:{}\tfilename:{}", service, filename);
|
||||
try {
|
||||
File file = getLogPath(service).resolve(filename).normalize().toFile();
|
||||
if (file.exists()) {
|
||||
log.info("下载日志【{}】", filename);
|
||||
FileSystemResource resource = new FileSystemResource(file);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
String name = URLEncoder.encode(file.getName(), "UTF-8");
|
||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
headers.setContentDisposition(ContentDisposition.attachment().filename(name).build()
|
||||
);
|
||||
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("下载文件出错service:{}\tfilename:{}", service, filename, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Operation(description = "日志文件删除")
|
||||
@GetMapping("/file/delete")
|
||||
@RequiresRoles("admin")
|
||||
public Result<Boolean> fileDelete(String service, String filename) throws IOException {
|
||||
return Result.ok(Files.deleteIfExists(getLogPath(service).resolve(filename).normalize()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user