多维度排序算法在企业级应用中的性能优化
多维度排序算法在企业级应用中的性能优化
业务需求:复杂的数据排序场景
在我们的系统中,排序需求非常多样化:
应用列表排序
- 创建时间:最新创建的应用排在前面
- 优先级:精选应用优先显示
- 用户权限:自己创建的应用优先
- 部署状态:已部署的应用优先
用户管理排序
- 注册时间:按注册时间倒序
- 用户角色:管理员优先显示
- 活跃度:最近登录用户优先
聊天历史排序
- 时间顺序:最新消息在前
- 消息类型:系统消息优先
- 游标分页:基于时间戳的高效分页
面对这样复杂的排序需求,我需要设计一套既高效又灵活的排序方案。
技术架构:统一的排序设计
基础分页请求设计
首先,我设计了一个通用的分页请求基类:
// 来源:src/main/java/com/ustinian/cheeseaicode/common/PageRequest.java (第6-27行)
@Data
public class PageRequest {
/**
* 当前页号
*/
private int pageNum = 1;
/**
* 页面大小
*/
private int pageSize = 10;
/**
* 排序字段
*/
private String sortField;
/**
* 排序顺序(默认降序)
*/
private String sortOrder = "descend";
}
设计亮点:
- 统一标准:所有分页查询都继承这个基类,保证接口的一致性
- 默认值设置:合理的默认分页大小和排序方向,减少前端配置
- 灵活排序:支持动态指定排序字段和方向
- 简洁设计:只包含核心字段,避免过度设计
应用查询请求实现
// 来源:src/main/java/com/ustinian/cheeseaicode/model/dto/app/AppQueryRequest.java (第12-57行)
@EqualsAndHashCode(callSuper = true)
@Data
public class AppQueryRequest extends PageRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 应用名称
*/
private String appName;
/**
* 应用封面
*/
private String cover;
/**
* 应用初始化的 prompt
*/
private String initPrompt;
/**
* 代码生成类型(枚举)
*/
private String codeGenType;
/**
* 部署标识
*/
private String deployKey;
/**
* 优先级
*/
private Integer priority;
/**
* 创建用户id
*/
private Long userId;
private static final long serialVersionUID = 1L;
}
这个设计继承了 PageRequest,既包含了分页和排序功能,又包含了应用相关的查询条件。
核心实现:QueryWrapper 的排序机制
应用列表的排序实现
// 来源:src/main/java/com/ustinian/cheeseaicode/service/impl/AppServiceImpl.java (第104-128行)
@Override
public QueryWrapper getQueryWrapper(AppQueryRequest appQueryRequest) {
if (appQueryRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
}
Long id = appQueryRequest.getId();
String appName = appQueryRequest.getAppName();
String cover = appQueryRequest.getCover();
String initPrompt = appQueryRequest.getInitPrompt();
String codeGenType = appQueryRequest.getCodeGenType();
String deployKey = appQueryRequest.getDeployKey();
Integer priority = appQueryRequest.getPriority();
Long userId = appQueryRequest.getUserId();
String sortField = appQueryRequest.getSortField();
String sortOrder = appQueryRequest.getSortOrder();
return QueryWrapper.create()
.eq("id", id)
.like("appName", appName)
.like("cover", cover)
.like("initPrompt", initPrompt)
.eq("codeGenType", codeGenType)
.eq("deployKey", deployKey)
.eq("priority", priority)
.eq("userId", userId)
.orderBy(sortField, "ascend".equals(sortOrder));
}
实现要点分析:
- 条件过滤:使用
eq()进行精确匹配,like()进行模糊搜索 - 动态排序:
orderBy(sortField, "ascend".equals(sortOrder))实现动态排序 - 空值处理:MyBatis-Flex 会自动忽略 null 值的条件
- 性能考虑:链式调用构建查询条件,避免重复创建对象
用户列表的排序实现
// 来源:src/main/java/com/ustinian/cheeseaicode/service/impl/UserServiceImpl.java (第157-175行)
@Override
public QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest) {
if (userQueryRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
}
Long id = userQueryRequest.getId();
String userAccount = userQueryRequest.getUserAccount();
String userName = userQueryRequest.getUserName();
String userProfile = userQueryRequest.getUserProfile();
String userRole = userQueryRequest.getUserRole();
String sortField = userQueryRequest.getSortField();
String sortOrder = userQueryRequest.getSortOrder();
return QueryWrapper.create()
.eq("id", id)
.eq("userRole", userRole)
.like("userAccount", userAccount)
.like("userName", userName)
.like("userProfile", userProfile)
.orderBy(sortField, "ascend".equals(sortOrder));
}
用户列表的排序逻辑与应用列表类似,但查询字段根据业务需求有所不同。这体现了我们设计的通用性和灵活性。
聊天历史的特殊排序实现
聊天历史的排序比较特殊,因为涉及到游标分页:
// 来源:src/main/java/com/ustinian/cheeseaicode/service/impl/ChatHistoryServiceImpl.java (第86-116行)
@Override
public QueryWrapper getQueryWrapper(ChatHistoryQueryRequest chatHistoryQueryRequest) {
QueryWrapper queryWrapper = QueryWrapper.create();
if (chatHistoryQueryRequest == null) {
return queryWrapper;
}
Long id = chatHistoryQueryRequest.getId();
String message = chatHistoryQueryRequest.getMessage();
String messageType = chatHistoryQueryRequest.getMessageType();
Long appId = chatHistoryQueryRequest.getAppId();
Long userId = chatHistoryQueryRequest.getUserId();
LocalDateTime lastCreateTime = chatHistoryQueryRequest.getLastCreateTime();
String sortField = chatHistoryQueryRequest.getSortField();
String sortOrder = chatHistoryQueryRequest.getSortOrder();
// 拼接查询条件
queryWrapper.eq("id", id)
.like("message", message)
.eq("messageType", messageType)
.eq("appId", appId)
.eq("userId", userId);
// 游标查询逻辑 - 只使用 createTime 作为游标
if (lastCreateTime != null) {
queryWrapper.lt("createTime", lastCreateTime);
}
// 排序
if (StrUtil.isNotBlank(sortField)) {
queryWrapper.orderBy(sortField, "ascend".equals(sortOrder));
} else {
// 默认按创建时间降序排列
queryWrapper.orderBy("createTime", false);
}
return queryWrapper;
}
聊天历史排序的特殊处理:
- 游标分页:使用
lastCreateTime作为游标,实现高效的时间分页 - 默认排序:当没有指定排序字段时,默认按创建时间降序
- 时间过滤:
lt("createTime", lastCreateTime)实现游标查询
分页查询的完整流程
控制器层的分页处理
// 来源:src/main/java/com/ustinian/cheeseaicode/controller/AppController.java (第200-213行)
User loginUser = userService.getLoginUser(request);
// 限制每页最多 20 个
long pageSize = appQueryRequest.getPageSize();
ThrowUtils.throwIf(pageSize > 20, ErrorCode.PARAMS_ERROR, "每页最多查询 20 个应用");
long pageNum = appQueryRequest.getPageNum();
// 只查询当前用户的应用
appQueryRequest.setUserId(loginUser.getId());
QueryWrapper queryWrapper = appService.getQueryWrapper(appQueryRequest);
Page<App> appPage = appService.page(Page.of(pageNum, pageSize), queryWrapper);
// 数据封装
Page<AppVO> appVOPage = new Page<>(pageNum, pageSize, appPage.getTotalRow());
List<AppVO> appVOList = appService.getAppVOList(appPage.getRecords());
appVOPage.setRecords(appVOList);
return ResultUtils.success(appVOPage);
分页流程解析:
- 参数校验:限制每页最多查询 20 个应用,防止大查询
- 权限控制:设置
userId确保只查询当前用户的应用 - 查询执行:使用
page()方法执行分页查询 - 数据转换:将实体对象转换为 VO 对象,保护敏感数据
- 结果封装:返回包含分页信息的完整结果
性能优化策略
1. 数据库层面的优化
索引设计:
-- 应用表的复合索引
CREATE INDEX idx_app_user_priority_time ON app(userId, priority, createTime DESC);
CREATE INDEX idx_app_codegen_time ON app(codeGenType, createTime DESC);
-- 用户表的索引
CREATE INDEX idx_user_role_time ON user(userRole, createTime DESC);
-- 聊天历史的索引
CREATE INDEX idx_chat_app_time ON chat_history(appId, createTime DESC);
索引选择原则:
- 高选择性字段优先:将选择性高的字段放在索引前面
- 排序字段在最后:排序字段通常放在复合索引的最后
- 覆盖索引优化:尽量让索引覆盖查询的所有字段
2. 应用层面的优化
分页大小限制:
// 限制每页最多 20 个
long pageSize = appQueryRequest.getPageSize();
ThrowUtils.throwIf(pageSize > 20, ErrorCode.PARAMS_ERROR, "每页最多查询 20 个应用");
查询缓存策略:
// 来源:src/main/java/com/ustinian/cheeseaicode/controller/AppController.java (精选应用缓存)
@PostMapping("/good/list/page/vo")
@Cacheable(
value = "good_app_page",
key = "T(com.ustinian.cheeseaicode.utils.CacheKeyUtils).generateKey(#appQueryRequest)",
condition = "#appQueryRequest.pageNum <= 10"
)
public BaseResponse<Page<AppVO>> listGoodAppVOByPage(@RequestBody AppQueryRequest appQueryRequest) {
// 查询逻辑
}
游标分页优化:
// 来源:src/main/java/com/ustinian/cheeseaicode/service/impl/ChatHistoryServiceImpl.java (第152-155行)
QueryWrapper queryWrapper = QueryWrapper.create()
.eq(ChatHistory::getAppId, appId)
.orderBy(ChatHistory::getCreateTime, false)
.limit(1, maxCount);

浙公网安备 33010602011771号