feat(REQ-2972): 支持传统分页和游标分页

This commit is contained in:
chenwenjian 2024-09-23 15:31:10 +08:00
parent 170bec1b89
commit 769126b9f8
4 changed files with 91 additions and 42 deletions

View File

@ -26,7 +26,8 @@ public interface LogApi {
/**
* 通过筛查条件查询日志列表
* <p>
* 若所有条件为空则返回按时间序倒排的1000条日志
* 1. 若所有条件为空则返回最近的1000条日志
* 2. 支持分页可通过开关控制默认开启最多返回1000条
* </p>
*
* @param req 查询条件 {@link LogFindReq}

View File

@ -41,28 +41,53 @@ public class LogFindReq {
* 日志内容筛查
* 具体使用可参考https://alidocs.dingtalk.com/i/nodes/YndMj49yWj7q1dB9hQLNneo0V3pmz5aA
* <p>
* message中可嵌套json当需要查询内部json中某个字段的相关情况时key为message.xxx例如message.userId
* "messageConditions": [
* {
* "key": "comments.commentId",
* "operator": "like",
* "value": "c"
* },{
* "key": "articleId",
* "operator": "not exists"
* }
* ]
* message中可嵌套json当需要查询内部json中某个字段的相关情况时key为message.xxx例如message.comments.commentId
* "messageConditions": [
* {
* "key": "comments.commentId",
* "operator": "like",
* "value": "c"
* },{
* "key": "articleId",
* "operator": "not exists"
* }
* ]
* </p>
*/
private List<@Valid Condition> messageConditions;
/**
* 开始时间时间戳字符串
* 开始时间时间戳
*/
private Long start;
/**
* 结束时间时间戳字符串
* 结束时间时间戳
*/
private Long end;
/**
* 分页页码从1开始{@code pageSize}搭配使用
* <p>
* 1. 分页开关开启后生效默认开启分页
* 2. 若与{@code lastId}同时存在则优先使用{@code lastId}参数
* </p>
*/
private Long pageNum;
/**
* 分页大小{@code pageNum}{@code lastId}搭配使用
* <p>最大限制为1000</p>
*/
private Integer pageSize;
/**
* 游标上次请求返回的最后一条日志的id{@code pageSize}搭配使用
* <p>
* 1. 分页开关开启后生效默认开启分页
* 2. 默认为空首次请求时不传此参数
* 3. 若与{@code pageNum}同时存在则优先使用本参数
* </p>
*/
private String lastId;
}

View File

@ -33,7 +33,8 @@ public class WebLogController {
/**
* 通过筛查条件查询日志列表
* <p>
* 若所有条件为空则返回按时间序倒排的1000条日志
* 1. 若所有条件为空则返回最近的1000条日志
* 2. 支持分页可通过开关控制默认开启最多返回1000条
* </p>
*
* @param req 查询条件 {@link LogFindReq}

View File

@ -17,8 +17,10 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
@ -38,6 +40,7 @@ import java.util.stream.Collectors;
* @version 1.0
* @date 2024/9/11 11:04
*/
@RefreshScope
@Service
@RequiredArgsConstructor
public class LogServiceImpl implements LogService {
@ -50,6 +53,12 @@ public class LogServiceImpl implements LogService {
public static String MESSAGE_FIELD_ARRAY_DEFAULT_KEY = "\"defaultItems\"";
/**
* 是否开启分页查询
*/
@Value("${log.enable-page:true}")
public boolean enablePage;
/**
* 添加日志
*
@ -72,13 +81,13 @@ public class LogServiceImpl implements LogService {
@Override
public LogResp findLogById(String id) {
return logMapper.findById(id)
.map(logEntity -> {
LogResp logResp = BeanUtil.copyProperties(logEntity, LogResp.class);
logResp.setMessage(logEntity.getMessage()
.toJson());
return logResp;
})
.orElse(null);
.map(logEntity -> {
LogResp logResp = BeanUtil.copyProperties(logEntity, LogResp.class);
logResp.setMessage(logEntity.getMessage()
.toJson());
return logResp;
})
.orElse(null);
}
/**
@ -95,7 +104,7 @@ public class LogServiceImpl implements LogService {
List<LogEntity> logs;
if (Objects.isNull(req) || isAllFieldsNullOrBlankOrEmpty(req)) {
// 按照时间倒序即字段timestamp查询1000条
logs = findTopLimitOrderByTimestamp(1, 1000L);
logs = findTopLimitOrderById(-1, 1000L);
if (ObjectUtil.isEmpty(logs)) {
return Collections.emptyList();
}
@ -108,13 +117,13 @@ public class LogServiceImpl implements LogService {
}
}
return logs.stream()
.map(logEntity -> {
LogResp logResp = BeanUtil.copyProperties(logEntity, LogResp.class, "message");
logResp.setMessage(logEntity.getMessage()
.toJson());
return logResp;
})
.collect(Collectors.toList());
.map(logEntity -> {
LogResp logResp = BeanUtil.copyProperties(logEntity, LogResp.class, "message");
logResp.setMessage(logEntity.getMessage()
.toJson());
return logResp;
})
.collect(Collectors.toList());
}
/**
@ -126,13 +135,13 @@ public class LogServiceImpl implements LogService {
@Override
public List<String> batchAddLogs(LogBatchAddReq req) {
List<LogEntity> logEntities = req.getLogs()
.stream()
.map(this::buildToLogEntity)
.collect(Collectors.toList());
.stream()
.map(this::buildToLogEntity)
.collect(Collectors.toList());
logMapper.saveAll(logEntities);
return logEntities.stream()
.map(LogEntity::getId)
.collect(Collectors.toList());
.map(LogEntity::getId)
.collect(Collectors.toList());
}
/**
@ -142,7 +151,7 @@ public class LogServiceImpl implements LogService {
* @return Query对象
*/
@NotNull
private static Query buildQueryCriteria(LogFindReq req) {
private Query buildQueryCriteria(LogFindReq req) {
Criteria criteria = new Criteria();
if (CharSequenceUtil.isNotBlank(req.getScene())) {
criteria.and("scene")
@ -167,8 +176,21 @@ public class LogServiceImpl implements LogService {
criteria.and("timestamp")
.lte(req.getEnd());
}
Sort sort = Sort.by(Sort.Direction.DESC, "timestamp");
Query query = new Query(criteria);
// 构建分页条件
if (enablePage) {
if (CharSequenceUtil.isNotBlank(req.getLastId())) {
criteria.and("_id").lt(new ObjectId(req.getLastId()));
} else if (NumberUtil.isPositiveNumber(req.getPageNum())) {
query.skip((req.getPageNum() - 1) * req.getPageSize());
}
if (NumberUtil.isNotPositiveNumber(req.getPageSize()) || req.getPageSize() > 1000) {
req.setPageSize(1000);
}
query.limit(req.getPageSize());
}
Sort sort = Sort.by(Sort.Direction.DESC, "_id");
query.with(sort);
return query;
}
@ -235,12 +257,12 @@ public class LogServiceImpl implements LogService {
* @param size 查询条数
* @return 日志列表
*/
List<LogEntity> findTopLimitOrderByTimestamp(Integer order, Long size) {
List<LogEntity> findTopLimitOrderById(Integer order, Long size) {
Criteria criteria = Criteria.where("timestamp")
.exists(true);
.exists(true);
Query query = new Query(criteria);
query.with(Sort.by(order > 0 ? Sort.Direction.ASC : Sort.Direction.DESC, "timestamp"))
.limit(size.intValue());
.limit(size.intValue());
return mongoTemplate.find(query, LogEntity.class);
}
@ -262,7 +284,7 @@ public class LogServiceImpl implements LogService {
Object value = field.get(obj);
if (value instanceof String) {
if (!((String) value).trim()
.isEmpty()) {
.isEmpty()) {
return false; // 字段不是空字符串
}
} else if (value instanceof Collection) {